diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e9ccba1a..734dc25e0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,8 @@ ], "typescript.enablePromptUseWorkspaceTsdk": true, "typescript.tsdk": "node_modules/typescript/lib", - "css.lint.unknownAtRules": "ignore" + "css.lint.unknownAtRules": "ignore", + "files.associations": { + "*.css": "tailwindcss" + } } diff --git a/GUIDE.md b/GUIDE.md new file mode 100644 index 000000000..7a85c37e7 --- /dev/null +++ b/GUIDE.md @@ -0,0 +1,33 @@ +# ABC User Feedback Guide + +## Image Storage Integration + +ABC User Feedback supports the integration of image storage solutions to handle images submitted as part of user feedback. We currently support AWS S3 and S3-compatible storage services. + +### Uploading Images + +There are two methods for uploading images associated with feedback: + +1. **Multipart Upload API**: This method requires setting up the [image configuration](#configuration). Once configured, you can use the multipart upload API to securely upload images directly to your storage service. + +2. **Feedback Creation API with Image URLs**: Alternatively, users can submit feedback with image URLs. This method does not require the image configuration setup; however, the image URLs must come from the whitelisted domains. + +**Note**: For detailed instructions on using these methods, please refer to the API documentation. You can see the documentation by accessing to `{API server host}/docs` or `{API server host}/docs/redoc`. + +### Configuration + +To enable image uploads directly to the server, you must configure the image storage settings. The service uses the following configuration parameters and you can set them in the setting menu. + +- `accessKeyId`: Your storage service access key ID. +- `secretAccessKey`: Your storage service secret access key. +- `endpoint`: The endpoint URL for the storage service. +- `region`: The region your storage service is located in. +- `bucket`: The name of the bucket where images will be stored. + +Depending on your use case and the desired level of access, you may need to adjust the permissions of your S3 bucket. If your application requires that the images be publicly accessible, configure your S3 bucket's policy to allow public reads. + +### Domain Whitelist + +Users can specify a whitelist of domains for image URLs. This ensures that only images from trusted sources are accepted and managed by User Feedback API server. + +**Note**: The domain whitelist is enforced at the time of posting feedback with images. This means that validation against the whitelist occurs only during the submission of new feedback. Once an image URL has been uploaded to the database and accepted, it will be accessible through the web admin interface regardless of its current status on the whitelist. It is important to ensure that image URLs are from trusted sources before they are uploaded, as subsequent changes to the whitelist will not retroactively affect previously stored image URLs. diff --git a/README.md b/README.md index f8dfbffd2..288f9ca35 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ ABC User Feedback provides the following features: ![role management image](./assets/05-role-management.png) ![dashboard image](./assets/06-dashboard.png) - ## Getting Started The frontend is built with NextJS and the backend is built with NestJS. We provide Docker images for fast and easy setup. @@ -114,6 +113,10 @@ yarn turbo run dev --filter=web yarn turbo run dev --filter=api ``` +### ADMIN WEB GUIDE + +For detailed information on using the admin web interface, please refer to our [Admin Web Guide](./GUIDE.md). + ### Build Docker Image For your code build, you can build docker image using docker-compose. Please refer to [remote caching](https://turbo.build/repo/docs/core-concepts/remote-caching) and [deploying with docker](https://turbo.build/repo/docs/handbook/deploying-with-docker) using `turborepo`. @@ -151,4 +154,3 @@ under the License. ``` See [LICENSE](./LICENSE) for more details. - diff --git a/apps/api/package.json b/apps/api/package.json index d17acb94f..cd7bccd41 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -25,8 +25,9 @@ }, "prettier": "@ufb/prettier-config", "dependencies": { - "@aws-sdk/client-s3": "^3.465.0", - "@aws-sdk/s3-presigned-post": "^3.465.0", + "@aws-sdk/client-s3": "^3.490.0", + "@aws-sdk/s3-request-presigner": "^3.490.0", + "@fastify/multipart": "^8.1.0", "@fastify/static": "^6.11.2", "@nestjs-modules/mailer": "^1.9.1", "@nestjs/axios": "^3.0.1", @@ -51,8 +52,9 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "exceljs": "^4.4.0", - "fast-csv": "^5.0.0", + "fast-csv": "^5.0.1", "joi": "^17.11.0", + "magic-bytes.js": "^1.8.0", "mysql2": "^3.6.2", "nestjs-cls": "^3.6.0", "nestjs-pino": "^3.5.0", diff --git a/apps/api/src/common/enums/field-format.enum.ts b/apps/api/src/common/enums/field-format.enum.ts index 099999ced..5bf0b0cb3 100644 --- a/apps/api/src/common/enums/field-format.enum.ts +++ b/apps/api/src/common/enums/field-format.enum.ts @@ -20,7 +20,7 @@ export enum FieldFormatEnum { select = 'select', multiSelect = 'multiSelect', date = 'date', - image = 'image', + images = 'images', } export function isSelectFieldFormat(type: FieldFormatEnum) { diff --git a/apps/api/src/configs/modules/typeorm-config/migrations/1707979877290-change-field-format-name.ts b/apps/api/src/configs/modules/typeorm-config/migrations/1707979877290-change-field-format-name.ts new file mode 100644 index 000000000..9344f2e47 --- /dev/null +++ b/apps/api/src/configs/modules/typeorm-config/migrations/1707979877290-change-field-format-name.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChangeFieldFormatName1707979877290 implements MigrationInterface { + name = 'ChangeFieldFormatName1707979877290'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`fields\` CHANGE \`format\` \`format\` enum ('text', 'keyword', 'number', 'select', 'multiSelect', 'date', 'images') NOT NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`fields\` CHANGE \`format\` \`format\` enum ('text', 'keyword', 'number', 'select', 'multiSelect', 'date', 'image') NOT NULL`, + ); + } +} diff --git a/apps/api/src/domains/admin/channel/channel/channel.controller.ts b/apps/api/src/domains/admin/channel/channel/channel.controller.ts index 012be9f46..e386d10e0 100644 --- a/apps/api/src/domains/admin/channel/channel/channel.controller.ts +++ b/apps/api/src/domains/admin/channel/channel/channel.controller.ts @@ -40,6 +40,7 @@ import { CreateChannelDto } from './dtos'; import { CreateChannelRequestDto, FindChannelsByProjectIdRequestDto, + ImageUploadUrlTestRequestDto, UpdateChannelFieldsRequestDto, UpdateChannelRequestDto, } from './dtos/requests'; @@ -47,6 +48,7 @@ import { CreateChannelResponseDto, FindChannelByIdResponseDto, FindChannelsByProjectIdResponseDto, + ImageUploadUrlTestResponseDto, } from './dtos/responses'; @ApiTags('channel') @@ -105,7 +107,7 @@ export class ChannelController { @ApiParam({ name: 'projectId', type: Number }) @RequirePermission(PermissionEnum.channel_update) - @Put('/channels/:channelId') + @Put('/:channelId') async updateOne( @Param('channelId', ParseIntPipe) channelId: number, @Body() body: UpdateChannelRequestDto, @@ -115,7 +117,7 @@ export class ChannelController { @ApiParam({ name: 'projectId', type: Number }) @RequirePermission(PermissionEnum.channel_field_update) - @Put('/channels/:channelId/fields') + @Put('/:channelId/fields') async updateFields( @Param('channelId', ParseIntPipe) channelId: number, @Body() body: UpdateChannelFieldsRequestDto, @@ -129,4 +131,26 @@ export class ChannelController { async delete(@Param('channelId', ParseIntPipe) channelId: number) { await this.channelService.deleteById(channelId); } + + @ApiParam({ name: 'projectId', type: Number }) + @ApiOkResponse({ type: ImageUploadUrlTestResponseDto }) + @Post('/image-upload-url-test') + async getImageUploadUrlTest( + @Body() + { + accessKeyId, + secretAccessKey, + endpoint, + region, + }: ImageUploadUrlTestRequestDto, + ) { + return { + success: await this.channelService.isValidImageConfig({ + accessKeyId, + secretAccessKey, + endpoint, + region, + }), + }; + } } diff --git a/apps/api/src/domains/admin/channel/channel/channel.entity.ts b/apps/api/src/domains/admin/channel/channel/channel.entity.ts index 6db63ca5f..c989b7481 100644 --- a/apps/api/src/domains/admin/channel/channel/channel.entity.ts +++ b/apps/api/src/domains/admin/channel/channel/channel.entity.ts @@ -35,6 +35,7 @@ export interface ImageConfig { endpoint: string; region: string; bucket: string; + domainWhiteList: string[]; } @Entity('channels') diff --git a/apps/api/src/domains/admin/channel/channel/channel.mysql.service.ts b/apps/api/src/domains/admin/channel/channel/channel.mysql.service.ts index 5a63bb53c..2d4de4709 100644 --- a/apps/api/src/domains/admin/channel/channel/channel.mysql.service.ts +++ b/apps/api/src/domains/admin/channel/channel/channel.mysql.service.ts @@ -114,13 +114,13 @@ export class ChannelMySQLService { channel.name = name; channel.description = description; channel.imageConfig = imageConfig; - await this.repository.save(channel); + return await this.repository.save(channel); } @Transactional() async delete(channelId: number) { const channel = new ChannelEntity(); channel.id = channelId; - await this.repository.remove(channel); + return await this.repository.remove(channel); } } diff --git a/apps/api/src/domains/admin/channel/channel/channel.service.spec.ts b/apps/api/src/domains/admin/channel/channel/channel.service.spec.ts index 020e31467..9e1c2943d 100644 --- a/apps/api/src/domains/admin/channel/channel/channel.service.spec.ts +++ b/apps/api/src/domains/admin/channel/channel/channel.service.spec.ts @@ -128,18 +128,13 @@ describe('ChannelService', () => { const dto = new UpdateChannelDto(); dto.name = faker.string.sample(); dto.description = faker.string.sample(); - jest - .spyOn(ChannelMySQLService.prototype, 'findById') - .mockResolvedValue(channelFixture); - jest.spyOn(channelRepo, 'findOne').mockResolvedValue(null); + jest.spyOn(channelRepo, 'findOne').mockResolvedValueOnce(channelFixture); + jest.spyOn(channelRepo, 'findOne').mockResolvedValueOnce(null); - await channelService.updateInfo(channelId, dto); + const channel = await channelService.updateInfo(channelId, dto); - expect(channelRepo.save).toBeCalledTimes(1); - expect(channelRepo.save).toHaveBeenCalledWith({ - ...channelFixture, - ...dto, - }); + expect(channel.name).toEqual(dto.name); + expect(channel.description).toEqual(dto.description); }); it('updating fails with a duplicate channel name', async () => { const channelId = channelFixture.id; @@ -166,10 +161,9 @@ describe('ChannelService', () => { const channel = new ChannelEntity(); channel.id = channelId; - await channelService.deleteById(channelId); + const deletedChannel = await channelService.deleteById(channelId); - expect(channelRepo.remove).toBeCalledTimes(1); - expect(channelRepo.remove).toHaveBeenCalledWith(channel); + expect(deletedChannel.id).toEqual(channel.id); }); }); }); diff --git a/apps/api/src/domains/admin/channel/channel/channel.service.ts b/apps/api/src/domains/admin/channel/channel/channel.service.ts index f25d49bc0..1fdb39091 100644 --- a/apps/api/src/domains/admin/channel/channel/channel.service.ts +++ b/apps/api/src/domains/admin/channel/channel/channel.service.ts @@ -13,12 +13,22 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { + ListBucketsCommand, + PutObjectCommand, + S3Client, +} from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Transactional } from 'typeorm-transactional'; import { OpensearchRepository } from '@/common/repositories'; import { ProjectService } from '@/domains/admin/project/project/project.service'; +import type { + CreateImageUploadUrlDto, + ImageUploadUrlTestDto, +} from '../../feedback/dtos'; import { FieldService } from '../field/field.service'; import { ChannelMySQLService } from './channel.mysql.service'; import type { @@ -72,7 +82,7 @@ export class ChannelService { @Transactional() async updateInfo(channelId: number, dto: UpdateChannelDto) { - await this.channelMySQLService.update(channelId, dto); + return await this.channelMySQLService.update(channelId, dto); } @Transactional() @@ -89,6 +99,53 @@ export class ChannelService { await this.osRepository.deleteIndex(channelId.toString()); } - await this.channelMySQLService.delete(channelId); + return await this.channelMySQLService.delete(channelId); + } + + async createImageUploadUrl(dto: CreateImageUploadUrlDto) { + const { + projectId, + channelId, + accessKeyId, + secretAccessKey, + endpoint, + region, + bucket, + extension, + } = dto; + + const s3 = new S3Client({ + credentials: { accessKeyId, secretAccessKey }, + endpoint, + region, + }); + + const command = new PutObjectCommand({ + Bucket: bucket, + Key: `${projectId}_${channelId}_${Date.now()}.${extension}`, + ContentType: 'image/*', + ACL: 'public-read', + }); + + return await getSignedUrl(s3, command, { expiresIn: 60 * 60 }); + } + + async isValidImageConfig(dto: ImageUploadUrlTestDto) { + const { accessKeyId, secretAccessKey, endpoint, region } = dto; + + const s3 = new S3Client({ + credentials: { accessKeyId, secretAccessKey }, + endpoint, + region, + }); + + const command = new ListBucketsCommand({}); + + try { + await s3.send(command); + return true; + } catch { + return false; + } } } diff --git a/apps/api/src/domains/admin/channel/channel/dtos/image-config.dto.ts b/apps/api/src/domains/admin/channel/channel/dtos/image-config.dto.ts index 084d4906b..920cf29fa 100644 --- a/apps/api/src/domains/admin/channel/channel/dtos/image-config.dto.ts +++ b/apps/api/src/domains/admin/channel/channel/dtos/image-config.dto.ts @@ -31,6 +31,9 @@ export class ImageConfigDto { @Expose() bucket: string; + @Expose() + domainWhiteList: string[]; + public static from(params: any): ImageConfigDto { return plainToInstance(ImageConfigDto, params, { excludeExtraneousValues: true, diff --git a/apps/api/src/domains/admin/channel/channel/dtos/requests/image-config-request.dto.ts b/apps/api/src/domains/admin/channel/channel/dtos/requests/image-config-request.dto.ts index 7d138ff4d..10ef298b9 100644 --- a/apps/api/src/domains/admin/channel/channel/dtos/requests/image-config-request.dto.ts +++ b/apps/api/src/domains/admin/channel/channel/dtos/requests/image-config-request.dto.ts @@ -36,4 +36,8 @@ export class ImageConfigRequestDto { @ApiProperty() @IsString() bucket: string; + + @ApiProperty({ nullable: true }) + @IsString({ each: true }) + domainWhiteList: string[]; } diff --git a/apps/api/src/domains/admin/channel/channel/dtos/requests/image-upload-url-test-request.dto.ts b/apps/api/src/domains/admin/channel/channel/dtos/requests/image-upload-url-test-request.dto.ts new file mode 100644 index 000000000..45d5a5d9f --- /dev/null +++ b/apps/api/src/domains/admin/channel/channel/dtos/requests/image-upload-url-test-request.dto.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class ImageUploadUrlTestRequestDto { + @ApiProperty() + @IsString() + accessKeyId: string; + + @ApiProperty() + @IsString() + secretAccessKey: string; + + @ApiProperty() + @IsString() + endpoint: string; + + @ApiProperty() + @IsString() + region: string; + + @ApiProperty() + @IsString() + bucket: string; +} diff --git a/apps/api/src/domains/admin/channel/channel/dtos/requests/index.ts b/apps/api/src/domains/admin/channel/channel/dtos/requests/index.ts index 9df002c9a..55fd71016 100644 --- a/apps/api/src/domains/admin/channel/channel/dtos/requests/index.ts +++ b/apps/api/src/domains/admin/channel/channel/dtos/requests/index.ts @@ -18,3 +18,4 @@ export { FindChannelsByProjectIdRequestDto } from './find-channels-by-project-id export { ImageConfigRequestDto } from './image-config-request.dto'; export { UpdateChannelRequestDto } from './update-channel-request.dto'; export { UpdateChannelFieldsRequestDto } from './update-channel-fields-request.dto'; +export { ImageUploadUrlTestRequestDto } from './image-upload-url-test-request.dto'; diff --git a/apps/api/src/domains/admin/channel/channel/dtos/responses/image-config-response.dto.ts b/apps/api/src/domains/admin/channel/channel/dtos/responses/image-config-response.dto.ts index 94c468186..63e9704fe 100644 --- a/apps/api/src/domains/admin/channel/channel/dtos/responses/image-config-response.dto.ts +++ b/apps/api/src/domains/admin/channel/channel/dtos/responses/image-config-response.dto.ts @@ -36,4 +36,8 @@ export class ImageConfigResponseDto { @Expose() @ApiProperty() bucket: string; + + @Expose() + @ApiProperty() + domainWhiteList: string[]; } diff --git a/apps/api/src/domains/admin/channel/channel/dtos/responses/image-upload-url-test-response.dto.ts b/apps/api/src/domains/admin/channel/channel/dtos/responses/image-upload-url-test-response.dto.ts new file mode 100644 index 000000000..96271be41 --- /dev/null +++ b/apps/api/src/domains/admin/channel/channel/dtos/responses/image-upload-url-test-response.dto.ts @@ -0,0 +1,30 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose, plainToInstance } from 'class-transformer'; + +export class ImageUploadUrlTestResponseDto { + @Expose() + @ApiProperty() + success: boolean; + public static transform( + params: Partial, + ): ImageUploadUrlTestResponseDto { + return plainToInstance(ImageUploadUrlTestResponseDto, params, { + excludeExtraneousValues: true, + }); + } +} diff --git a/apps/api/src/domains/admin/channel/channel/dtos/responses/index.ts b/apps/api/src/domains/admin/channel/channel/dtos/responses/index.ts index db1e2112a..fcca31ef0 100644 --- a/apps/api/src/domains/admin/channel/channel/dtos/responses/index.ts +++ b/apps/api/src/domains/admin/channel/channel/dtos/responses/index.ts @@ -17,3 +17,4 @@ export { CreateChannelResponseDto } from './create-channel-response.dto'; export { FindChannelByIdResponseDto } from './find-channel-by-id-response.dto'; export { FindChannelsByProjectIdResponseDto } from './find-channels-by-id-response.dto'; export { ImageConfigResponseDto } from './image-config-response.dto'; +export { ImageUploadUrlTestResponseDto } from './image-upload-url-test-response.dto'; diff --git a/apps/api/src/domains/admin/channel/field/field.service.ts b/apps/api/src/domains/admin/channel/field/field.service.ts index 828a1f80f..75a4a0265 100644 --- a/apps/api/src/domains/admin/channel/field/field.service.ts +++ b/apps/api/src/domains/admin/channel/field/field.service.ts @@ -30,7 +30,7 @@ export const FIELD_TYPES_TO_MAPPING_TYPES: Record = { select: 'keyword', multiSelect: 'keyword', date: 'date', - image: 'text', + images: 'text', }; @Injectable() @@ -45,7 +45,7 @@ export class FieldService { return fields.reduce( (mapping: Record, field) => Object.assign(mapping, { - [field.key]: [FieldFormatEnum.text, FieldFormatEnum.image].includes( + [field.key]: [FieldFormatEnum.text, FieldFormatEnum.images].includes( field.format, ) ? { diff --git a/apps/api/src/domains/admin/feedback/dtos/create-image-upload-url.dto.ts b/apps/api/src/domains/admin/feedback/dtos/create-image-upload-url.dto.ts index b9e4113b0..6bfdef758 100644 --- a/apps/api/src/domains/admin/feedback/dtos/create-image-upload-url.dto.ts +++ b/apps/api/src/domains/admin/feedback/dtos/create-image-upload-url.dto.ts @@ -22,4 +22,5 @@ export class CreateImageUploadUrlDto { endpoint: string; region: string; bucket: string; + extension: string; } diff --git a/apps/api/src/domains/admin/feedback/dtos/image-upload-url-test.dto.ts b/apps/api/src/domains/admin/feedback/dtos/image-upload-url-test.dto.ts new file mode 100644 index 000000000..10312dfd5 --- /dev/null +++ b/apps/api/src/domains/admin/feedback/dtos/image-upload-url-test.dto.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +export class ImageUploadUrlTestDto { + accessKeyId: string; + secretAccessKey: string; + endpoint: string; + region: string; +} diff --git a/apps/api/src/domains/admin/feedback/dtos/index.ts b/apps/api/src/domains/admin/feedback/dtos/index.ts index 464f8cce7..fa19e1671 100644 --- a/apps/api/src/domains/admin/feedback/dtos/index.ts +++ b/apps/api/src/domains/admin/feedback/dtos/index.ts @@ -29,7 +29,7 @@ export { RemoveIssueDto } from './remove-issue.dto'; export { AddIssueDto } from './add-issue.dto'; export { CountByProjectIdDto } from './count-by-project-id.dto'; export { DeleteByIdsDto } from './delete-by-ids.dto'; - +export { ImageUploadUrlTestDto } from './image-upload-url-test.dto'; export { ScrollFeedbacksDto } from './scroll-feedbacks.dto'; export { GenerateExcelDto } from './generate-excel.dto'; export { OsQueryDto, MustItem } from './os-query.dto'; diff --git a/apps/api/src/domains/admin/feedback/feedback.common.ts b/apps/api/src/domains/admin/feedback/feedback.common.ts index cfaa894ad..2b20916ae 100644 --- a/apps/api/src/domains/admin/feedback/feedback.common.ts +++ b/apps/api/src/domains/admin/feedback/feedback.common.ts @@ -45,8 +45,8 @@ export function validateValue(field: FieldEntity, value: any) { } case FieldFormatEnum.date: return !isNaN(Date.parse(value)) && typeof value !== 'number'; - case FieldFormatEnum.image: - return typeof value === 'string'; + case FieldFormatEnum.images: + return Array.isArray(value); default: throw new Error(`${field.key}: ${field.format} is error ${value}`); } diff --git a/apps/api/src/domains/admin/feedback/feedback.controller.ts b/apps/api/src/domains/admin/feedback/feedback.controller.ts index 8f0544ec2..8f3c6cb09 100644 --- a/apps/api/src/domains/admin/feedback/feedback.controller.ts +++ b/apps/api/src/domains/admin/feedback/feedback.controller.ts @@ -18,7 +18,6 @@ import { Body, Controller, Delete, - Get, Param, ParseIntPipe, Post, @@ -192,27 +191,4 @@ export class FeedbackController { ) { await this.feedbackService.deleteByIds({ channelId, feedbackIds }); } - - @ApiParam({ name: 'projectId', type: Number }) - @UseGuards(ApiKeyAuthGuard) - @Get('/image-upload-url') - async getImageUploadUrl( - @Param('projectId', ParseIntPipe) projectId: number, - @Param('channelId', ParseIntPipe) channelId: number, - ) { - const channel = await this.channelService.findById({ channelId }); - if (channel.project.id !== projectId) { - throw new BadRequestException('Invalid channel id'); - } - - return await this.feedbackService.createImageUploadUrl({ - projectId, - channelId, - accessKeyId: channel.imageConfig.accessKeyId, - secretAccessKey: channel.imageConfig.secretAccessKey, - endpoint: channel.imageConfig.endpoint, - region: channel.imageConfig.region, - bucket: channel.imageConfig.bucket, - }); - } } diff --git a/apps/api/src/domains/admin/feedback/feedback.mysql.service.ts b/apps/api/src/domains/admin/feedback/feedback.mysql.service.ts index c7fd67b2b..c2cb8c5e4 100644 --- a/apps/api/src/domains/admin/feedback/feedback.mysql.service.ts +++ b/apps/api/src/domains/admin/feedback/feedback.mysql.service.ts @@ -186,7 +186,7 @@ export class FeedbackMySQLService { ); } } else if ( - [FieldFormatEnum.text, FieldFormatEnum.image].includes(format) + [FieldFormatEnum.text, FieldFormatEnum.images].includes(format) ) { queryBuilder.andWhere( `JSON_EXTRACT(feedbacks.${dataColumn}, '$."${fieldKey}"') like :value`, diff --git a/apps/api/src/domains/admin/feedback/feedback.os.service.ts b/apps/api/src/domains/admin/feedback/feedback.os.service.ts index f8a710742..50789ffbe 100644 --- a/apps/api/src/domains/admin/feedback/feedback.os.service.ts +++ b/apps/api/src/domains/admin/feedback/feedback.os.service.ts @@ -168,7 +168,7 @@ export class FeedbackOSService { }, }); } else if ( - [FieldFormatEnum.text, FieldFormatEnum.image].includes(format) + [FieldFormatEnum.text, FieldFormatEnum.images].includes(format) ) { osQuery.bool.must.push({ match_phrase: { diff --git a/apps/api/src/domains/admin/feedback/feedback.service.spec.ts b/apps/api/src/domains/admin/feedback/feedback.service.spec.ts index ab1272d90..cdd7a8f48 100644 --- a/apps/api/src/domains/admin/feedback/feedback.service.spec.ts +++ b/apps/api/src/domains/admin/feedback/feedback.service.spec.ts @@ -30,6 +30,7 @@ import { feedbackDataFixture, fieldsFixture, } from '@/test-utils/fixtures'; +import type { ChannelRepositoryStub } from '@/test-utils/stubs'; import { createQueryBuilder, TestConfig } from '@/test-utils/util-functions'; import { FeedbackServiceProviders } from '../../../test-utils/providers/feedback.service.providers'; import { ChannelEntity } from '../channel/channel/channel.entity'; @@ -48,7 +49,7 @@ describe('FeedbackService Test Suite', () => { let clsService: ClsService; let fieldRepo: Repository; let issueRepo: Repository; - let channelRepo: Repository; + let channelRepo: ChannelRepositoryStub; let projectRepo: Repository; let feedbackStatsRepo: Repository; let issueStatsRepo: Repository; @@ -75,6 +76,11 @@ describe('FeedbackService Test Suite', () => { }); describe('create', () => { + beforeEach(() => { + channelRepo.setImageConfig({ + domainWhiteList: ['example.com'], + }); + }); it('creating a feedback succeeds with valid inputs', async () => { const dto = new CreateFeedbackDto(); dto.channelId = faker.number.int(); @@ -148,25 +154,6 @@ describe('FeedbackService Test Suite', () => { new BadRequestException('this field is for admin: ' + adminFieldKey), ); }); - it('creating a feedback fails with an inactive field', async () => { - const dto = new CreateFeedbackDto(); - dto.channelId = faker.number.int(); - dto.data = JSON.parse(JSON.stringify(feedbackDataFixture)); - const inactiveFieldKey = 'inactiveFieldKey'; - dto.data[inactiveFieldKey] = faker.string.sample(); - jest.spyOn(fieldRepo, 'find').mockResolvedValue([ - ...fieldsFixture, - createFieldDto({ - key: inactiveFieldKey, - type: FieldTypeEnum.API, - status: FieldStatusEnum.INACTIVE, - }) as FieldEntity, - ]); - - await expect(feedbackService.create(dto)).rejects.toThrow( - new BadRequestException('this field is inactive: ' + inactiveFieldKey), - ); - }); it('creating a feedback fails with an invalid value for field type', async () => { const formats = [ { @@ -194,8 +181,8 @@ describe('FeedbackService Test Suite', () => { invalidValues: ['not a date', 123, true, {}, []], }, { - format: FieldFormatEnum.image, - invalidValues: [123, true, {}, [], new Date()], + format: FieldFormatEnum.images, + invalidValues: ['not images', 123, true, {}, new Date()], }, ]; for (const { format, invalidValues } of formats) { @@ -245,13 +232,6 @@ describe('FeedbackService Test Suite', () => { id: faker.number.int(), name: issueNames[0], } as IssueEntity); - jest.spyOn(channelRepo, 'findOne').mockResolvedValue({ - id: dto.channelId, - fields: [], - project: { - id: faker.number.int(), - }, - } as ChannelEntity); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValueOnce(null); jest.spyOn(issueRepo, 'save').mockResolvedValue({ id: faker.number.int(), @@ -275,7 +255,6 @@ describe('FeedbackService Test Suite', () => { expect(feedback.id).toBeDefined(); expect(fieldRepo.find).toBeCalledTimes(1); expect(issueRepo.findOneBy).toBeCalledTimes(3); - expect(channelRepo.findOne).toBeCalledTimes(1); expect(issueRepo.save).toBeCalledTimes(1); }); it('creating a feedback succeeds with valid inputs and an existent issue name', async () => { @@ -325,13 +304,6 @@ describe('FeedbackService Test Suite', () => { } as ProjectEntity); jest.spyOn(fieldRepo, 'find').mockResolvedValue(fieldsFixture); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValueOnce(null); - jest.spyOn(channelRepo, 'findOne').mockResolvedValue({ - id: dto.channelId, - fields: [], - project: { - id: faker.number.int(), - }, - } as ChannelEntity); jest.spyOn(issueRepo, 'findOneBy').mockResolvedValueOnce(null); jest.spyOn(issueRepo, 'save').mockResolvedValue({ id: faker.number.int(), @@ -354,7 +326,6 @@ describe('FeedbackService Test Suite', () => { expect(feedback.id).toBeDefined(); expect(issueRepo.findOneBy).toBeCalledTimes(2); - expect(channelRepo.findOne).toBeCalledTimes(1); expect(issueRepo.save).toBeCalledTimes(1); }); }); diff --git a/apps/api/src/domains/admin/feedback/feedback.service.ts b/apps/api/src/domains/admin/feedback/feedback.service.ts index abf894409..d2a794874 100644 --- a/apps/api/src/domains/admin/feedback/feedback.service.ts +++ b/apps/api/src/domains/admin/feedback/feedback.service.ts @@ -17,11 +17,11 @@ import { createReadStream, existsSync } from 'fs'; import * as fs from 'fs/promises'; import path from 'path'; import { PassThrough } from 'stream'; -import { S3Client } from '@aws-sdk/client-s3'; -import { createPresignedPost } from '@aws-sdk/s3-presigned-post'; +import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; import { BadRequestException, Injectable, + InternalServerErrorException, StreamableFile, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @@ -44,7 +44,6 @@ import { OptionService } from '../channel/option/option.service'; import { IssueService } from '../project/issue/issue.service'; import type { CountByProjectIdDto, - CreateImageUploadUrlDto, FindFeedbacksByChannelIdDto, GenerateExcelDto, } from './dtos'; @@ -131,9 +130,11 @@ export class FeedbackService { if (typeof query[fieldKey] !== 'string') throw new BadRequestException(`${fieldKey} must be string`); break; - case FieldFormatEnum.image: - if (typeof query[fieldKey] !== 'string') - throw new BadRequestException(`${fieldKey} must be string`); + case FieldFormatEnum.images: + if (!Array.isArray(query[fieldKey])) + throw new BadRequestException( + `${fieldKey} must be array of string`, + ); break; } } @@ -343,10 +344,6 @@ export class FeedbackService { throw new BadRequestException('this field is for admin: ' + fieldKey); } - if (field.status === FieldStatusEnum.INACTIVE) { - throw new BadRequestException('this field is inactive: ' + fieldKey); - } - if (!validateValue(field, value)) { throw new BadRequestException( `invalid value: (value: ${JSON.stringify(value)}, type: ${ @@ -354,6 +351,23 @@ export class FeedbackService { }, fieldKey: ${field.key})`, ); } + + if (field.format === FieldFormatEnum.images) { + const channel = await this.channelService.findById({ channelId }); + const domainWhiteList = channel.imageConfig.domainWhiteList; + + if (domainWhiteList) { + const images = value as string[]; + for (const image of images) { + const url = new URL(image); + if (!domainWhiteList.includes(url.hostname)) { + throw new BadRequestException( + `invalid domain in image link: ${url.hostname} (fieldKey: ${field.key})`, + ); + } + } + } + } } const feedback = await this.feedbackMySQLService.create({ @@ -580,33 +594,6 @@ export class FeedbackService { } } - async createImageUploadUrl(dto: CreateImageUploadUrlDto) { - const { - projectId, - channelId, - accessKeyId, - secretAccessKey, - endpoint, - region, - bucket, - } = dto; - - const s3 = new S3Client({ - credentials: { - accessKeyId, - secretAccessKey, - }, - endpoint, - region, - }); - - return await createPresignedPost(s3, { - Bucket: bucket, - Key: `${projectId}_${channelId}_${Date.now()}.png`, - Conditions: [{ 'Content-Type': 'image/png' }], - }); - } - async findById({ channelId, feedbackId, @@ -629,4 +616,63 @@ export class FeedbackService { return await this.feedbackMySQLService.findById({ feedbackId }); } } + + async uploadImages({ + channelId, + files, + }: { + channelId: number; + files: Array; + }) { + const channel = await this.channelService.findById({ channelId }); + if (!channel) { + throw new BadRequestException('invalid channel id'); + } + + const s3 = new S3Client({ + credentials: { + accessKeyId: channel.imageConfig.accessKeyId, + secretAccessKey: channel.imageConfig.secretAccessKey, + }, + endpoint: channel.imageConfig.endpoint, + region: channel.imageConfig.region, + }); + try { + const imageUrls = await Promise.all( + files.map(async (file) => { + const key = `${channelId}_${Date.now()}_${file.originalname}`; + const command = new PutObjectCommand({ + Bucket: channel.imageConfig.bucket, + Key: key, + Body: file.buffer, + ContentType: file.mimetype, + ACL: 'public-read', + }); + await s3.send(command); + + return { + ...file, + url: `${channel.imageConfig.endpoint}/${channel.imageConfig.bucket}/${key}`, + }; + }), + ); + const imageUrlsByKeys = imageUrls.reduce((prev, curr) => { + if (curr.fieldname in prev) { + return { + ...prev, + [curr.fieldname]: prev[curr.fieldname].concat(curr.url), + }; + } else { + return { + ...prev, + [curr.fieldname]: [curr.url], + }; + } + }, {}); + + return imageUrlsByKeys; + } catch (e) { + throw new InternalServerErrorException('failed to upload images'); + } + } } diff --git a/apps/api/src/domains/admin/project/issue/dtos/responses/find-issue-by-id-response.dto.ts b/apps/api/src/domains/admin/project/issue/dtos/responses/find-issue-by-id-response.dto.ts index 186087164..42831cc05 100644 --- a/apps/api/src/domains/admin/project/issue/dtos/responses/find-issue-by-id-response.dto.ts +++ b/apps/api/src/domains/admin/project/issue/dtos/responses/find-issue-by-id-response.dto.ts @@ -36,9 +36,9 @@ export class FindIssueByIdResponseDto { @Expose() @ApiProperty({ + enum: IssueStatusEnum, description: 'Issue status', example: IssueStatusEnum.IN_PROGRESS, - enum: IssueStatusEnum, }) status: IssueStatusEnum; diff --git a/apps/api/src/domains/admin/project/role/permission.enum.ts b/apps/api/src/domains/admin/project/role/permission.enum.ts index be79e2e6d..58542bac5 100644 --- a/apps/api/src/domains/admin/project/role/permission.enum.ts +++ b/apps/api/src/domains/admin/project/role/permission.enum.ts @@ -51,6 +51,8 @@ export enum PermissionEnum { channel_delete = 'channel_delete', channel_field_read = 'channel_field_read', channel_field_update = 'channel_field_update', + channel_image_read = 'channel_image_read', + channel_image_update = 'channel_image_update', } export const AllPermissions = Object.values(PermissionEnum); diff --git a/apps/api/src/domains/api/api.module.ts b/apps/api/src/domains/api/api.module.ts index 09aca24fb..4b0cf4349 100644 --- a/apps/api/src/domains/api/api.module.ts +++ b/apps/api/src/domains/api/api.module.ts @@ -33,6 +33,7 @@ import { IssueModule } from '../admin/project/issue/issue.module'; import { FeedbackIssueStatisticsModule } from '../admin/statistics/feedback-issue/feedback-issue-statistics.module'; import { FeedbackStatisticsModule } from '../admin/statistics/feedback/feedback-statistics.module'; import { APIController } from './api.controller'; +import { ChannelController } from './channel.controller'; import { FeedbackController } from './feedback.controller'; import { IssueController } from './issue.controller'; @@ -58,6 +59,11 @@ import { IssueController } from './issue.controller'; FeedbackOSService, OpensearchRepository, ], - controllers: [FeedbackController, IssueController, APIController], + controllers: [ + FeedbackController, + IssueController, + APIController, + ChannelController, + ], }) export class APIModule {} diff --git a/apps/api/src/domains/api/channel.controller.ts b/apps/api/src/domains/api/channel.controller.ts new file mode 100644 index 000000000..0302f24c8 --- /dev/null +++ b/apps/api/src/domains/api/channel.controller.ts @@ -0,0 +1,80 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { + BadRequestException, + Controller, + Get, + Param, + ParseIntPipe, + Query, + UseGuards, +} from '@nestjs/common'; +import { ApiOkResponse, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { ApiKeyAuthGuard } from '@/domains/admin/auth/guards'; +import { ChannelService } from '../admin/channel/channel/channel.service'; + +@ApiTags('channels') +@Controller('/projects/:projectId/channels/:channelId') +@UseGuards(ApiKeyAuthGuard) +export class ChannelController { + constructor(private readonly channelService: ChannelService) {} + + @ApiParam({ + name: 'projectId', + type: Number, + description: 'Project id', + example: 1, + }) + @ApiParam({ + name: 'channelId', + type: Number, + description: 'Channel id', + example: 1, + }) + @ApiOkResponse({ + type: String, + description: 'Presigned url for image upload', + }) + @Get('/image-upload-url') + async getImageUploadUrl( + @Param('projectId', ParseIntPipe) projectId: number, + @Param('channelId', ParseIntPipe) channelId: number, + @Query('extension') extension: string, + ) { + if (!extension) { + throw new BadRequestException('Extension is required in query parameter'); + } + const channel = await this.channelService.findById({ channelId }); + if (channel.project.id !== projectId) { + throw new BadRequestException('Invalid channel id'); + } + if (!channel.imageConfig) { + throw new BadRequestException('No image config in this channel'); + } + + return await this.channelService.createImageUploadUrl({ + projectId, + channelId, + accessKeyId: channel.imageConfig.accessKeyId, + secretAccessKey: channel.imageConfig.secretAccessKey, + endpoint: channel.imageConfig.endpoint, + region: channel.imageConfig.region, + bucket: channel.imageConfig.bucket, + extension, + }); + } +} diff --git a/apps/api/src/domains/api/feedback.controller.ts b/apps/api/src/domains/api/feedback.controller.ts index 46fa2ac82..d359861ee 100644 --- a/apps/api/src/domains/api/feedback.controller.ts +++ b/apps/api/src/domains/api/feedback.controller.ts @@ -23,9 +23,18 @@ import { ParseIntPipe, Post, Put, + Req, UseGuards, } from '@nestjs/common'; -import { ApiBody, ApiOkResponse, ApiParam, ApiTags } from '@nestjs/swagger'; +import { + ApiBody, + ApiConsumes, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiTags, +} from '@nestjs/swagger'; +import filetypemime from 'magic-bytes.js'; import { ApiKeyAuthGuard } from '@/domains/admin/auth/guards'; import { ChannelService } from '../admin/channel/channel/channel.service'; @@ -40,7 +49,7 @@ import { import { FeedbackService } from '../admin/feedback/feedback.service'; @ApiTags('feedbacks') -@Controller('/projects/:projectId/channels/:channelId/feedbacks') +@Controller('/projects/:projectId/channels/:channelId') @UseGuards(ApiKeyAuthGuard) export class FeedbackController { constructor( @@ -82,7 +91,14 @@ export class FeedbackController { }, }, }) - @Post() + @ApiOperation({ + summary: 'Create Feedback', + description: `Create feedback by json data. If you want to create feedback with issues, you can add 'issueNames' in the request data. + You can put an array of image urls in the 'images format field' in the request data. + Make sure to set image domain whitelist in the channel settings. + `, + }) + @Post('feedbacks') async create( @Param('projectId', ParseIntPipe) projectId: number, @Param('channelId', ParseIntPipe) channelId: number, @@ -94,9 +110,116 @@ export class FeedbackController { } const { id } = await this.feedbackService.create({ data: body, channelId }); + return { id }; } + @ApiParam({ + name: 'projectId', + type: Number, + description: 'Project id', + example: 1, + }) + @ApiParam({ + name: 'channelId', + type: Number, + description: 'Channel id', + example: 1, + }) + @ApiConsumes('multipart/form-data') + @ApiBody({ + type: Object, + description: 'Feedback data by multipart/form-data', + examples: { + 'Create feedback': { + summary: 'Create feedback', + value: { + message: 'feedback message', + issueNames: ['issue name 1', 'issue name 2'], + images: ['image file 1', 'image file 2'], + }, + }, + }, + }) + @ApiOkResponse({ + type: Object, + description: 'Feedback id', + schema: { + example: { + id: 1, + }, + }, + }) + @ApiOperation({ + summary: 'Create Feedback with Image Files', + description: `Create feedback with data and image files by multi-part. If you want to create feedback with issues, you can add 'issueNames' in the request data.`, + }) + @Post('feedbacks-with-images') + async createWithImageFiles( + @Param('projectId', ParseIntPipe) projectId: number, + @Param('channelId', ParseIntPipe) channelId: number, + @Req() request, + ) { + const channel = await this.channelService.findById({ channelId }); + if (channel.project.id !== projectId) { + throw new BadRequestException('Invalid channel id'); + } + + const files = []; + const body = {}; + const parts = await request.parts(); + for await (const part of parts) { + if (part.type === 'file') { + if (part.mimetype.includes('image') === false) { + throw new BadRequestException(`Not Image File (${part.mimetype})`); + } + const buffer = await part.toBuffer(); + const mimes = filetypemime(buffer).map((mime) => mime.mime); + if (mimes.includes(part.mimetype) === false) { + throw new BadRequestException( + `Invalid file (sent type: ${ + part.mimetype + }, detected type: ${JSON.stringify(mimes)})`, + ); + } + files.push({ + fieldname: part.fieldname, + buffer, + mimetype: part.mimetype, + originalname: part.filename, + }); + } else { + body[part.fieldname] = part.value; + } + } + + if (files.length === 0) { + const { id } = await this.feedbackService.create({ + data: body, + channelId, + }); + + return { id }; + } else { + const imageUrlsByKeys = await this.feedbackService.uploadImages({ + channelId, + files, + }); + + const feedbackData = { + ...body, + ...imageUrlsByKeys, + }; + + const { id } = await this.feedbackService.create({ + data: feedbackData, + channelId, + }); + + return { id }; + } + } + @ApiParam({ name: 'projectId', type: Number, @@ -111,7 +234,7 @@ export class FeedbackController { }) @ApiBody({ type: FindFeedbacksByChannelIdRequestDto }) @ApiOkResponse({ type: FindFeedbacksByChannelIdResponseDto }) - @Post('search') + @Post('feedbacks/search') async findByChannelId( @Param('channelId', ParseIntPipe) channelId: number, @Body() body: FindFeedbacksByChannelIdRequestDto, @@ -146,7 +269,7 @@ export class FeedbackController { example: 1, }) @ApiOkResponse({ type: AddIssueResponseDto }) - @Post(':feedbackId/issue/:issueId') + @Post('feedbacks/:feedbackId/issue/:issueId') async addIssue( @Param('channelId', ParseIntPipe) channelId: number, @Param('feedbackId', ParseIntPipe) feedbackId: number, @@ -184,7 +307,7 @@ export class FeedbackController { example: 1, }) @ApiOkResponse({ type: AddIssueResponseDto }) - @Delete(':feedbackId/issue/:issueId') + @Delete('feedbacks/:feedbackId/issue/:issueId') async removeIssue( @Param('channelId', ParseIntPipe) channelId: number, @Param('feedbackId', ParseIntPipe) feedbackId: number, @@ -228,7 +351,7 @@ export class FeedbackController { }, }, }) - @Put(':feedbackId') + @Put('feedbacks/:feedbackId') async updateFeedback( @Param('channelId', ParseIntPipe) channelId: number, @Param('feedbackId', ParseIntPipe) feedbackId: number, @@ -254,7 +377,7 @@ export class FeedbackController { example: 1, }) @ApiBody({ type: DeleteFeedbacksRequestDto }) - @Delete() + @Delete('feedbacks') async deleteMany( @Param('channelId', ParseIntPipe) channelId: number, @Body() { feedbackIds }: DeleteFeedbacksRequestDto, @@ -281,7 +404,7 @@ export class FeedbackController { example: 1, }) @ApiOkResponse({ type: Object, description: 'Feedback data' }) - @Get(':feedbackId') + @Get('feedbacks/:feedbackId') async findFeedback( @Param('channelId', ParseIntPipe) channelId: number, @Param('feedbackId', ParseIntPipe) feedbackId: number, diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 370ba2df7..b7fb0eb70 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ +import multiPart from '@fastify/multipart'; import { Logger as DefaultLogger, ValidationPipe } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; @@ -39,6 +40,8 @@ async function bootstrap() { { bufferLogs: true }, ); + await app.register(multiPart); + app.enableCors({ origin: '*', exposedHeaders: ['Content-Disposition'] }); app.setGlobalPrefix(globalPrefix, { diff --git a/apps/api/src/test-utils/fixtures.ts b/apps/api/src/test-utils/fixtures.ts index 4a69c3056..8f2ed63e2 100644 --- a/apps/api/src/test-utils/fixtures.ts +++ b/apps/api/src/test-utils/fixtures.ts @@ -110,8 +110,8 @@ export const getRandomValue = ( .map((option) => option.key); case FieldFormatEnum.date: return faker.date.anytime().toISOString(); - case FieldFormatEnum.image: - return faker.string.sample(); + case FieldFormatEnum.images: + return ['https://example.com/' + faker.string.sample()]; default: throw new Error('Invalid field type '); } @@ -218,3 +218,20 @@ export const tenantFixture = { createdAt: faker.date.past(), updatedAt: faker.date.past(), } as TenantEntity; + +export const channelFixture = { + id: faker.number.int(), + name: faker.string.sample(), + description: faker.lorem.lines(2), + imageConfig: null, + createdAt: faker.date.past(), + updatedAt: faker.date.past(), + project: { + id: faker.number.int(), + name: faker.string.sample(), + description: faker.lorem.lines(2), + createdAt: faker.date.past(), + updatedAt: faker.date.past(), + }, + fields: fieldsFixture, +} as ChannelEntity; diff --git a/apps/api/src/test-utils/providers/channel.service.providers.ts b/apps/api/src/test-utils/providers/channel.service.providers.ts index fd0428603..bb9187a78 100644 --- a/apps/api/src/test-utils/providers/channel.service.providers.ts +++ b/apps/api/src/test-utils/providers/channel.service.providers.ts @@ -21,11 +21,11 @@ import { ProjectServiceProviders } from '@/test-utils/providers/project.service. import { getMockProvider, MockOpensearchRepository, - mockRepository, } from '@/test-utils/util-functions'; import { ChannelEntity } from '../../domains/admin/channel/channel/channel.entity'; import { ChannelMySQLService } from '../../domains/admin/channel/channel/channel.mysql.service'; import { ChannelService } from '../../domains/admin/channel/channel/channel.service'; +import { ChannelRepositoryStub } from '../stubs'; import { FieldServiceProviders } from './field.service.providers'; export const ChannelServiceProviders = [ @@ -33,7 +33,7 @@ export const ChannelServiceProviders = [ ChannelMySQLService, { provide: getRepositoryToken(ChannelEntity), - useValue: mockRepository(), + useClass: ChannelRepositoryStub, }, getMockProvider(OpensearchRepository, MockOpensearchRepository), ...ProjectServiceProviders, diff --git a/apps/api/src/test-utils/providers/feedback-statistics.service.providers.ts b/apps/api/src/test-utils/providers/feedback-statistics.service.providers.ts index decf97cb3..a8282106f 100644 --- a/apps/api/src/test-utils/providers/feedback-statistics.service.providers.ts +++ b/apps/api/src/test-utils/providers/feedback-statistics.service.providers.ts @@ -23,7 +23,7 @@ import { ProjectEntity } from '@/domains/admin/project/project/project.entity'; import { FeedbackStatisticsEntity } from '@/domains/admin/statistics/feedback/feedback-statistics.entity'; import { FeedbackStatisticsService } from '@/domains/admin/statistics/feedback/feedback-statistics.service'; import { mockRepository } from '@/test-utils/util-functions'; -import { FeedbackRepositoryStub } from '../stubs'; +import { ChannelRepositoryStub, FeedbackRepositoryStub } from '../stubs'; export const FeedbackStatisticsServiceProviders = [ FeedbackStatisticsService, @@ -41,7 +41,7 @@ export const FeedbackStatisticsServiceProviders = [ }, { provide: getRepositoryToken(ChannelEntity), - useValue: mockRepository(), + useClass: ChannelRepositoryStub, }, { provide: getRepositoryToken(ProjectEntity), diff --git a/apps/api/src/test-utils/providers/project.service.providers.ts b/apps/api/src/test-utils/providers/project.service.providers.ts index 27ac1f614..c8a139e43 100644 --- a/apps/api/src/test-utils/providers/project.service.providers.ts +++ b/apps/api/src/test-utils/providers/project.service.providers.ts @@ -25,6 +25,7 @@ import { } from '@/test-utils/util-functions'; import { ProjectEntity } from '../../domains/admin/project/project/project.entity'; import { ProjectService } from '../../domains/admin/project/project/project.service'; +import { ChannelRepositoryStub } from '../stubs'; import { ApiKeyServiceProviders } from './api-key.service.providers'; import { FeedbackIssueStatisticsServiceProviders } from './feedback-issue-statistics.service.providers'; import { FeedbackStatisticsServiceProviders } from './feedback-statistics.service.providers'; @@ -41,7 +42,7 @@ export const ProjectServiceProviders = [ }, { provide: getRepositoryToken(ChannelEntity), - useValue: mockRepository(), + useClass: ChannelRepositoryStub, }, getMockProvider(OpensearchRepository, MockOpensearchRepository), ...TenantServiceProviders, diff --git a/apps/api/src/test-utils/stubs/channel-repository.stub.ts b/apps/api/src/test-utils/stubs/channel-repository.stub.ts new file mode 100644 index 000000000..a366ffe45 --- /dev/null +++ b/apps/api/src/test-utils/stubs/channel-repository.stub.ts @@ -0,0 +1,69 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { channelFixture } from '../fixtures'; +import { createQueryBuilder } from '../util-functions'; + +export class ChannelRepositoryStub { + channel = channelFixture; + findOne() { + return this.channel; + } + + findOneBy() { + return this.channel; + } + + find() { + return [this.channel]; + } + + findBy() { + return [this.channel]; + } + + findAndCount() { + return [[this.channel], 1]; + } + + findAndCountBy() { + return [[this.channel], 1]; + } + + save(channel) { + return { ...channel, id: channelFixture.id }; + } + + count() { + return 1; + } + + remove({ id }) { + return { id }; + } + + setImageConfig(config) { + this.channel.imageConfig = config; + } + + setNull() { + this.channel = null; + } + + createQueryBuilder() { + createQueryBuilder.getMany = () => [channelFixture]; + return createQueryBuilder; + } +} diff --git a/apps/api/src/test-utils/stubs/index.ts b/apps/api/src/test-utils/stubs/index.ts index fc1fb0b0f..3e04e77d0 100644 --- a/apps/api/src/test-utils/stubs/index.ts +++ b/apps/api/src/test-utils/stubs/index.ts @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ +export { ChannelRepositoryStub } from './channel-repository.stub'; export { FeedbackRepositoryStub } from './feedback-repository.stub'; export { TenantRepositoryStub } from './tenant-repository.stub'; export { UserRepositoryStub } from './user-repository.stub'; diff --git a/apps/e2e/scenarios/with-database-seed/create-channel.spec.ts b/apps/e2e/scenarios/with-database-seed/create-channel.spec.ts index 1f74e69e5..0d3c05a48 100644 --- a/apps/e2e/scenarios/with-database-seed/create-channel.spec.ts +++ b/apps/e2e/scenarios/with-database-seed/create-channel.spec.ts @@ -18,6 +18,7 @@ export default () => { .fill('Channel for test'); await page.getByRole('button', { name: 'Next' }).click(); await page.getByRole('button', { name: 'Next' }).click(); + await page.getByRole('button', { name: 'Next' }).click(); await page.getByRole('button', { name: 'Complete' }).click(); await expect(page.locator('#Channel\\ Name')).toHaveValue('TestChannel'); await expect(page.locator('#Channel\\ Description')).toHaveValue( diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 1ea4eb881..31c471373 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -17,6 +17,7 @@ const nextConfig = { eslint: { ignoreDuringBuilds: true }, transpilePackages: ['@ufb/ui'], compiler: { removeConsole: process.env.NODE_ENV === 'production' }, + images: { remotePatterns: [{ hostname: '*' }] }, webpack(config) { const fileLoaderRule = config.module.rules.find((rule) => rule.test?.test?.('.svg'), diff --git a/apps/web/package.json b/apps/web/package.json index f4aa541a2..5556991c9 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -32,7 +32,7 @@ "@floating-ui/react": "^0.26.1", "@headlessui/react": "1.7.17", "@headlessui/tailwindcss": "^0.2.0", - "@hookform/resolvers": "^3.3.2", + "@hookform/resolvers": "^3.3.4", "@mui/base": "^5.0.0-beta.23", "@t3-oss/env-nextjs": "^0.7.1", "@tanstack/react-query": "^5.0.0", @@ -47,10 +47,10 @@ "date-fns": "^3.0.6", "dayjs": "^1.11.10", "framer-motion": "^10.16.4", - "i18next": "^23.7.18", + "i18next": "^23.9.0", "immer": "^10.0.3", "iron-session": "^6.3.1", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", "next": "^14.0.3", "next-i18next": "^14.0.3", "pino": "^8.16.0", diff --git a/apps/web/public/assets/images/image-setting-help.png b/apps/web/public/assets/images/image-setting-help.png new file mode 100644 index 000000000..d2d0fd4b4 Binary files /dev/null and b/apps/web/public/assets/images/image-setting-help.png differ diff --git a/apps/web/public/assets/images/sample_image.png b/apps/web/public/assets/images/sample_image.png new file mode 100644 index 000000000..6cd49675e Binary files /dev/null and b/apps/web/public/assets/images/sample_image.png differ diff --git a/apps/web/public/assets/images/sample_image1.png b/apps/web/public/assets/images/sample_image1.png new file mode 100644 index 000000000..7cc8ea2ce Binary files /dev/null and b/apps/web/public/assets/images/sample_image1.png differ diff --git a/apps/web/public/locales/en/common.json b/apps/web/public/locales/en/common.json index 3c5fced9d..f72667f18 100644 --- a/apps/web/public/locales/en/common.json +++ b/apps/web/public/locales/en/common.json @@ -115,20 +115,6 @@ }, "setting": { "title": "Setting", - "subtitle": { - "tenant-info": "Tenant Information", - "sign-up-mgmt": "Login & Signup Management", - "user-mgmt": "User Management", - "project-info": "Project Information", - "member-mgmt": "Member Management", - "role-mgmt": "Role Management", - "api-key-mgmt": "API Key Management", - "issue-tracker-mgmt": "Issue Tracker Management", - "delete-project": "Delete Project", - "channel-info": "Channel Information", - "field-mgmt": "Field Management", - "delete-channel": "Delete Channel" - }, "button": { "create-project": "Create Project", "create-channel": "Create Channel", @@ -161,7 +147,6 @@ "register-member": "Please register a member.", "same-key": "Input the same value as the key", "feedback-request-code": "Feedback Request Code", - "issue-tracker-mgmt.description": "UserFeedback issues and Issue Tracking System tickets can be linked and managed. \nPlease enter the Issue Tracking System URL you are using.", "option-info": "Option Information", "field-mgmt": { "description": "To save, click Preview first.", @@ -216,13 +201,6 @@ "create-project": { "title": "Create Project", "complete-title": "Project creation is complete.", - "help": { - "projectInfo": "You can collect feedback on a project-by-project basis. Please register your Project information based on the product you want to manage your feedback on. (ex. Product Name)", - "roles": "You can set permissions to access and use the Project.", - "members": "You can register or manage members to participate in the Project.", - "apiKeys": "Manage API Key information in the feedback collection API. If you collect feedback using API, please generate Key information.", - "issueTracker": "UserFeedback feedback and Issue Tracking System can be linked and managed. Please enter your Issue Tracking System information." - }, "error-member": "User information that does not currently exist.", "continue-channel-creation": "UserFeedback must be created up to the Channel before UserFeedback is available.\nWould you like to continue creating a channel?", "guide": { @@ -234,16 +212,6 @@ "create-channel": { "title": "Create Channel", "complete-title": "Channel creation is complete.", - "help": { - "channel-info": "You can define the feedback fields that you want to collect through the Channel. Please register the Channel information considering the feedback path and personality. (ex. VOC, APP Reivew)", - "fields": "Define the feedback fields that you want to collect with User Feedback according to the registered channel. Try pre-setting the fields you want to collect through the API or the fields you want to register directly from the ADMIN.", - "field-preview": "You can preview what the fields you set up in Field Management will look like. Previews are not real because they are shown as arbitrary data." - }, - "stepper-text": { - "channel-info": "Channel Infomation", - "fields": "Field Management", - "field-preview": "Field Preview" - }, "finish-channel-creation": "Everything is ready.\nStart Using UserFeedback!", "guide": { "invalid-channel": "Invalid Channel information exists." @@ -332,13 +300,6 @@ "add": "Addition Complete", "copy": "Copy Complete" }, - "placeholder": "Please enter {{name}}.", - "hint": { - "required": "Required", - "max-length": "Please enter {{length}} characters or less.", - "name-already-exists": "{{name}} already exists.", - "invalid-domain": "Not in domain format." - }, "dialog": { "continue": { "title": "Do you want to continue?", @@ -349,44 +310,42 @@ } } }, - "card": { - "dashboard": { - "total-feedback": { - "title": "Total number of feedbacks", - "description": "Number of feedback created in a specific period of time. ({{targetDate}})" - }, - "total-issue": { - "title": "Total number of issues", - "description": "Number of issues created in a specific period of time. ({{targetDate}})" - }, - "issue-ratio": { - "title": "Register Issue rate", - "description": "Register Issue rate of feedback created in a specific period of time. ({{targetDate}})" - }, - "today-feedback": { - "title": "Number of today's feedback", - "description": "Number of feedback created today. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" - }, - "yesterday-feedback": { - "title": "Number of yesterday's feedback", - "description": "Number of feedback created yesterday. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" - }, - "n-days-feedback": { - "title": "Number of feedbacks in the last {{n}} days", - "description": "Number of feedback created in the last {{n}} days. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" - }, - "today-issue": { - "title": "Number of today's issue", - "description": "Number of issue created today. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" - }, - "yesterday-issue": { - "title": "Number of yesterday's issue", - "description": "Number of issue created yesterday. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" - }, - "n-days-issue": { - "title": "Number of issue in the last {{n}} days", - "description": "Number of issue created in the last {{n}} days. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" - } + "dashboard-card": { + "total-feedback": { + "title": "Total number of feedbacks", + "description": "Number of feedback created in a specific period of time. ({{targetDate}})" + }, + "total-issue": { + "title": "Total number of issues", + "description": "Number of issues created in a specific period of time. ({{targetDate}})" + }, + "issue-ratio": { + "title": "Register Issue rate", + "description": "Register Issue rate of feedback created in a specific period of time. ({{targetDate}})" + }, + "today-feedback": { + "title": "Number of today's feedback", + "description": "Number of feedback created today. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" + }, + "yesterday-feedback": { + "title": "Number of yesterday's feedback", + "description": "Number of feedback created yesterday. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" + }, + "n-days-feedback": { + "title": "Number of feedbacks in the last {{n}} days", + "description": "Number of feedback created in the last {{n}} days. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" + }, + "today-issue": { + "title": "Number of today's issue", + "description": "Number of issue created today. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" + }, + "yesterday-issue": { + "title": "Number of yesterday's issue", + "description": "Number of issue created yesterday. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" + }, + "n-days-issue": { + "title": "Number of issue in the last {{n}} days", + "description": "Number of issue created in the last {{n}} days. ({{targetDate}})\nThe increase/decrease rate is compared to the following period. ({{compareDate}})" } }, "chart": { @@ -423,5 +382,51 @@ }, "tooltip": { "issue-feedback-count": "Number of feedbacks registered in the issue." - } + }, + "tenant-setting-menu": { + "tenant-info": "Tenant Information", + "sign-up-mgmt": "Login & Signup Management", + "user-mgmt": "User Management" + }, + "project-setting-menu": { + "project-info": "Project Information", + "member-mgmt": "Member Management", + "role-mgmt": "Role Management", + "api-key-mgmt": "API Key Management", + "issue-tracker-mgmt": "Issue Tracker Management", + "delete-project": "Delete Project" + }, + "channel-setting-menu": { + "channel-info": "Channel Information", + "field-mgmt": "Field Management", + "image-mgmt": "Image Management", + "delete-channel": "Delete Channel" + }, + "modal": { + "image-preview": { + "title": "Image Preview" + } + }, + "help-card": { + "project-info": "You can collect feedback on a project-by-project basis. Please register your Project information based on the product you want to manage your feedback on. (ex. Product Name)", + "role": "You can set permissions to access and use the Project.", + "member": "You can register or manage members to participate in the Project.", + "api-key": "Manage API Key information in the feedback collection API. If you collect feedback using API, please generate Key information.", + "issue-tracker": "UserFeedback feedback and Issue Tracking System can be linked and managed. Please enter your Issue Tracking System information.", + "channel-info": "You can define the feedback fields that you want to collect through the Channel. Please register the Channel information considering the feedback path and personality. (ex. VOC, APP Reivew)", + "field": "Define the feedback fields that you want to collect with User Feedback according to the registered channel. Try pre-setting the fields you want to collect through the API or the fields you want to register directly from the ADMIN.", + "image-setting": "When collecting feedback on the Image format, you can work with Image Storage and set up a whitelist for the Image URL domain.\nSee Image Docs.", + "field-preview": "You can preview what the fields you set up in Field Management will look like. Previews are not real because they are shown as arbitrary data." + }, + "hint": { + "required": "Required", + "max-length": "Please enter {{length}} characters or less.", + "name-already-exists": "{{name}} already exists.", + "invalid-domain": "Invalid domain format.", + "image-format": "The format for entering the Image URL." + }, + "title-box": { + "image-storage-integration": "Image Storage Integration" + }, + "placeholder": "Please enter {{name}}." } diff --git a/apps/web/public/locales/ja/common.json b/apps/web/public/locales/ja/common.json index bff611db1..451d06d3f 100644 --- a/apps/web/public/locales/ja/common.json +++ b/apps/web/public/locales/ja/common.json @@ -115,20 +115,6 @@ }, "setting": { "title": "設定", - "subtitle": { - "tenant-info": "Tenant情報", - "sign-up-mgmt": "Login &Signup管理", - "user-mgmt": "User管理", - "project-info": "Project情報", - "member-mgmt": "Member管理", - "role-mgmt": "Role管理", - "api-key-mgmt": "API Key管理", - "issue-tracker-mgmt": "Issue Tracker管理", - "delete-project": "Project削除", - "channel-info": "Channel情報", - "field-mgmt": "Field管理", - "delete-channel": "Channel削除" - }, "button": { "create-project": "Project生成", "create-channel": "Channel生成", @@ -161,7 +147,6 @@ "register-member": "会員を登録してください。", "same-key": "Key と同様に入力", "feedback-request-code": "フィードバック要求コード", - "issue-tracker-mgmt.description": "ユーザーフィードバックイシューとイシュー追跡システムチケットをリンクして管理することができます。 \n使用中のイシュー追跡システムURLを入力してください。", "option-info": "オプション情報", "field-mgmt": { "description": "保存するにはプレビューを先に押してください。", @@ -216,13 +201,6 @@ "create-project": { "title": "Project生成", "complete-title": "Projectの作成が完了しました。", - "help": { - "projectInfo": "Project単位でフィードバックを収集することができます。 フィードバックを管理するプロダクトを基準にProject情報を登録してください。 (ex. Product Name)", - "roles": "Projectにfアクセスして使用する権限を設定できます。", - "members": "Projectに参加する Memberを登録または管理することができます。", - "apiKeys": "フィードバック収集APIのAPI Key情報を管理します。 APIを活用してフィードバックを収集するなら、Key情報を生成してください。", - "issueTracker": "UserFeedbackフィードバックとIssue Tracking Systemを接続して管理することができます。 使用中のIssue Tracking System情報を入力してください。" - }, "error-member": "現在存在しないユーザ情報です。", "continue-channel-creation": "Channelまで生成しないとUser Feedbackを使用できません。\nChannel生成を続けますか?", "guide": { @@ -234,16 +212,6 @@ "create-channel": { "title": "Channel生成", "complete-title": "Channelの作成が完了しました。", - "help": { - "channel-info": "Channelを通じて収集したいフィードバックフィールドを定義できます。 フィードバック経路と性格を考慮してChannel情報を登録してください。(ex. VOC, APP Reivew)", - "fields": "登録したChannelに合わせてUser Feedbackで収集したいフィードバックフィールドを定義します。 APIを通じて収集したいフィールドやADMINで直接登録したいフィールドをあらかじめ設定してみてください。", - "field-preview": "Field管理で設定したフィールドがどのように見えるかを事前に確認することができます。 プレビューは任意のデータで表示されるため、実際とは異なります。" - }, - "stepper-text": { - "channel-info": "Channel Infomation", - "fields": "Field 管理", - "field-preview": "Field プレビュー" - }, "finish-channel-creation": "すべての準備が完了しました。\nUser Feedbackの利用を始めてみてください!", "guide": { "invalid-channel": "無効なChannel情報が存在します。" @@ -332,13 +300,6 @@ "add": "追加完了", "copy": "コピー完了" }, - "placeholder": "{{name}}を入力してください。", - "hint": { - "required": "必須入力対象です。", - "max-length": "{{length}}字以下で入力してください。", - "name-already-exists": "既に存在する{{name}}です。", - "invalid-domain": "ドメイン形式ではありません。" - }, "dialog": { "continue": { "title": "続いて作成しますか?", @@ -349,44 +310,42 @@ } } }, - "card": { - "dashboard": { - "total-feedback": { - "title": "フィードバックの合計数", - "description": "特定の期間中に作成されたフィードバックの数です。 ({{targetDate}})" - }, - "total-issue": { - "title": "全体イシュー数", - "description": "特定の期間中に作成されたイシューの数です。 ({{targetDate}})" - }, - "issue-ratio": { - "title": "イシュー登録比重", - "description": "特定の期間中に作成されたフィードバックのイシュー登録の割合です。 ({{targetDate}})" - }, - "today-feedback": { - "title": "今日のフィードバック数", - "description": "今日作成されたフィードバックの数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" - }, - "yesterday-feedback": { - "title": "昨日のフィードバック数", - "description": "昨日作成されたフィードバックの数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" - }, - "n-days-feedback": { - "title": "過去{{n}}日フィードバック数", - "description": "過去{{n}}日間に生成されたフィードバックの数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" - }, - "today-issue": { - "title": "今日の話題数", - "description": "今日作成されたイシュー数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" - }, - "yesterday-issue": { - "title": "昨日のイシュー数", - "description": "昨日作成されたイシュー数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" - }, - "n-days-issue": { - "title": "過去{{n}}日のイシュー数", - "description": "過去{{n}}日間に生成されたイシュー数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" - } + "dashboard-card": { + "total-feedback": { + "title": "フィードバックの合計数", + "description": "特定の期間中に作成されたフィードバックの数です。 ({{targetDate}})" + }, + "total-issue": { + "title": "全体イシュー数", + "description": "特定の期間中に作成されたイシューの数です。 ({{targetDate}})" + }, + "issue-ratio": { + "title": "イシュー登録比重", + "description": "特定の期間中に作成されたフィードバックのイシュー登録の割合です。 ({{targetDate}})" + }, + "today-feedback": { + "title": "今日のフィードバック数", + "description": "今日作成されたフィードバックの数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" + }, + "yesterday-feedback": { + "title": "昨日のフィードバック数", + "description": "昨日作成されたフィードバックの数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" + }, + "n-days-feedback": { + "title": "過去{{n}}日フィードバック数", + "description": "過去{{n}}日間に生成されたフィードバックの数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" + }, + "today-issue": { + "title": "今日の話題数", + "description": "今日作成されたイシュー数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" + }, + "yesterday-issue": { + "title": "昨日のイシュー数", + "description": "昨日作成されたイシュー数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" + }, + "n-days-issue": { + "title": "過去{{n}}日のイシュー数", + "description": "過去{{n}}日間に生成されたイシュー数です。 ({{targetDate}})\n増減率は次の期間と比較して示します。 ({{compareDate}})" } }, "chart": { @@ -423,5 +382,51 @@ }, "tooltip": { "issue-feedback-count": "該当イシューに登録されたフィードバック数です。" - } + }, + "tenant-setting-menu": { + "tenant-info": "Tenant情報", + "sign-up-mgmt": "Login & Signup管理", + "user-mgmt": "User管理" + }, + "project-setting-menu": { + "project-info": "Project情報", + "member-mgmt": "Member管理", + "role-mgmt": "Role管理", + "api-key-mgmt": "API Key管理", + "issue-tracker-mgmt": "Issue Tracker管理", + "delete-project": "Project削除" + }, + "channel-setting-menu": { + "channel-info": "Channel情報", + "field-mgmt": "Field管理", + "image-mgmt": "Image管理", + "delete-channel": "Channel削除" + }, + "modal": { + "image-preview": { + "title": "Imageプレビュー" + } + }, + "help-card": { + "project-info": "Project単位でフィードバックを収集することができます。 フィードバックを管理するプロダクトを基準にProject情報を登録してください。 (ex. Product Name)", + "role": "Projectにfアクセスして使用する権限を設定できます。", + "member": "Projectに参加する Memberを登録または管理することができます。", + "api-key": "フィードバック収集APIのAPI Key情報を管理します。 APIを活用してフィードバックを収集するなら、Key情報を生成してください。", + "issue-tracker": "UserFeedbackフィードバックとIssue Tracking Systemを接続して管理することができます。\n使用中のIssue Tracking System情報を入力してください。", + "channel-info": "Channelを通じて収集したいフィードバックフィールドを定義できます。 フィードバック経路と性格を考慮してChannel情報を登録してください。(ex. VOC, APP Reivew)", + "field": "登録したChannelに合わせてUser Feedbackで収集したいフィードバックフィールドを定義します。 APIを通じて収集したいフィールドやADMINで直接登録したいフィールドをあらかじめ設定してみてください。", + "image-setting": "Imageフォーマットに対してフィードバックを収集するとき、Image Storageと連動することができ、Image URLドメインに対するホワイトリストを設定することができます。\n詳細については、Image Docsを参照してください。", + "field-preview": "Field管理で設定したフィールドがどのように見えるかを事前に確認することができます。 プレビューは任意のデータで表示されるため、実際とは異なります。" + }, + "hint": { + "required": "必須入力対象です。", + "max-length": "{{length}}字以下で入力してください。", + "name-already-exists": "既に存在する{{name}}です。", + "invalid-domain": "ドメイン形式ではありません。", + "image-format": "Image URLを入力するフォーマットです。" + }, + "title-box": { + "image-storage-integration": "Image Storageの連動" + }, + "placeholder": "{{name}}を入力してください。" } diff --git a/apps/web/public/locales/ko/common.json b/apps/web/public/locales/ko/common.json index 232fbdc3a..f3b8e7411 100644 --- a/apps/web/public/locales/ko/common.json +++ b/apps/web/public/locales/ko/common.json @@ -115,20 +115,6 @@ }, "setting": { "title": "설정", - "subtitle": { - "tenant-info": "Tenant 정보", - "sign-up-mgmt": "Login & Signup 관리", - "user-mgmt": "User 관리", - "project-info": "Project 정보", - "member-mgmt": "Member 관리", - "role-mgmt": "Role 관리", - "api-key-mgmt": "API Key 관리", - "issue-tracker-mgmt": "Issue Tracker 관리", - "delete-project": "Project 삭제", - "channel-info": "Channel 정보", - "field-mgmt": "Field 관리", - "delete-channel": "Channel 삭제" - }, "button": { "create-project": "Project 생성", "create-channel": "Channel 생성", @@ -161,7 +147,6 @@ "register-member": "Member를 등록해주세요.", "same-key": "Key 정보와 동일하게 입력", "feedback-request-code": "피드백 요청 코드", - "issue-tracker-mgmt.description": "UserFeedback Issue와 Issue Tracking System 티켓을 연결하여 관리할 수 있습니다. \n 사용중인 Issue Tracking System URL을 입력해주세요.", "option-info": "옵션 정보", "field-mgmt": { "description": "저장을 하려면 미리보기를 먼저 눌러주세요.", @@ -216,13 +201,6 @@ "create-project": { "title": "Project 생성", "complete-title": "Project 생성이 완료되었습니다.", - "help": { - "projectInfo": "Project 단위로 피드백을 수집할 수 있습니다. 피드백을 관리할 프로덕트 기준으로 Project 정보를 등록해 주세요. (ex. Product Name)", - "roles": "Project에 접근하고 사용할 수 있는 권한을 설정할 수 있습니다.", - "members": "Project에 참여할 Member를 등록하거나 관리할 수 있습니다. ", - "apiKeys": "피드백 수집 API의 API Key 정보를 관리합니다. API를 활용해 피드백을 수집한다면 Key 정보를 생성해 주세요.", - "issueTracker": "UserFeedback 피드백과 Issue Tracking System을 연결해서 관리할 수 있습니다. 사용 중인 Issue Tracking System 정보를 입력해 주세요." - }, "error-member": "현재 존재하지 않는 User 정보 입니다.", "continue-channel-creation": "Channel까지 생성해야 UserFeedback을 사용할 수 있습니다.\nChannel 생성을 이어서 하시겠어요?", "guide": { @@ -234,16 +212,6 @@ "create-channel": { "title": "Channel 생성", "complete-title": "Channel 생성이 완료되었습니다.", - "help": { - "channel-info": "Channel을 통해 수집하고 싶은 피드백 필드를 정의할 수 있습니다. 피드백 경로와 성격을 고려하여 Channel 정보를 등록해주세요. (ex. VOC, APP Reivew)", - "fields": "등록한 Channel에 맞춰 UserFeedback으로 수집하고 싶은 피드백 필드를 정의합니다. API를 통해 수집하고 싶은 필드나 ADMIN에서 직접 등록하고 싶은 필드를 미리 설정해보세요. ", - "field-preview": "Field 관리에서 설정한 필드가 어떻게 보일지 미리 확인할 수 있습니다. 미리보기는 임의의 데이터로 보여주기 때문에 실제와 다릅니다." - }, - "stepper-text": { - "channel-info": "Channel 정보", - "fields": "Field 관리", - "field-preview": "Field 미리보기" - }, "finish-channel-creation": "모든 준비가 완료됐습니다.\nUserFeedback 이용을 시작해보세요!", "guide": { "invalid-channel": "유효하지 않은 Project 정보가 존재합니다." @@ -332,13 +300,6 @@ "add": "추가 완료", "copy": "복사 완료" }, - "placeholder": "{{name}}을 입력해주세요.", - "hint": { - "required": "필수 입력 대상입니다.", - "max-length": "{{length}}자 이하로 입력해주세요.", - "name-already-exists": "이미 존재하는 {{name}}입니다.", - "invalid-domain": "도메인 형식이 아닙니다." - }, "dialog": { "continue": { "title": "이어서 생성하시겠어요?", @@ -349,44 +310,42 @@ } } }, - "card": { - "dashboard": { - "total-feedback": { - "title": "전체 피드백 수", - "description": "특정 기간 동안 생성된 피드백 개수입니다. ({{targetDate}})" - }, - "total-issue": { - "title": "전체 이슈 수", - "description": "특정 기간 동안 생성된 이슈 개수입니다. ({{targetDate}})" - }, - "issue-ratio": { - "title": "이슈 등록 비중", - "description": "특정 기간 동안 생성된 피드백의 이슈 등록 비중입니다. ({{targetDate}}) " - }, - "today-feedback": { - "title": "오늘 피드백 수", - "description": "오늘 생성된 피드백 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" - }, - "yesterday-feedback": { - "title": "어제 피드백 수", - "description": "어제 생성된 피드백 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" - }, - "n-days-feedback": { - "title": "지난 {{n}}일 피드백 수", - "description": "지난 {{n}}일 동안 생성된 피드백 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" - }, - "today-issue": { - "title": "오늘 이슈 수", - "description": "오늘 생성된 이슈 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" - }, - "yesterday-issue": { - "title": "어제 이슈 수", - "description": "어제 생성된 이슈 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" - }, - "n-days-issue": { - "title": "지난 {{n}}일 이슈 수", - "description": "지난 {{n}}일 동안 생성된 이슈 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" - } + "dashboard-card": { + "total-feedback": { + "title": "전체 피드백 수", + "description": "특정 기간 동안 생성된 피드백 개수입니다. ({{targetDate}})" + }, + "total-issue": { + "title": "전체 이슈 수", + "description": "특정 기간 동안 생성된 이슈 개수입니다. ({{targetDate}})" + }, + "issue-ratio": { + "title": "이슈 등록 비중", + "description": "특정 기간 동안 생성된 피드백의 이슈 등록 비중입니다. ({{targetDate}}) " + }, + "today-feedback": { + "title": "오늘 피드백 수", + "description": "오늘 생성된 피드백 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" + }, + "yesterday-feedback": { + "title": "어제 피드백 수", + "description": "어제 생성된 피드백 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" + }, + "n-days-feedback": { + "title": "지난 {{n}}일 피드백 수", + "description": "지난 {{n}}일 동안 생성된 피드백 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" + }, + "today-issue": { + "title": "오늘 이슈 수", + "description": "오늘 생성된 이슈 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" + }, + "yesterday-issue": { + "title": "어제 이슈 수", + "description": "어제 생성된 이슈 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" + }, + "n-days-issue": { + "title": "지난 {{n}}일 이슈 수", + "description": "지난 {{n}}일 동안 생성된 이슈 개수입니다. ({{targetDate}})\n증감률은 다음 기간과 비교해서 나타냅니다. ({{compareDate}})" } }, "chart": { @@ -423,5 +382,51 @@ }, "tooltip": { "issue-feedback-count": "해당 이슈에 등록된 피드백 개수입니다." - } + }, + "tenant-setting-menu": { + "tenant-info": "Tenant 정보", + "sign-up-mgmt": "Login & Signup 관리", + "user-mgmt": "User 관리" + }, + "project-setting-menu": { + "project-info": "Project 정보", + "member-mgmt": "Member 관리", + "role-mgmt": "Role 관리", + "api-key-mgmt": "API Key 관리", + "issue-tracker-mgmt": "Issue Tracker 관리", + "delete-project": "Project 삭제" + }, + "channel-setting-menu": { + "channel-info": "Channel 정보", + "field-mgmt": "Field 관리", + "image-mgmt": "Image 관리", + "delete-channel": "Channel 삭제" + }, + "modal": { + "image-preview": { + "title": "Image 미리보기" + } + }, + "help-card": { + "project-info": "Project 단위로 피드백을 수집할 수 있습니다. 피드백을 관리할 프로덕트 기준으로 Project 정보를 등록해 주세요. (ex. Product Name)", + "role": "Project에 접근하고 사용할 수 있는 권한을 설정할 수 있습니다.", + "member": "Project에 참여할 Member를 등록하거나 관리할 수 있습니다. ", + "api-key": "피드백 수집 API의 API Key 정보를 관리합니다. API를 활용해 피드백을 수집한다면 Key 정보를 생성해 주세요.", + "issue-tracker": "UserFeedback 피드백과 Issue Tracking System을 연결해서 관리할 수 있습니다.\n사용 중인 Issue Tracking System 정보를 입력해 주세요.", + "channel-info": "Channel을 통해 수집하고 싶은 피드백 필드를 정의할 수 있습니다. 피드백 경로와 성격을 고려하여 Channel 정보를 등록해주세요. (ex. VOC, APP Reivew)", + "field": "등록한 Channel에 맞춰 UserFeedback으로 수집하고 싶은 피드백 필드를 정의합니다. API를 통해 수집하고 싶은 필드나 ADMIN에서 직접 등록하고 싶은 필드를 미리 설정해보세요. ", + "image-setting": "Image 포맷에 대해 피드백을 수집할 때 Image Storage와 연동할 수 있고 Image URL 도메인에 대한 화이트리스트를 설정할 수 있습니다.\n자세한 내용은 Image Docs를 참고해 주세요.", + "field-preview": "Field 관리에서 설정한 필드가 어떻게 보일지 미리 확인할 수 있습니다. 미리보기는 임의의 데이터로 보여주기 때문에 실제와 다릅니다." + }, + "hint": { + "required": "필수 입력 대상입니다.", + "max-length": "{{length}}자 이하로 입력해주세요.", + "name-already-exists": "이미 존재하는 {{name}}입니다.", + "image-format": "Image URL을 입력하는 포맷입니다.", + "invalid-domain": "도메인 형식이 아닙니다." + }, + "title-box": { + "image-storage-integration": "Image Storage 연동" + }, + "placeholder": "{{name}}을 입력해주세요." } diff --git a/apps/web/src/components/buttons/ImagePreviewButton/ImagePreviewButton.tsx b/apps/web/src/components/buttons/ImagePreviewButton/ImagePreviewButton.tsx new file mode 100644 index 000000000..e6b616714 --- /dev/null +++ b/apps/web/src/components/buttons/ImagePreviewButton/ImagePreviewButton.tsx @@ -0,0 +1,146 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { useState } from 'react'; +import Image from 'next/image'; +import { useTranslation } from 'react-i18next'; + +import { Icon, Popover, PopoverContent, PopoverTrigger } from '@ufb/ui'; + +import { useHorizontalScroll } from '@/hooks'; + +interface IProps { + urls: string[]; +} + +const ImagePreviewButton: React.FC = (props) => { + const { urls } = props; + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const [currentImageIndex, setCurrentImageIndex] = useState(0); + + const { + containerRef, + scrollLeft, + scrollRight, + showLeftButton, + showRightButton, + } = useHorizontalScroll({ + defaultRightButtonShown: urls.length > 7, + scrollGap: 78, + }); + + if (urls.length === 0) return null; + return ( + + + + + e.stopPropagation()} + > +
+

{t('modal.image-preview.title')}

+ +
+ + preview window.open(urls[currentImageIndex], '_blank')} + style={{ width: 580, height: 400 }} + width={580} + height={400} + /> +
+
+ {showRightButton && ( + + )} + {showLeftButton && ( + + )} +
+
+
+ {urls.map((url, index) => ( +
setCurrentImageIndex(index)} + > + preview + {index === currentImageIndex && ( + <> +
+ + + )} +
+ ))} +
+
+
+ + + ); +}; + +export default ImagePreviewButton; diff --git a/apps/web/src/containers/setting-menu/TicketSetting/index.ts b/apps/web/src/components/buttons/ImagePreviewButton/index.ts similarity index 92% rename from apps/web/src/containers/setting-menu/TicketSetting/index.ts rename to apps/web/src/components/buttons/ImagePreviewButton/index.ts index 6358fafc1..9139090fe 100644 --- a/apps/web/src/containers/setting-menu/TicketSetting/index.ts +++ b/apps/web/src/components/buttons/ImagePreviewButton/index.ts @@ -13,4 +13,4 @@ * License for the specific language governing permissions and limitations * under the License. */ -export { default } from './TicketSetting'; +export { default } from './ImagePreviewButton'; diff --git a/apps/web/src/components/buttons/index.ts b/apps/web/src/components/buttons/index.ts index 16a81da61..2dd3e23e6 100644 --- a/apps/web/src/components/buttons/index.ts +++ b/apps/web/src/components/buttons/index.ts @@ -14,3 +14,4 @@ * under the License. */ export { default as ThemeToggleButton } from './ThemeToggleButton'; +export { default as ImagePreviewButton } from './ImagePreviewButton'; diff --git a/apps/web/src/components/layouts/Header/Header.tsx b/apps/web/src/components/layouts/Header/Header.tsx index 29dee4f96..b9b472e4a 100644 --- a/apps/web/src/components/layouts/Header/Header.tsx +++ b/apps/web/src/components/layouts/Header/Header.tsx @@ -35,4 +35,5 @@ const Header: React.FC = () => { ); }; + export default Header; diff --git a/apps/web/src/components/layouts/Header/index.ts b/apps/web/src/components/layouts/Header/index.ts index ec7c7ed96..8f7e90b0d 100644 --- a/apps/web/src/components/layouts/Header/index.ts +++ b/apps/web/src/components/layouts/Header/index.ts @@ -13,4 +13,4 @@ * License for the specific language governing permissions and limitations * under the License. */ -export { default as Header } from './Header'; +export { default } from './Header'; diff --git a/apps/web/src/components/layouts/SideNav/index.ts b/apps/web/src/components/layouts/SideNav/index.ts index e21c834a4..8534f01ee 100644 --- a/apps/web/src/components/layouts/SideNav/index.ts +++ b/apps/web/src/components/layouts/SideNav/index.ts @@ -13,4 +13,4 @@ * License for the specific language governing permissions and limitations * under the License. */ -export { default as SideNav } from './SideNav'; +export { default } from './SideNav'; diff --git a/apps/web/src/components/layouts/index.ts b/apps/web/src/components/layouts/index.ts index fb70cadc1..59c3129b9 100644 --- a/apps/web/src/components/layouts/index.ts +++ b/apps/web/src/components/layouts/index.ts @@ -13,5 +13,5 @@ * License for the specific language governing permissions and limitations * under the License. */ -export { Header } from './Header'; -export { SideNav } from './SideNav'; +export { default as SideNav } from './SideNav'; +export { default as Header } from './Header'; diff --git a/apps/web/src/components/templates/AuthTemplate/AuthTemplate.tsx b/apps/web/src/components/templates/AuthTemplate/AuthTemplate.tsx index eef470af7..4bcc8cf62 100644 --- a/apps/web/src/components/templates/AuthTemplate/AuthTemplate.tsx +++ b/apps/web/src/components/templates/AuthTemplate/AuthTemplate.tsx @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ + import { Header } from '@/components/layouts'; interface IProps extends React.PropsWithChildren {} diff --git a/apps/web/src/components/templates/CreateProjectChannelTemplate/CreateProjectChannelTemplate.tsx b/apps/web/src/components/templates/CreateProjectChannelTemplate/CreateProjectChannelTemplate.tsx index 1a51c69a3..81bde715a 100644 --- a/apps/web/src/components/templates/CreateProjectChannelTemplate/CreateProjectChannelTemplate.tsx +++ b/apps/web/src/components/templates/CreateProjectChannelTemplate/CreateProjectChannelTemplate.tsx @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Fragment } from 'react'; +import React, { Fragment } from 'react'; import Image from 'next/image'; import { useRouter } from 'next/router'; import { useTranslation } from 'react-i18next'; @@ -24,14 +24,16 @@ import { Path } from '@/constants/path'; interface IProps extends React.PropsWithChildren { type: 'project' | 'channel'; - helpText: Record; + helpText: Record; stepObj: Record; currentStepIndex: number; completeStepIndex: number; currentStep: T; } -function CreateProjectChannelTemplate(props: IProps) { +const CreateProjectChannelTemplate = ( + props: IProps, +): React.ReactNode => { const { type, helpText, @@ -55,7 +57,7 @@ function CreateProjectChannelTemplate(props: IProps) { {children}
); -} +}; const Header: React.FC<{ type: 'project' | 'channel' }> = ({ type }) => { const { t } = useTranslation(); @@ -157,7 +159,7 @@ const Stepper: React.FC = (props) => { ); }; -const Helper: React.FC<{ text: string }> = ({ text }) => { +const Helper: React.FC<{ text: string | React.ReactNode }> = ({ text }) => { const { t } = useTranslation(); return (
diff --git a/apps/web/src/constants/issues.ts b/apps/web/src/constants/issues.ts index 9dec22221..35ce9eb4b 100644 --- a/apps/web/src/constants/issues.ts +++ b/apps/web/src/constants/issues.ts @@ -31,7 +31,7 @@ export const ISSUES: (t: TFunction) => IssuesItemType[] = (t) => [ { key: 'RESOLVED', name: t('text.issue.resolved'), color: 'green' }, { key: 'PENDING', name: t('text.issue.pending'), color: 'purple' }, ]; -export const getStatusColor = (status: string): ColorType => { +export const getStatusColor = (status: IssueStatus): ColorType => { switch (status) { case 'INIT': return 'red'; diff --git a/apps/web/src/containers/buttons/CreateProjectButton/CreateProjectButton.tsx b/apps/web/src/containers/buttons/CreateProjectButton/CreateProjectButton.tsx index bd9513f8a..1b9f1fa11 100644 --- a/apps/web/src/containers/buttons/CreateProjectButton/CreateProjectButton.tsx +++ b/apps/web/src/containers/buttons/CreateProjectButton/CreateProjectButton.tsx @@ -53,10 +53,7 @@ const CreateProjectButton: React.FC = ({ hasProject }) => { return ( <> - 0)} - placement="bottom" - > + 0} placement="bottom"> - - {!hasProject ? ( - t('main.index.no-project') - ) : ( + 0 ? 'red' : 'blue'}> + {step > 0 ? ( <> {t('text.create-project-in-progress')}{' '} ({step + 1}/{PROJECT_STEPS.length}) + ) : ( + t('main.index.no-project') )} diff --git a/apps/web/src/containers/create-channel-complete/ChannelInfoSection.tsx b/apps/web/src/containers/create-channel-complete/ChannelInfoSection.tsx index 0f2fb34d0..55dcab9f4 100644 --- a/apps/web/src/containers/create-channel-complete/ChannelInfoSection.tsx +++ b/apps/web/src/containers/create-channel-complete/ChannelInfoSection.tsx @@ -29,7 +29,7 @@ const ChannelInfoSection: React.FC = ({ description, name }) => { const { t } = useTranslation(); return ( diff --git a/apps/web/src/containers/create-channel-complete/FieldPreviewSection.tsx b/apps/web/src/containers/create-channel-complete/FieldPreviewSection.tsx index ccf3e599c..24e6e08d0 100644 --- a/apps/web/src/containers/create-channel-complete/FieldPreviewSection.tsx +++ b/apps/web/src/containers/create-channel-complete/FieldPreviewSection.tsx @@ -27,7 +27,7 @@ const FieldPreviewSection: React.FC = ({ fields }) => { const { t } = useTranslation(); return ( diff --git a/apps/web/src/containers/create-channel-complete/FieldSection.tsx b/apps/web/src/containers/create-channel-complete/FieldSection.tsx index da7d963e7..7b96155db 100644 --- a/apps/web/src/containers/create-channel-complete/FieldSection.tsx +++ b/apps/web/src/containers/create-channel-complete/FieldSection.tsx @@ -103,7 +103,7 @@ const FieldSection: React.FC = ({ fields }) => { }); return ( - + diff --git a/apps/web/src/containers/create-channel-complete/ImageUploadSection.tsx b/apps/web/src/containers/create-channel-complete/ImageUploadSection.tsx new file mode 100644 index 000000000..829924767 --- /dev/null +++ b/apps/web/src/containers/create-channel-complete/ImageUploadSection.tsx @@ -0,0 +1,64 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { useTranslation } from 'react-i18next'; + +import { Badge, Input } from '@ufb/ui'; + +import { CreateSectionTemplate } from '@/components/templates/CreateSectionTemplate'; +import type { InputImageConfigType } from '@/types/channel.type'; + +interface IProps extends InputImageConfigType {} + +const ImageUploadSection: React.FC = ({ + accessKeyId, + bucket, + endpoint, + region, + secretAccessKey, + domainWhiteList, +}) => { + const { t } = useTranslation(); + + return ( + +
+

+ {t('title-box.image-storage-integration')} +

+ + + + + +
+
+

Image URL Domain Whitelist

+ {domainWhiteList && ( +
+ {domainWhiteList.map((domain, index) => ( + + {domain} + + ))} +
+ )} +
+
+ ); +}; + +export default ImageUploadSection; diff --git a/apps/web/src/containers/create-channel-complete/index.ts b/apps/web/src/containers/create-channel-complete/index.ts index efe794b57..f0cdf9495 100644 --- a/apps/web/src/containers/create-channel-complete/index.ts +++ b/apps/web/src/containers/create-channel-complete/index.ts @@ -16,3 +16,4 @@ export { default as ChannelInfoSection } from './ChannelInfoSection'; export { default as FieldPreviewSection } from './FieldPreviewSection'; export { default as FieldSection } from './FieldSection'; +export { default as ImageUploadSection } from './ImageUploadSection'; diff --git a/apps/web/src/containers/create-channel/InputFieldPreview.tsx b/apps/web/src/containers/create-channel/InputFieldPreview.tsx index 93a1e5f54..a99eb4c4e 100644 --- a/apps/web/src/containers/create-channel/InputFieldPreview.tsx +++ b/apps/web/src/containers/create-channel/InputFieldPreview.tsx @@ -68,6 +68,7 @@ const InputFieldPreview: React.FC = () => { mutate({ ...input.channelInfo, fields: input.fields.filter((v) => v.type !== 'DEFAULT'), + imageConfig: input.imageConfig, }); }; diff --git a/apps/web/src/containers/create-channel/InputImageSetting.tsx b/apps/web/src/containers/create-channel/InputImageSetting.tsx new file mode 100644 index 000000000..66a9d9dba --- /dev/null +++ b/apps/web/src/containers/create-channel/InputImageSetting.tsx @@ -0,0 +1,319 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useRouter } from 'next/router'; +import { useTranslation } from 'react-i18next'; + +import { Badge, Input, toast } from '@ufb/ui'; + +import { useCreateChannel } from '@/contexts/create-channel.context'; +import { useOAIMutation } from '@/hooks'; +import type { InputImageConfigType } from '@/types/channel.type'; +import CreateChannelInputTemplate from './CreateChannelInputTemplate'; + +type InputErrorType = { + accessKeyId?: string; + bucket?: string; + endpoint?: string; + region?: string; + secretAccessKey?: string; + domainWhiteList?: string; +}; + +const defaultInputError = {}; + +interface IProps {} + +const InputImageSetting: React.FC = () => { + const { t } = useTranslation(); + const { input, onChangeInput } = useCreateChannel(); + const router = useRouter(); + + const projectId = useMemo( + () => Number(router.query.projectId), + [router.query.projectId], + ); + + const [inputError, setInputError] = + useState(defaultInputError); + + const setError = useCallback( + (key: T, value: { message: string }) => { + setInputError((prev) => ({ ...prev, [key]: value.message })); + }, + [], + ); + const [isSubmittedWhiteList, setIsSubmittedWhiteList] = + useState(false); + const [isSubmittedConfig, setIsSubmittedConfig] = useState(false); + + const [inputDomain, setInputDomain] = useState(''); + + const { + accessKeyId, + bucket, + endpoint, + region, + secretAccessKey, + domainWhiteList, + } = useMemo( + () => + input.imageConfig ?? { + accessKeyId: '', + bucket: '', + endpoint: '', + region: '', + secretAccessKey: '', + domainWhiteList: null, + }, + [input.imageConfig], + ); + + const resetError = useCallback(() => { + setInputError(defaultInputError); + setIsSubmittedConfig(false); + setIsSubmittedWhiteList(false); + }, [defaultInputError]); + + useEffect(() => { + resetError(); + }, [input.channelInfo]); + + const onChangeProjectInfo = useCallback( + ( + key: T, + value: InputImageConfigType[T], + ) => { + onChangeInput('imageConfig', { + accessKeyId, + bucket, + endpoint, + region, + secretAccessKey, + domainWhiteList, + [key]: value, + }); + }, + [input.channelInfo], + ); + + const { mutate: testConection } = useOAIMutation({ + method: 'post', + path: '/api/admin/projects/{projectId}/channels/image-upload-url-test', + pathParams: { projectId }, + queryOptions: { + onSuccess(data) { + if (data?.success) { + toast.accent({ title: 'Test Connection Success' }); + } else { + toast.negative({ title: 'Test Connection failed' }); + } + }, + onError() { + toast.negative({ title: 'Test Connection failed' }); + }, + }, + }); + + const handleTestConnection = () => { + setIsSubmittedConfig(true); + let isError = false; + if (accessKeyId.length === 0) { + setError('accessKeyId', { message: t('hint.required') }); + isError = true; + } + if (bucket.length === 0) { + setError('bucket', { message: t('hint.required') }); + isError = true; + } + if (endpoint.length === 0) { + setError('endpoint', { message: t('hint.required') }); + isError = true; + } + if (region.length === 0) { + setError('region', { message: t('hint.required') }); + isError = true; + } + if (secretAccessKey.length === 0) { + setError('secretAccessKey', { message: t('hint.required') }); + isError = true; + } + if (isError) return; + testConection(input.imageConfig); + }; + + const addDomainWhiteList = () => { + if (!domainWhiteList) return; + + if (domainWhiteList.includes(inputDomain)) { + setError('domainWhiteList', { + message: t('hint.name-already-exists', { name: 'Domain' }), + }); + return; + } + + if (!/[a-z]+\.[a-z]{2,3}/.test(inputDomain)) { + setError('domainWhiteList', { message: t('hint.invalid-domain') }); + return; + } + + onChangeProjectInfo('domainWhiteList', domainWhiteList.concat(inputDomain)); + setInputError(defaultInputError); + setInputDomain(''); + }; + + const removeDomainWhiteList = (index: number) => { + if (!domainWhiteList) return; + onChangeProjectInfo( + 'domainWhiteList', + domainWhiteList.filter((_, i) => i !== index), + ); + }; + const validate = () => { + setIsSubmittedWhiteList(true); + if (domainWhiteList?.length === 0) { + setError('domainWhiteList', { message: t('hint.required') }); + return false; + } + return true; + }; + + return ( + +
+
+

+ {t('title-box.image-storage-integration')} +

+ +
+ onChangeProjectInfo('accessKeyId', e.target.value)} + isSubmitted={isSubmittedConfig} + isValid={!inputError.accessKeyId} + hint={inputError.accessKeyId} + /> + + onChangeProjectInfo('secretAccessKey', e.target.value) + } + isSubmitted={isSubmittedConfig} + isValid={!inputError.secretAccessKey} + hint={inputError.secretAccessKey} + /> + onChangeProjectInfo('endpoint', e.target.value)} + isSubmitted={isSubmittedConfig} + isValid={!inputError.endpoint} + hint={inputError.endpoint} + /> + onChangeProjectInfo('region', e.target.value)} + isSubmitted={isSubmittedConfig} + isValid={!inputError.region} + hint={inputError.region} + /> + onChangeProjectInfo('bucket', e.target.value)} + isSubmitted={isSubmittedConfig} + isValid={!inputError.bucket} + hint={inputError.bucket} + /> +
+
+
+

Image URL Domain Whitelist

+ { + setIsSubmittedWhiteList(false); + onChangeProjectInfo( + 'domainWhiteList', + e.target.checked ? [] : null, + ); + }} + /> +
+ {domainWhiteList && ( + <> + setInputDomain(e.target.value)} + rightChildren={ + + } + isValid={!inputError.domainWhiteList} + hint={inputError.domainWhiteList} + isSubmitted={isSubmittedWhiteList} + required + /> +
+ {domainWhiteList.map((domain, index) => ( + removeDomainWhiteList(index), + }} + > + {domain} + + ))} +
+ + )} +
+
+ ); +}; + +export default InputImageSetting; diff --git a/apps/web/src/containers/create-channel/index.ts b/apps/web/src/containers/create-channel/index.ts index f4a83007f..05a6472ee 100644 --- a/apps/web/src/containers/create-channel/index.ts +++ b/apps/web/src/containers/create-channel/index.ts @@ -15,3 +15,5 @@ */ export { default as InputField } from './InputField'; export { default as InputFieldPreview } from './InputFieldPreview'; +export { default as InputImageSetting } from './InputImageSetting'; +export { default as InputChannelInfo } from './InputChannelInfo'; diff --git a/apps/web/src/containers/create-project-complete/ApiKeySection.tsx b/apps/web/src/containers/create-project-complete/ApiKeySection.tsx index 8480aa17b..748e9bfb3 100644 --- a/apps/web/src/containers/create-project-complete/ApiKeySection.tsx +++ b/apps/web/src/containers/create-project-complete/ApiKeySection.tsx @@ -99,7 +99,7 @@ const ApiKeySection: React.FC = ({ projectId }) => { }); return ( - +
diff --git a/apps/web/src/containers/create-project-complete/IssueTrackerSection.tsx b/apps/web/src/containers/create-project-complete/IssueTrackerSection.tsx index 43ac11e88..512c0d0c5 100644 --- a/apps/web/src/containers/create-project-complete/IssueTrackerSection.tsx +++ b/apps/web/src/containers/create-project-complete/IssueTrackerSection.tsx @@ -33,9 +33,7 @@ const IssueTrackerSection: React.FC = ({ projectId }) => { variables: { projectId }, }); return ( - + = ({ projectId }) => { /> diff --git a/apps/web/src/containers/create-project-complete/MemberSection.tsx b/apps/web/src/containers/create-project-complete/MemberSection.tsx index 7e2d01d30..6d4b437eb 100644 --- a/apps/web/src/containers/create-project-complete/MemberSection.tsx +++ b/apps/web/src/containers/create-project-complete/MemberSection.tsx @@ -82,7 +82,7 @@ const MemberSection: React.FC = ({ projectId }) => { }); return ( - +
diff --git a/apps/web/src/containers/create-project-complete/ProjectInfoSection.tsx b/apps/web/src/containers/create-project-complete/ProjectInfoSection.tsx index a295698b8..daedfbe95 100644 --- a/apps/web/src/containers/create-project-complete/ProjectInfoSection.tsx +++ b/apps/web/src/containers/create-project-complete/ProjectInfoSection.tsx @@ -36,7 +36,7 @@ const ProjectInfoSection: React.FC = ({ const { t } = useTranslation(); return ( diff --git a/apps/web/src/containers/create-project-complete/RoleSection.tsx b/apps/web/src/containers/create-project-complete/RoleSection.tsx index 00903253d..c1b49187f 100644 --- a/apps/web/src/containers/create-project-complete/RoleSection.tsx +++ b/apps/web/src/containers/create-project-complete/RoleSection.tsx @@ -32,7 +32,7 @@ const RoleSection: React.FC = ({ projectId }) => { }); return ( - + {}} diff --git a/apps/web/src/containers/create-project/InputIssueTracker.tsx b/apps/web/src/containers/create-project/InputIssueTracker.tsx index 0bc800255..58028d245 100644 --- a/apps/web/src/containers/create-project/InputIssueTracker.tsx +++ b/apps/web/src/containers/create-project/InputIssueTracker.tsx @@ -100,14 +100,14 @@ const InputIssueTracker: React.FC = () => { mutate({ name: projectInfo.name, description: projectInfo.description, - apiKeys: apiKeys, - issueTracker: { data: issueTracker as any }, + timezone: projectInfo.timezone, members: members.map((member) => ({ roleName: roles.find((role) => role.id === member.roleId)?.name ?? '', userId: member.user.id, })), - roles: roles, - timezone: projectInfo.timezone, + issueTracker: { data: issueTracker as any }, + apiKeys, + roles, }); }; @@ -126,7 +126,7 @@ const InputIssueTracker: React.FC = () => { /> onChangeIssueTracker('ticketKey', e.target.value)} /> diff --git a/apps/web/src/containers/dashboard/CreateFeedbackPerIssueCard.tsx b/apps/web/src/containers/dashboard/CreateFeedbackPerIssueCard.tsx index 7045677f1..4c731d963 100644 --- a/apps/web/src/containers/dashboard/CreateFeedbackPerIssueCard.tsx +++ b/apps/web/src/containers/dashboard/CreateFeedbackPerIssueCard.tsx @@ -43,8 +43,8 @@ const CreateFeedbackPerIssueCard: React.FC = (props) => { return ( = ({ projectId }) => { return ( = ({ projectId }) => { return ( = ({ projectId }) => { return ( = ({ projectId }) => { return ( = ({ projectId }) => { return ( = ({ projectId }) => { return ( = ({ projectId, from, to }) => { return ( = ({ from, to, projectId }) => { return ( = ({ projectId }) => { return ( = ({ projectId }) => { return ( = () => { path: '/api/admin/users/password/change', queryOptions: { async onSuccess() { - toast.positive({ title: t('toast.save') }); + toast.accent({ title: t('toast.save') }); reset({ confirmNewPassword: '', newPassword: '', password: '' }); }, onError(error) { diff --git a/apps/web/src/containers/setting-menu/APIKeySetting/APIKeySetting.tsx b/apps/web/src/containers/setting-menu/APIKeySetting/APIKeySetting.tsx index 1e96d2f1a..7e88d30d3 100644 --- a/apps/web/src/containers/setting-menu/APIKeySetting/APIKeySetting.tsx +++ b/apps/web/src/containers/setting-menu/APIKeySetting/APIKeySetting.tsx @@ -162,7 +162,7 @@ const APIKeySetting: React.FC = ({ projectId }) => { return ( createApiKey({ value: undefined }), diff --git a/apps/web/src/containers/setting-menu/ChannelDeleteSetting/ChannelDeleteSetting.tsx b/apps/web/src/containers/setting-menu/ChannelDeleteSetting/ChannelDeleteSetting.tsx index 940993d2b..efd2f2b77 100644 --- a/apps/web/src/containers/setting-menu/ChannelDeleteSetting/ChannelDeleteSetting.tsx +++ b/apps/web/src/containers/setting-menu/ChannelDeleteSetting/ChannelDeleteSetting.tsx @@ -73,7 +73,7 @@ const ChannelDeleteSetting: React.FC = (props) => { return ( = ({ projectId, channelId }) => { const { mutate, isPending } = useOAIMutation({ method: 'put', - path: '/api/admin/projects/{projectId}/channels/channels/{channelId}', + path: '/api/admin/projects/{projectId}/channels/{channelId}', pathParams: { channelId, projectId }, queryOptions: { onSuccess: async () => { @@ -87,7 +87,7 @@ const ChannelInfoSetting: React.FC = ({ projectId, channelId }) => { return ( = (props) => {
    + = ({ channelId, projectId }) => { path: '/api/admin/projects/{projectId}/channels/{channelId}', variables: { channelId, projectId }, }); + const { data: apiKeysData } = useOAIQuery({ path: '/api/admin/projects/{projectId}/api-keys', variables: { projectId }, }); + const apiKey = useMemo( () => apiKeysData?.items.find((v) => !v.deletedAt) ?? null, [apiKeysData], @@ -71,6 +73,9 @@ const FeedbackRequestPopover: React.FC = ({ channelId, projectId }) => { case 'multiSelect': body[key] = `[${field.options.map((v) => `'${v.name}'`).join(', ')}]`; break; + case 'images': + body[key] = `URL[]`; + break; default: break; } diff --git a/apps/web/src/containers/setting-menu/FieldSetting/FieldSetting.tsx b/apps/web/src/containers/setting-menu/FieldSetting/FieldSetting.tsx index 54b213b4d..e3fac201f 100644 --- a/apps/web/src/containers/setting-menu/FieldSetting/FieldSetting.tsx +++ b/apps/web/src/containers/setting-menu/FieldSetting/FieldSetting.tsx @@ -169,19 +169,17 @@ const FieldSetting: React.FC = ({ projectId, channelId }) => { const { t } = useTranslation(); const perms = usePermissions(projectId); - const canUpdateField = useMemo( - () => perms.includes('channel_field_update'), - [perms], - ); - const [status, setStatus] = useState<'ACTIVE' | 'INACTIVE'>('ACTIVE'); const [rows, setRows] = useState([]); - const [showPreview, setShowPreview] = useState(false); - const [channelData, setChannelData] = useState(); - const [channelDataLoading, setChannelDataLoading] = useState(true); + + const canUpdateField = useMemo( + () => perms.includes('channel_field_update'), + [perms], + ); + const getChannelData = async () => { setChannelDataLoading(true); @@ -206,6 +204,7 @@ const FieldSetting: React.FC = ({ projectId, channelId }) => { }), ); }; + const deleteField = (index: number) => { setRows((prev) => prev.filter((_, i) => i !== index)); }; @@ -227,7 +226,7 @@ const FieldSetting: React.FC = ({ projectId, channelId }) => { const { mutate, isPending } = useOAIMutation({ method: 'put', - path: '/api/admin/projects/{projectId}/channels/channels/{channelId}/fields', + path: '/api/admin/projects/{projectId}/channels/{channelId}/fields', pathParams: { channelId, projectId }, queryOptions: { onSuccess: async () => { @@ -247,7 +246,7 @@ const FieldSetting: React.FC = ({ projectId, channelId }) => { return ( diff --git a/apps/web/src/containers/setting-menu/FieldSetting/FieldSettingPopover.tsx b/apps/web/src/containers/setting-menu/FieldSetting/FieldSettingPopover.tsx index aec09ed86..4730d3a5c 100644 --- a/apps/web/src/containers/setting-menu/FieldSetting/FieldSettingPopover.tsx +++ b/apps/web/src/containers/setting-menu/FieldSetting/FieldSettingPopover.tsx @@ -254,15 +254,24 @@ const FieldSettingPopover: React.FC = (props) => { {t('main.setting.same-key')} - value?.key && setValue('format', value?.key)} - options={FieldFormatEnumList.map((v) => ({ key: v, name: v }))} - value={{ key: watch('format'), name: watch('format') }} - isDisabled={isOriginalData} - getOptionValue={(option) => option.key} - getOptionLabel={(option) => option.name} - /> +
    + + value?.key && setValue('format', value?.key) + } + options={FieldFormatEnumList.map((v) => ({ key: v, name: v }))} + value={{ key: watch('format'), name: watch('format') }} + isDisabled={isOriginalData} + getOptionValue={(option) => option.key} + getOptionLabel={(option) => option.name} + /> + {watch('format') === 'images' && ( +

    + {t('hint.image-format')} +

    + )} +
    {(watch('format') === 'select' || watch('format') === 'multiSelect') && (
    @@ -289,6 +298,7 @@ const FieldSettingPopover: React.FC = (props) => { isSubmitted={optionSubmitted} hint={formState.errors.options?.message} /> + {(watch('options') ?? []).length > 0 && (
    {watch('options')?.map((v, i) => ( @@ -329,7 +339,7 @@ const FieldSettingPopover: React.FC = (props) => { e.target.checked ? setValue('type', 'ADMIN') : {} } checked={watch('type') === 'ADMIN'} - disabled={isOriginalData} + disabled={isOriginalData || watch('format') === 'images'} /> ADMIN diff --git a/apps/web/src/containers/setting-menu/FieldSetting/PreviewTable.tsx b/apps/web/src/containers/setting-menu/FieldSetting/PreviewTable.tsx index 6e8a2eec9..d70c4a2ae 100644 --- a/apps/web/src/containers/setting-menu/FieldSetting/PreviewTable.tsx +++ b/apps/web/src/containers/setting-menu/FieldSetting/PreviewTable.tsx @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { Fragment, useEffect, useMemo, useState } from 'react'; +import { Fragment, memo, useEffect, useMemo, useState } from 'react'; import { faker } from '@faker-js/faker'; import { createColumnHelper, @@ -26,16 +26,18 @@ import { useTranslation } from 'react-i18next'; import { Badge } from '@ufb/ui'; +import { ImagePreviewButton } from '@/components/buttons'; import { ExpandableText, TableResizer } from '@/components/etc'; import { DATE_TIME_FORMAT } from '@/constants/dayjs-format'; import { getStatusColor, ISSUES } from '@/constants/issues'; import EditableCell from '@/containers/tables/FeedbackTable/EditableCell/EditableCell'; import type { FieldType } from '@/types/field.type'; +import type { IssueStatus } from '@/types/issue.type'; import type { FieldRowType } from './FieldSetting'; const columnHelper = createColumnHelper(); -interface IProps extends React.PropsWithChildren { +interface IProps { fields: FieldRowType[]; } @@ -86,11 +88,19 @@ const PreviewTable: React.FC = ({ fields }) => { ? faker.number.int() : field.format === 'text' ? faker.lorem.text() + : field.format === 'images' + ? faker.helpers.arrayElements( + Array.from( + { length: faker.number.int({ min: 1, max: 15 }) }, + () => '/assets/images/sample_image.png', + ), + ) : null; } } fakeRows.push(fakeData); } + setRows(fakeRows); }, [fields]); @@ -112,17 +122,17 @@ const PreviewTable: React.FC = ({ fields }) => { dayjs(info.getValue() as string).format(DATE_TIME_FORMAT) ) : field.key === 'issues' ? (
    - {(info.getValue() as { status: string; name: string }[])?.map( - (v, i) => ( - - {v.name} - - ), - )} + {( + info.getValue() as { status: IssueStatus; name: string }[] + )?.map((v, i) => ( + + {v.name} + + ))}
    ) : field.format === 'multiSelect' ? ( ((info.getValue() ?? []) as string[]).join(', ') @@ -130,6 +140,8 @@ const PreviewTable: React.FC = ({ fields }) => { {info.getValue() as string} + ) : field.format === 'images' ? ( + ) : ( String(info.getValue()) ), @@ -197,4 +209,7 @@ const PreviewTable: React.FC = ({ fields }) => { ); }; -export default PreviewTable; +export default memo( + PreviewTable, + (prev, next) => JSON.stringify(prev.fields) === JSON.stringify(next.fields), +); diff --git a/apps/web/src/containers/setting-menu/ImageSetting/ImageSetting.tsx b/apps/web/src/containers/setting-menu/ImageSetting/ImageSetting.tsx new file mode 100644 index 000000000..b71ff5252 --- /dev/null +++ b/apps/web/src/containers/setting-menu/ImageSetting/ImageSetting.tsx @@ -0,0 +1,383 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { useEffect, useState } from 'react'; +import Image from 'next/image'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { Trans, useTranslation } from 'react-i18next'; +import { z } from 'zod'; + +import { Badge, Icon, Input, TextInput, toast } from '@ufb/ui'; + +import { SettingMenuTemplate } from '@/components'; +import { useOAIMutation, useOAIQuery, usePermissions } from '@/hooks'; + +interface IForm { + accessKeyId: string; + secretAccessKey: string; + endpoint: string; + region: string; + bucket: string; + domainWhiteList: string[] | null; +} + +const schema: Zod.ZodType = z.object({ + accessKeyId: z.string(), + secretAccessKey: z.string(), + endpoint: z.string(), + region: z.string(), + bucket: z.string(), + domainWhiteList: z.array(z.string()).or(z.null()), +}); + +type InputDomainState = { + isSubmitted: boolean; + isValid: boolean; + hint?: string; +}; + +interface IProps { + channelId: number; + projectId: number; +} + +const ImageSetting: React.FC = ({ channelId, projectId }) => { + const { t } = useTranslation(); + const perms = usePermissions(projectId); + const [inputDomain, setInputDomain] = useState(''); + const [inputDomainState, setInputDomainState] = useState({ + isSubmitted: false, + isValid: true, + }); + + const { + register, + formState, + handleSubmit, + reset, + watch, + setValue, + setError, + } = useForm({ resolver: zodResolver(schema) }); + + const domainWhiteList = watch('domainWhiteList'); + + const { data } = useOAIQuery({ + path: '/api/admin/projects/{projectId}/channels/{channelId}', + variables: { channelId, projectId }, + }); + + const { mutate } = useOAIMutation({ + method: 'put', + path: '/api/admin/projects/{projectId}/channels/{channelId}', + pathParams: { channelId, projectId }, + queryOptions: { + onSuccess() { + toast.positive({ title: t('toast.save') }); + }, + onError(error) { + toast.negative({ title: error?.message ?? 'Error' }); + }, + }, + }); + + const { mutate: testConection } = useOAIMutation({ + method: 'post', + path: '/api/admin/projects/{projectId}/channels/image-upload-url-test', + pathParams: { projectId }, + queryOptions: { + onSuccess(data) { + if (data?.success) { + toast.accent({ title: 'Test Connection Success' }); + } else { + toast.negative({ title: 'Test Connection failed' }); + } + }, + onError() { + toast.negative({ title: 'Test Connection failed' }); + }, + }, + }); + + useEffect(() => { + if (!data) return; + reset({ + ...data.imageConfig, + domainWhiteList: data.imageConfig?.domainWhiteList ?? null, + }); + }, [data]); + + const onSubmit = (input: IForm) => { + if (!data) return; + if (input.domainWhiteList?.length === 0) { + setInputDomainState({ + isSubmitted: true, + isValid: false, + hint: t('hint.required'), + }); + } + mutate({ ...data, imageConfig: input }); + }; + + const handleTestConnection = handleSubmit( + ({ accessKeyId, bucket, endpoint, region, secretAccessKey }) => { + let isError = false; + if (accessKeyId.length === 0) { + setError('accessKeyId', { message: t('hint.required') }); + isError = true; + } + if (bucket.length === 0) { + setError('bucket', { message: t('hint.required') }); + isError = true; + } + if (endpoint.length === 0) { + setError('endpoint', { message: t('hint.required') }); + isError = true; + } + if (region.length === 0) { + setError('region', { message: t('hint.required') }); + isError = true; + } + if (secretAccessKey.length === 0) { + setError('secretAccessKey', { message: t('hint.required') }); + isError = true; + } + if (isError) return; + testConection({ accessKeyId, bucket, endpoint, region, secretAccessKey }); + }, + ); + + const addDomainWhiteList = () => { + if (!domainWhiteList) return; + + if (domainWhiteList.includes(inputDomain)) { + setInputDomainState({ + isSubmitted: true, + isValid: false, + hint: t('hint.name-already-exists', { name: 'Domain' }), + }); + return; + } + + if (!/[a-z]+\.[a-z]{2,3}/.test(inputDomain)) { + setInputDomainState({ + isSubmitted: true, + isValid: false, + hint: t('hint.invalid-domain'), + }); + return; + } + + setValue('domainWhiteList', domainWhiteList.concat(inputDomain), { + shouldDirty: true, + }); + + setInputDomainState({ isSubmitted: false, isValid: true }); + setInputDomain(''); + }; + + const removeDomainWhiteList = (index: number) => { + if (!domainWhiteList) return; + setValue( + 'domainWhiteList', + domainWhiteList.filter((_, i) => i !== index), + { shouldDirty: true }, + ); + }; + + return ( + +
    +

    + { + if (typeof window === 'undefined') return; + window.open( + 'https://github.com/line/abc-user-feedback/blob/main/GUIDE.md#image-storage-integration', + '_blank', + ); + }} + /> + ), + docs: ( + { + if (typeof window === 'undefined') return; + window.open( + 'https://github.com/line/abc-user-feedback/blob/main/GUIDE.md#image-storage-integration', + '_blank', + ); + }} + /> + ), + }} + /> +

    +
    + +
    +
    +
    +
    +
    +

    + {t('title-box.image-storage-integration')} +

    + +
    + + + + + +
    +
    +
    +

    Image URL Domain Whitelist

    + + setValue('domainWhiteList', e.target.checked ? [] : null, { + shouldDirty: true, + }) + } + /> +
    + {domainWhiteList && ( + <> + setInputDomain(e.target.value)} + rightChildren={ + + } + isValid={!inputDomainState} + hint={inputDomainState.hint} + isSubmitted={inputDomainState.isSubmitted} + required + /> +
    + {domainWhiteList.map((domain, index) => ( + removeDomainWhiteList(index), + disabled: !perms.includes('channel_image_update'), + }} + > + {domain} + + ))} +
    + + )} +
    + +
    + ); +}; + +export default ImageSetting; diff --git a/apps/api/src/utils/export-schema.ts b/apps/web/src/containers/setting-menu/ImageSetting/index.ts similarity index 93% rename from apps/api/src/utils/export-schema.ts rename to apps/web/src/containers/setting-menu/ImageSetting/index.ts index 893adb6ae..09c5cb440 100644 --- a/apps/api/src/utils/export-schema.ts +++ b/apps/web/src/containers/setting-menu/ImageSetting/index.ts @@ -13,3 +13,4 @@ * License for the specific language governing permissions and limitations * under the License. */ +export { default } from './ImageSetting'; diff --git a/apps/web/src/containers/setting-menu/TicketSetting/TicketSetting.tsx b/apps/web/src/containers/setting-menu/IssueTrackerSetting/IssueTrackerSetting.tsx similarity index 94% rename from apps/web/src/containers/setting-menu/TicketSetting/TicketSetting.tsx rename to apps/web/src/containers/setting-menu/IssueTrackerSetting/IssueTrackerSetting.tsx index 252ee34eb..302aec504 100644 --- a/apps/web/src/containers/setting-menu/TicketSetting/TicketSetting.tsx +++ b/apps/web/src/containers/setting-menu/IssueTrackerSetting/IssueTrackerSetting.tsx @@ -38,7 +38,7 @@ const scheme: Zod.ZodType = z.object({ interface IProps extends React.PropsWithChildren { projectId: number; } -const TicketSetting: React.FC = ({ projectId }) => { +const IssueTrackerSetting: React.FC = ({ projectId }) => { const { t } = useTranslation(); const perms = usePermissions(projectId); @@ -91,7 +91,7 @@ const TicketSetting: React.FC = ({ projectId }) => { return ( = ({ projectId }) => { >

    - {t('main.setting.issue-tracker-mgmt.description')} + {t('help-card.issue-tracker')}

    = ({ projectId }) => { = ({ projectId }) => { ); }; -export default TicketSetting; +export default IssueTrackerSetting; diff --git a/apps/api/src/utils/sort.ts b/apps/web/src/containers/setting-menu/IssueTrackerSetting/index.ts similarity index 88% rename from apps/api/src/utils/sort.ts rename to apps/web/src/containers/setting-menu/IssueTrackerSetting/index.ts index 2c8553290..f7c562bcc 100644 --- a/apps/api/src/utils/sort.ts +++ b/apps/web/src/containers/setting-menu/IssueTrackerSetting/index.ts @@ -13,4 +13,4 @@ * License for the specific language governing permissions and limitations * under the License. */ -export const strSort = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0); +export { default } from './IssueTrackerSetting'; diff --git a/apps/web/src/containers/setting-menu/MemberSetting/MemberSetting.tsx b/apps/web/src/containers/setting-menu/MemberSetting/MemberSetting.tsx index bcbd010ff..c1c3a1fc7 100644 --- a/apps/web/src/containers/setting-menu/MemberSetting/MemberSetting.tsx +++ b/apps/web/src/containers/setting-menu/MemberSetting/MemberSetting.tsx @@ -148,7 +148,7 @@ const MemberSetting: React.FC = ({ projectId }) => { }); return ( setOpenDialog(true), diff --git a/apps/web/src/containers/setting-menu/ProjectDeleteSetting/ProjectDeleteSetting.tsx b/apps/web/src/containers/setting-menu/ProjectDeleteSetting/ProjectDeleteSetting.tsx index 49c5c1665..46f5c4ad8 100644 --- a/apps/web/src/containers/setting-menu/ProjectDeleteSetting/ProjectDeleteSetting.tsx +++ b/apps/web/src/containers/setting-menu/ProjectDeleteSetting/ProjectDeleteSetting.tsx @@ -72,7 +72,7 @@ const ProjectDeleteSetting: React.FC = ({ projectId }) => { return ( = ({ projectId }) => { return ( = (props) => { iconName="DocumentInfoFill" onClick={onClickSettingMenu('PROJECT_INFO')} active={settingMenu === 'PROJECT_INFO'} - name={t('main.setting.subtitle.project-info')} + name={t('project-setting-menu.project-info')} disabled={!perms.includes('project_read')} />
diff --git a/apps/web/src/containers/setting-menu/RoleSetting/RoleSetting.tsx b/apps/web/src/containers/setting-menu/RoleSetting/RoleSetting.tsx index b6f5be7c6..265cce93b 100644 --- a/apps/web/src/containers/setting-menu/RoleSetting/RoleSetting.tsx +++ b/apps/web/src/containers/setting-menu/RoleSetting/RoleSetting.tsx @@ -90,7 +90,7 @@ const RoleSetting: React.FC = ({ projectId }) => { return ( createRole({ name, permissions: [] })} diff --git a/apps/web/src/containers/setting-menu/RoleSetting/RoleSettingTable.tsx b/apps/web/src/containers/setting-menu/RoleSetting/RoleSettingTable.tsx index 399a335db..070e2a15c 100644 --- a/apps/web/src/containers/setting-menu/RoleSetting/RoleSettingTable.tsx +++ b/apps/web/src/containers/setting-menu/RoleSetting/RoleSettingTable.tsx @@ -19,6 +19,7 @@ import { useEffect, useMemo, useState } from 'react'; import type { PermissionType } from '@/types/permission.type'; import { ChannelFieldPermissionList, + ChannelImageSettingPermissionList, ChannelInfoPermissionList, ChannelPermissionText, FeedbackPermissionList, @@ -227,6 +228,20 @@ const RoleSettingTable: React.FC = (props) => { roles={roles ?? []} depth={3} /> + + = z.object({ }) .nullable(), }); + type DomainStateType = { isSubmitted: boolean; isValid: boolean; @@ -113,7 +114,7 @@ const SignUpSetting: React.FC = () => { }; const onClickAdd = () => { - setDomainState({ isSubmitted: true, isValid: false }); + setDomainState({ isSubmitted: true, isValid: true }); if (!currentDomain || currentDomain.length < 1) { setDomainState((prev) => ({ ...prev, @@ -137,11 +138,9 @@ const SignUpSetting: React.FC = () => { setValue(`allowDomains.${(watch('allowDomains') ?? []).length}`, domain, { shouldDirty: true, }); + setCurrentDomain(''); - setDomainState((prev) => ({ - ...prev, - isValid: true, - })); + setDomainState((prev) => ({ ...prev, isValid: true })); }; const onSubmit = (input: ISignUpInfoForm) => { @@ -158,7 +157,7 @@ const SignUpSetting: React.FC = () => { return ( = () => { }; return ( = (props) => {
    = () => { return ( } >
    diff --git a/apps/web/src/containers/setting-menu/index.ts b/apps/web/src/containers/setting-menu/index.ts index 46c79008b..7e5e9b344 100644 --- a/apps/web/src/containers/setting-menu/index.ts +++ b/apps/web/src/containers/setting-menu/index.ts @@ -20,11 +20,12 @@ export { default as ProjectDeleteSetting } from './ProjectDeleteSetting'; export { default as ProjectInfoSetting } from './ProjectInfoSetting'; export { default as SignUpSetting } from './SignUpSetting'; export { default as TenantInfoSetting } from './TenantInfoSetting'; -export { default as TicketSetting } from './TicketSetting'; +export { default as IssueTrackerSetting } from './IssueTrackerSetting'; export { default as RoleSetting } from './RoleSetting'; export { default as ChannelInfoSetting } from './ChannelInfoSetting'; export { default as MemberSetting } from './MemberSetting'; export { default as UserSetting } from './UserSetting'; +export { default as ImageSetting } from './ImageSetting'; export { default as TenantSettingMenu } from './TenantSettingMenu'; export { default as ProjectSettingMenu } from './ProjectSettingMenu'; diff --git a/apps/web/src/containers/tables/FeedbackTable/ColumnSettingPopover/ColumnSettingPopover.tsx b/apps/web/src/containers/tables/FeedbackTable/ColumnSettingPopover/ColumnSettingPopover.tsx index 369f79579..77205ccd7 100644 --- a/apps/web/src/containers/tables/FeedbackTable/ColumnSettingPopover/ColumnSettingPopover.tsx +++ b/apps/web/src/containers/tables/FeedbackTable/ColumnSettingPopover/ColumnSettingPopover.tsx @@ -60,7 +60,13 @@ const ColumnSettingPopover: React.FC = ({ () => columnOrder.length === 0 ? (columns.map((v) => v.id) as string[]) - : columnOrder, + : columnOrder.length === columns.length + ? columnOrder + : columnOrder.concat( + columns + .filter((v) => !columnOrder.includes(v.id as string)) + .map((v) => v.id) as string[], + ), [columns, columnOrder], ); diff --git a/apps/web/src/containers/tables/FeedbackTable/FeedbackCell/FeedbackCell.tsx b/apps/web/src/containers/tables/FeedbackTable/FeedbackCell/FeedbackCell.tsx index 0be6a0243..a9c216d20 100644 --- a/apps/web/src/containers/tables/FeedbackTable/FeedbackCell/FeedbackCell.tsx +++ b/apps/web/src/containers/tables/FeedbackTable/FeedbackCell/FeedbackCell.tsx @@ -17,6 +17,7 @@ import { memo } from 'react'; import dayjs from 'dayjs'; import { ExpandableText } from '@/components'; +import { ImagePreviewButton } from '@/components/buttons'; import { DATE_TIME_FORMAT } from '@/constants/dayjs-format'; import type { FieldType } from '@/types/field.type'; @@ -31,23 +32,25 @@ const FeedbackCell: React.FC = memo((props) => { return ( - {typeof value === 'undefined' - ? undefined - : field.format === 'date' - ? dayjs(value as string).format(DATE_TIME_FORMAT) - : field.format === 'multiSelect' - ? (value as string[]) - .map( - (key) => - field.options?.find((option) => option.key === key)?.name ?? - value, - ) - .join(', ') - : field.format === 'select' - ? field.options?.find((option) => option.key === value)?.name ?? value - : field.format === 'text' - ? (value as string) - : String(value)} + {typeof value === 'undefined' ? undefined : field.format === 'date' ? ( + dayjs(value as string).format(DATE_TIME_FORMAT) + ) : field.format === 'multiSelect' ? ( + (value as string[]) + .map( + (key) => + field.options?.find((option) => option.key === key)?.name ?? + value, + ) + .join(', ') + ) : field.format === 'select' ? ( + field.options?.find((option) => option.key === value)?.name ?? value + ) : field.format === 'images' ? ( + + ) : field.format === 'text' ? ( + (value as string) + ) : ( + String(value) + )} ); }); diff --git a/apps/web/src/containers/tables/FeedbackTable/FeedbackDetail/FeedbackDetail.tsx b/apps/web/src/containers/tables/FeedbackTable/FeedbackDetail/FeedbackDetail.tsx index 6dc154825..53ec2af19 100644 --- a/apps/web/src/containers/tables/FeedbackTable/FeedbackDetail/FeedbackDetail.tsx +++ b/apps/web/src/containers/tables/FeedbackTable/FeedbackDetail/FeedbackDetail.tsx @@ -13,6 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ +import { useMemo } from 'react'; +import Image from 'next/image'; import { autoUpdate, FloatingFocusManager, @@ -31,10 +33,11 @@ import { Badge, Icon } from '@ufb/ui'; import { DATE_TIME_FORMAT } from '@/constants/dayjs-format'; import { getStatusColor } from '@/constants/issues'; -import { useFeedbackSearch, useOAIQuery } from '@/hooks'; +import { useFeedbackSearch, useHorizontalScroll, useOAIQuery } from '@/hooks'; import type { FieldType } from '@/types/field.type'; import type { IssueType } from '@/types/issue.type'; import FeedbackDetailCell from './FeedbackDetailCell'; +import FeedbackDetailIssueCell from './FeedbackDetailIssueCell'; interface IProps { id: number; @@ -69,6 +72,30 @@ const FeedbackDetail: React.FC = (props) => { const { getFloatingProps } = useInteractions([click, dismiss, role]); + const idField = useMemo( + () => channelData?.fields.find((field) => field.key === 'id') ?? null, + [channelData?.fields], + ); + const issuesField = useMemo( + () => channelData?.fields.find((field) => field.key === 'issues') ?? null, + [channelData?.fields], + ); + const createdField = useMemo( + () => + channelData?.fields.find((field) => field.key === 'createdAt') ?? null, + [channelData?.fields], + ); + const updatedField = useMemo( + () => + channelData?.fields.find((field) => field.key === 'updatedAt') ?? null, + [channelData?.fields], + ); + + const feedbackFields = useMemo(() => { + if (!channelData?.fields) return []; + return channelData?.fields.filter((field) => field.type !== 'DEFAULT'); + }, [channelData?.fields]); + return ( @@ -97,8 +124,45 @@ const FeedbackDetail: React.FC = (props) => {
- {channelData?.fields.sort(fieldSortType).map((field) => ( - + + + + + + + + + + + + + {feedbackFields.sort(fieldSortType).map((field) => ( + @@ -127,6 +191,8 @@ const FeedbackDetail: React.FC = (props) => { dayjs(feedbackData[field.key]).format( DATE_TIME_FORMAT, ) + ) : field.format === 'images' ? ( + ) : ( feedbackData[field.key] )} @@ -142,6 +208,76 @@ const FeedbackDetail: React.FC = (props) => { ); }; + +interface IImageSliderProps { + urls: string[]; +} +const ImageSlider: React.FC = ({ urls }) => { + const { + containerRef, + scrollLeft, + scrollRight, + showLeftButton, + showRightButton, + } = useHorizontalScroll({ + defaultRightButtonShown: urls.length > 4, + scrollGap: 140, + }); + return ( +
+
+ {showRightButton && ( + + )} + {showLeftButton && ( + + )} +
+
+
+ {urls?.map((url) => ( +
window.open(url, '_blank')} + > +
+ + +
+ ))} +
+
+
+ ); +}; + const fieldSortType = (a: FieldType, b: FieldType) => { const aNum = a.type === 'DEFAULT' ? 1 : a.type === 'API' ? 2 : 3; const bNum = b.type === 'DEFAULT' ? 1 : b.type === 'API' ? 2 : 3; diff --git a/apps/web/src/containers/tables/FeedbackTable/FeedbackDetail/FeedbackDetailCell.tsx b/apps/web/src/containers/tables/FeedbackTable/FeedbackDetail/FeedbackDetailCell.tsx index af3cbd2da..c277c9cd4 100644 --- a/apps/web/src/containers/tables/FeedbackTable/FeedbackDetail/FeedbackDetailCell.tsx +++ b/apps/web/src/containers/tables/FeedbackTable/FeedbackDetail/FeedbackDetailCell.tsx @@ -29,7 +29,7 @@ const FeedbackDetailCell: React.FC = ({ children }) => { useTruncatedElement({ ref }); return ( -
+ {idField?.name} + + {JSON.stringify(feedbackData[idField?.key ?? ''])} + + {issuesField?.name} + + +
+ {createdField?.name} + + {dayjs(feedbackData[createdField?.key ?? '']).format( + DATE_TIME_FORMAT, + )} + + {updatedField?.name} + + {dayjs(feedbackData[updatedField?.key ?? '']).format( + DATE_TIME_FORMAT, + )} +
{field.name} +
= ({ issues }) => { + const ref = useRef(null); + const [isOverflow, setOverflow] = useState(false); + + useEffect(() => { + if (!ref.current) return; + const newState = ref.current.clientWidth < ref.current.scrollWidth; + if (newState === isOverflow) return; + setOverflow(newState); + }, [ref.current, issues]); + + return ( +
+ {issues.map((v) => ( + + {v.name} + + ))} + {isOverflow && ( +
+ + + + + +
+

전체

+
    + {issues.map((v) => ( +
  • + + {v.name} + +
  • + ))} +
+
+
+
+
+ )} +
+ ); +}; + +export default FeedbackDetailIssueCell; diff --git a/apps/web/src/containers/tables/FeedbackTable/FeedbackTable.tsx b/apps/web/src/containers/tables/FeedbackTable/FeedbackTable.tsx index 48b930474..2465a6bf8 100644 --- a/apps/web/src/containers/tables/FeedbackTable/FeedbackTable.tsx +++ b/apps/web/src/containers/tables/FeedbackTable/FeedbackTable.tsx @@ -93,9 +93,8 @@ const FeedbackTable: React.FC = (props) => { () => produce(query, (draft) => { if (sub) { - if (issueId) { - draft['issueIds'] = [issueId]; - } + if (issueId) draft['issueIds'] = [issueId]; + Object.keys(draft).forEach((key) => { if (key === 'issueIds') return; delete draft[key]; diff --git a/apps/web/src/containers/tables/FeedbackTable/IssueCell/IssueCell.tsx b/apps/web/src/containers/tables/FeedbackTable/IssueCell/IssueCell.tsx index 7fc06954b..4d8eb197b 100644 --- a/apps/web/src/containers/tables/FeedbackTable/IssueCell/IssueCell.tsx +++ b/apps/web/src/containers/tables/FeedbackTable/IssueCell/IssueCell.tsx @@ -149,7 +149,7 @@ const IssueCell: React.FC = (props) => { }; return ( -
e.stopPropagation()}> +
= (props) => { issues?.length === 0 ? (
); + const modalChild = context.modal ? ( {child} diff --git a/packages/ufb-ui/src/Toast/ToastBox.tsx b/packages/ufb-ui/src/Toast/ToastBox.tsx index 002a93102..cb29c9126 100644 --- a/packages/ufb-ui/src/Toast/ToastBox.tsx +++ b/packages/ufb-ui/src/Toast/ToastBox.tsx @@ -21,7 +21,7 @@ import type { IconNameType } from '../Icon'; import { Icon } from '../Icon'; interface IProps { - type: 'positive' | 'negative'; + type: 'positive' | 'negative' | 'accent'; title?: string; description?: string; iconName?: IconNameType; @@ -41,10 +41,13 @@ export const ToastBox: React.FC = ({ return 'bg-red-primary'; case 'positive': return 'bg-green-primary'; + case 'accent': + return 'bg-blue-primary'; default: return ''; } }, [type]); + const icon = useMemo(() => { if (iconName) return iconName; switch (type) { @@ -52,6 +55,8 @@ export const ToastBox: React.FC = ({ return 'WarningTriangleFill' as const; case 'positive': return 'CircleCheck' as const; + case 'accent': + return 'CircleCheck' as const; default: return 'CircleCheck' as const; } diff --git a/packages/ufb-ui/src/Toast/toast.tsx b/packages/ufb-ui/src/Toast/toast.tsx index db662c09f..8f4bb9109 100644 --- a/packages/ufb-ui/src/Toast/toast.tsx +++ b/packages/ufb-ui/src/Toast/toast.tsx @@ -31,6 +31,8 @@ export const toast = { reactToast.custom((t) => ), negative: (input: IToastProps) => reactToast.custom((t) => ), + accent: (input: IToastProps) => + reactToast.custom((t) => ), promise: async ( fn: Promise, input: { diff --git a/tooling/eslint/nextjs.js b/tooling/eslint/nextjs.js index e397a2aaf..802ff71cb 100644 --- a/tooling/eslint/nextjs.js +++ b/tooling/eslint/nextjs.js @@ -15,7 +15,9 @@ */ const config = { extends: ['plugin:@next/next/recommended'], - rules: { '@next/next/no-html-link-for-pages': 'off' }, + rules: { + '@next/next/no-html-link-for-pages': 'off', + }, }; module.exports = config; diff --git a/turbo.json b/turbo.json index 0f3f9e220..515136b76 100644 --- a/turbo.json +++ b/turbo.json @@ -14,7 +14,10 @@ "persistent": true }, "web#dev": { - "dependsOn": ["@ufb/tailwind#build"] + "dependsOn": ["@ufb/tailwind#build", "@ufb/shared#build"] + }, + "api#dev": { + "dependsOn": ["@ufb/shared#build"] }, "@ufb/tailwind#build": { "outputs": ["dist/**"] diff --git a/yarn.lock b/yarn.lock index c850ef9cb..4d426ad00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -159,457 +159,461 @@ "@aws-sdk/util-utf8-browser" "^3.0.0" tslib "^1.11.1" -"@aws-sdk/client-s3@3.465.0", "@aws-sdk/client-s3@^3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.465.0.tgz#7bad33ccfaa3d460247bbdb0e59c5ac7f4c3e2aa" - integrity sha512-S2W8aUs/SR7wabyKRldl5FKtAq2gsXo3BpbKjBvuCILwNl84ooQrsOmKtcVsINRdi+q/mZvwGenqqp/98+yjdg== +"@aws-sdk/client-s3@^3.490.0": + version "3.490.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.490.0.tgz#30fe38d6b8b3509aa91a5154318c849ec974c2fc" + integrity sha512-fBj3CJ3+5R+l/sc93Z9mKw8gM2b9K6vEhC9qSCG2XNymLd9YqlRft1peQ7VymrWywAHX3Koz1GCUrFEVNONiMw== dependencies: "@aws-crypto/sha1-browser" "3.0.0" "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.465.0" - "@aws-sdk/core" "3.465.0" - "@aws-sdk/credential-provider-node" "3.465.0" - "@aws-sdk/middleware-bucket-endpoint" "3.465.0" - "@aws-sdk/middleware-expect-continue" "3.465.0" - "@aws-sdk/middleware-flexible-checksums" "3.465.0" - "@aws-sdk/middleware-host-header" "3.465.0" - "@aws-sdk/middleware-location-constraint" "3.465.0" - "@aws-sdk/middleware-logger" "3.465.0" - "@aws-sdk/middleware-recursion-detection" "3.465.0" - "@aws-sdk/middleware-sdk-s3" "3.465.0" - "@aws-sdk/middleware-signing" "3.465.0" - "@aws-sdk/middleware-ssec" "3.465.0" - "@aws-sdk/middleware-user-agent" "3.465.0" - "@aws-sdk/region-config-resolver" "3.465.0" - "@aws-sdk/signature-v4-multi-region" "3.465.0" - "@aws-sdk/types" "3.465.0" - "@aws-sdk/util-endpoints" "3.465.0" - "@aws-sdk/util-user-agent-browser" "3.465.0" - "@aws-sdk/util-user-agent-node" "3.465.0" - "@aws-sdk/xml-builder" "3.465.0" - "@smithy/config-resolver" "^2.0.18" - "@smithy/eventstream-serde-browser" "^2.0.13" - "@smithy/eventstream-serde-config-resolver" "^2.0.13" - "@smithy/eventstream-serde-node" "^2.0.13" - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/hash-blob-browser" "^2.0.14" - "@smithy/hash-node" "^2.0.15" - "@smithy/hash-stream-node" "^2.0.15" - "@smithy/invalid-dependency" "^2.0.13" - "@smithy/md5-js" "^2.0.15" - "@smithy/middleware-content-length" "^2.0.15" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/middleware-retry" "^2.0.20" - "@smithy/middleware-serde" "^2.0.13" - "@smithy/middleware-stack" "^2.0.7" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/node-http-handler" "^2.1.9" - "@smithy/protocol-http" "^3.0.9" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" + "@aws-sdk/client-sts" "3.490.0" + "@aws-sdk/core" "3.490.0" + "@aws-sdk/credential-provider-node" "3.490.0" + "@aws-sdk/middleware-bucket-endpoint" "3.489.0" + "@aws-sdk/middleware-expect-continue" "3.489.0" + "@aws-sdk/middleware-flexible-checksums" "3.489.0" + "@aws-sdk/middleware-host-header" "3.489.0" + "@aws-sdk/middleware-location-constraint" "3.489.0" + "@aws-sdk/middleware-logger" "3.489.0" + "@aws-sdk/middleware-recursion-detection" "3.489.0" + "@aws-sdk/middleware-sdk-s3" "3.489.0" + "@aws-sdk/middleware-signing" "3.489.0" + "@aws-sdk/middleware-ssec" "3.489.0" + "@aws-sdk/middleware-user-agent" "3.489.0" + "@aws-sdk/region-config-resolver" "3.489.0" + "@aws-sdk/signature-v4-multi-region" "3.489.0" + "@aws-sdk/types" "3.489.0" + "@aws-sdk/util-endpoints" "3.489.0" + "@aws-sdk/util-user-agent-browser" "3.489.0" + "@aws-sdk/util-user-agent-node" "3.489.0" + "@aws-sdk/xml-builder" "3.485.0" + "@smithy/config-resolver" "^2.0.23" + "@smithy/core" "^1.2.2" + "@smithy/eventstream-serde-browser" "^2.0.16" + "@smithy/eventstream-serde-config-resolver" "^2.0.16" + "@smithy/eventstream-serde-node" "^2.0.16" + "@smithy/fetch-http-handler" "^2.3.2" + "@smithy/hash-blob-browser" "^2.0.17" + "@smithy/hash-node" "^2.0.18" + "@smithy/hash-stream-node" "^2.0.18" + "@smithy/invalid-dependency" "^2.0.16" + "@smithy/md5-js" "^2.0.18" + "@smithy/middleware-content-length" "^2.0.18" + "@smithy/middleware-endpoint" "^2.3.0" + "@smithy/middleware-retry" "^2.0.26" + "@smithy/middleware-serde" "^2.0.16" + "@smithy/middleware-stack" "^2.0.10" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/node-http-handler" "^2.2.2" + "@smithy/protocol-http" "^3.0.12" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" + "@smithy/url-parser" "^2.0.16" "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.0" + "@smithy/util-body-length-browser" "^2.0.1" "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.19" - "@smithy/util-defaults-mode-node" "^2.0.25" - "@smithy/util-endpoints" "^1.0.4" - "@smithy/util-retry" "^2.0.6" - "@smithy/util-stream" "^2.0.20" + "@smithy/util-defaults-mode-browser" "^2.0.24" + "@smithy/util-defaults-mode-node" "^2.0.32" + "@smithy/util-endpoints" "^1.0.8" + "@smithy/util-retry" "^2.0.9" + "@smithy/util-stream" "^2.0.24" "@smithy/util-utf8" "^2.0.2" - "@smithy/util-waiter" "^2.0.13" + "@smithy/util-waiter" "^2.0.16" fast-xml-parser "4.2.5" tslib "^2.5.0" -"@aws-sdk/client-sso@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.465.0.tgz#a732c640767d8d82c3c73d798720d0a8d355184d" - integrity sha512-JXDBa3Sl+LS0KEOs0PZoIjpNKEEGfeyFwdnRxi8Y1hMXNEKyJug1cI2Psqu2olpn4KeXwoP1BuITppZYdolOew== +"@aws-sdk/client-sso@3.490.0": + version "3.490.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.490.0.tgz#f18720d6301b83de858afd9b7dd4a2452b18e8ad" + integrity sha512-yfxoHmCL1w/IKmFRfzCxdVCQrGlSQf4eei9iVEm5oi3iE8REFyPj3o/BmKQEHG3h2ITK5UbdYDb5TY4xoYHsyA== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.465.0" - "@aws-sdk/middleware-host-header" "3.465.0" - "@aws-sdk/middleware-logger" "3.465.0" - "@aws-sdk/middleware-recursion-detection" "3.465.0" - "@aws-sdk/middleware-user-agent" "3.465.0" - "@aws-sdk/region-config-resolver" "3.465.0" - "@aws-sdk/types" "3.465.0" - "@aws-sdk/util-endpoints" "3.465.0" - "@aws-sdk/util-user-agent-browser" "3.465.0" - "@aws-sdk/util-user-agent-node" "3.465.0" - "@smithy/config-resolver" "^2.0.18" - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/hash-node" "^2.0.15" - "@smithy/invalid-dependency" "^2.0.13" - "@smithy/middleware-content-length" "^2.0.15" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/middleware-retry" "^2.0.20" - "@smithy/middleware-serde" "^2.0.13" - "@smithy/middleware-stack" "^2.0.7" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/node-http-handler" "^2.1.9" - "@smithy/protocol-http" "^3.0.9" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" + "@aws-sdk/core" "3.490.0" + "@aws-sdk/middleware-host-header" "3.489.0" + "@aws-sdk/middleware-logger" "3.489.0" + "@aws-sdk/middleware-recursion-detection" "3.489.0" + "@aws-sdk/middleware-user-agent" "3.489.0" + "@aws-sdk/region-config-resolver" "3.489.0" + "@aws-sdk/types" "3.489.0" + "@aws-sdk/util-endpoints" "3.489.0" + "@aws-sdk/util-user-agent-browser" "3.489.0" + "@aws-sdk/util-user-agent-node" "3.489.0" + "@smithy/config-resolver" "^2.0.23" + "@smithy/core" "^1.2.2" + "@smithy/fetch-http-handler" "^2.3.2" + "@smithy/hash-node" "^2.0.18" + "@smithy/invalid-dependency" "^2.0.16" + "@smithy/middleware-content-length" "^2.0.18" + "@smithy/middleware-endpoint" "^2.3.0" + "@smithy/middleware-retry" "^2.0.26" + "@smithy/middleware-serde" "^2.0.16" + "@smithy/middleware-stack" "^2.0.10" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/node-http-handler" "^2.2.2" + "@smithy/protocol-http" "^3.0.12" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" + "@smithy/url-parser" "^2.0.16" "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.0" + "@smithy/util-body-length-browser" "^2.0.1" "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.19" - "@smithy/util-defaults-mode-node" "^2.0.25" - "@smithy/util-endpoints" "^1.0.4" - "@smithy/util-retry" "^2.0.6" + "@smithy/util-defaults-mode-browser" "^2.0.24" + "@smithy/util-defaults-mode-node" "^2.0.32" + "@smithy/util-endpoints" "^1.0.8" + "@smithy/util-retry" "^2.0.9" "@smithy/util-utf8" "^2.0.2" tslib "^2.5.0" -"@aws-sdk/client-sts@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.465.0.tgz#b356b90b0e31a82dc41995282245f74d023ea8b5" - integrity sha512-rHi9ba6ssNbVjlWSdhi4C5newEhGhzkY9UE4KB+/Tj21zXfEP8r6uIltnQXPtun2SdA95Krh/yS1qQ4MRuzqyA== +"@aws-sdk/client-sts@3.490.0": + version "3.490.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.490.0.tgz#17bf245705790fd632e4fa5d0cf0f312069f8a4d" + integrity sha512-n2vQ5Qu2qi2I0XMI+IH99ElpIRHOJTa1+sqNC4juMYxKQBMvw+EnsqUtaL3QvTHoyxNB/R7mpkeBB6SzPQ1TtA== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.465.0" - "@aws-sdk/credential-provider-node" "3.465.0" - "@aws-sdk/middleware-host-header" "3.465.0" - "@aws-sdk/middleware-logger" "3.465.0" - "@aws-sdk/middleware-recursion-detection" "3.465.0" - "@aws-sdk/middleware-sdk-sts" "3.465.0" - "@aws-sdk/middleware-signing" "3.465.0" - "@aws-sdk/middleware-user-agent" "3.465.0" - "@aws-sdk/region-config-resolver" "3.465.0" - "@aws-sdk/types" "3.465.0" - "@aws-sdk/util-endpoints" "3.465.0" - "@aws-sdk/util-user-agent-browser" "3.465.0" - "@aws-sdk/util-user-agent-node" "3.465.0" - "@smithy/config-resolver" "^2.0.18" - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/hash-node" "^2.0.15" - "@smithy/invalid-dependency" "^2.0.13" - "@smithy/middleware-content-length" "^2.0.15" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/middleware-retry" "^2.0.20" - "@smithy/middleware-serde" "^2.0.13" - "@smithy/middleware-stack" "^2.0.7" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/node-http-handler" "^2.1.9" - "@smithy/protocol-http" "^3.0.9" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" + "@aws-sdk/core" "3.490.0" + "@aws-sdk/credential-provider-node" "3.490.0" + "@aws-sdk/middleware-host-header" "3.489.0" + "@aws-sdk/middleware-logger" "3.489.0" + "@aws-sdk/middleware-recursion-detection" "3.489.0" + "@aws-sdk/middleware-user-agent" "3.489.0" + "@aws-sdk/region-config-resolver" "3.489.0" + "@aws-sdk/types" "3.489.0" + "@aws-sdk/util-endpoints" "3.489.0" + "@aws-sdk/util-user-agent-browser" "3.489.0" + "@aws-sdk/util-user-agent-node" "3.489.0" + "@smithy/config-resolver" "^2.0.23" + "@smithy/core" "^1.2.2" + "@smithy/fetch-http-handler" "^2.3.2" + "@smithy/hash-node" "^2.0.18" + "@smithy/invalid-dependency" "^2.0.16" + "@smithy/middleware-content-length" "^2.0.18" + "@smithy/middleware-endpoint" "^2.3.0" + "@smithy/middleware-retry" "^2.0.26" + "@smithy/middleware-serde" "^2.0.16" + "@smithy/middleware-stack" "^2.0.10" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/node-http-handler" "^2.2.2" + "@smithy/protocol-http" "^3.0.12" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" + "@smithy/url-parser" "^2.0.16" "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.0" + "@smithy/util-body-length-browser" "^2.0.1" "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.19" - "@smithy/util-defaults-mode-node" "^2.0.25" - "@smithy/util-endpoints" "^1.0.4" - "@smithy/util-retry" "^2.0.6" + "@smithy/util-defaults-mode-browser" "^2.0.24" + "@smithy/util-defaults-mode-node" "^2.0.32" + "@smithy/util-endpoints" "^1.0.8" + "@smithy/util-middleware" "^2.0.9" + "@smithy/util-retry" "^2.0.9" "@smithy/util-utf8" "^2.0.2" fast-xml-parser "4.2.5" tslib "^2.5.0" -"@aws-sdk/core@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.465.0.tgz#bfc9dd0fbd953f0839666e9b24c50c4543f49112" - integrity sha512-fHSIw/Rgex3KbrEKn6ZrUc2VcsOTpdBMeyYtfmsTOLSyDDOG9k3jelOvVbCbrK5N6uEUSM8hrnySEKg94UB0cg== +"@aws-sdk/core@3.490.0": + version "3.490.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.490.0.tgz#387013cb6e4060b421c6b45bd33f419c5c8e4a76" + integrity sha512-TSBWkXtxMU7q1Zo6w3v5wIOr/sj7P5Jw3OyO7lJrFGsPsDC2xwpxkVqTesDxkzgMRypO52xjYEmveagn1xxBHg== dependencies: - "@smithy/smithy-client" "^2.1.15" + "@smithy/core" "^1.2.2" + "@smithy/protocol-http" "^3.0.12" + "@smithy/signature-v4" "^2.0.0" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/credential-provider-env@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.465.0.tgz#9bb1c2086165872ad024786e5a48ccb31c5438da" - integrity sha512-fku37AgkB9KhCuWHE6mfvbWYU0X84Df6MQ60nYH7s/PiNEhkX2cVI6X6kOKjP1MNIwRcYt+oQDvplVKdHume+A== +"@aws-sdk/credential-provider-env@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.489.0.tgz#69aeee7251047dbf3b1533514cb87c5fae333a47" + integrity sha512-5PqYsx9G5SB2tqPT9/z/u0EkF6D4wP6HTMWQs+DfMdmwXihrqQAgeYaTtV3KbXqb88p6sfacwxhUvE6+Rm494w== dependencies: - "@aws-sdk/types" "3.465.0" + "@aws-sdk/types" "3.489.0" "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.5.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/credential-provider-ini@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.465.0.tgz#d3c3596cc5ff5ebe372bbd62d7aac044cbf3e2e3" - integrity sha512-B1MFufvdToAEMtfszilVnKer2S7P/OfMhkCizq2zuu8aU/CquRyHvKEQgWdvqunUDrFnVTc0kUZgsbBY0uPjLg== - dependencies: - "@aws-sdk/credential-provider-env" "3.465.0" - "@aws-sdk/credential-provider-process" "3.465.0" - "@aws-sdk/credential-provider-sso" "3.465.0" - "@aws-sdk/credential-provider-web-identity" "3.465.0" - "@aws-sdk/types" "3.465.0" +"@aws-sdk/credential-provider-ini@3.490.0": + version "3.490.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.490.0.tgz#8a907f85a5d88614bc63eac15d0f86a6074fb9fe" + integrity sha512-7m63zyCpVqj9FsoDxWMWWRvL6c7zZzOcXYkHZmHujVVlmAXH0RT/vkXFkYgt+Ku+ov+v5NQrzwO5TmVoRt6O8g== + dependencies: + "@aws-sdk/credential-provider-env" "3.489.0" + "@aws-sdk/credential-provider-process" "3.489.0" + "@aws-sdk/credential-provider-sso" "3.490.0" + "@aws-sdk/credential-provider-web-identity" "3.489.0" + "@aws-sdk/types" "3.489.0" "@smithy/credential-provider-imds" "^2.0.0" "@smithy/property-provider" "^2.0.0" "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.5.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/credential-provider-node@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.465.0.tgz#b11cbc927aa17aacd0b7208cef5a88045c0bcf62" - integrity sha512-R3VA9yJ0BvezvrDxcgPTv9VHbVPbzchLTrX5jLFSVuW/lPPYLUi/Cjtyg9C9Y7qRfoQS4fNMvSRhwO5/TF68gA== - dependencies: - "@aws-sdk/credential-provider-env" "3.465.0" - "@aws-sdk/credential-provider-ini" "3.465.0" - "@aws-sdk/credential-provider-process" "3.465.0" - "@aws-sdk/credential-provider-sso" "3.465.0" - "@aws-sdk/credential-provider-web-identity" "3.465.0" - "@aws-sdk/types" "3.465.0" +"@aws-sdk/credential-provider-node@3.490.0": + version "3.490.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.490.0.tgz#fc1051f30e25eb00d63e40be79f5fd4b66d3bdfb" + integrity sha512-Gh33u2O5Xbout8G3z/Z5H/CZzdG1ophxf/XS3iMFxA1cazQ7swY1UMmGvB7Lm7upvax5anXouItD1Ph3gzKc4w== + dependencies: + "@aws-sdk/credential-provider-env" "3.489.0" + "@aws-sdk/credential-provider-ini" "3.490.0" + "@aws-sdk/credential-provider-process" "3.489.0" + "@aws-sdk/credential-provider-sso" "3.490.0" + "@aws-sdk/credential-provider-web-identity" "3.489.0" + "@aws-sdk/types" "3.489.0" "@smithy/credential-provider-imds" "^2.0.0" "@smithy/property-provider" "^2.0.0" "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.5.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/credential-provider-process@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.465.0.tgz#78134b19f7a02e7fb78afda16d7cae5b93ff324e" - integrity sha512-YE6ZrRYwvb8969hWQnr4uvOJ8RU0JrNsk3vWTe/czly37ioZUEhi8jmpQp4f2mX/6U6buoFGWu5Se3VCdw2SFQ== +"@aws-sdk/credential-provider-process@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.489.0.tgz#f0c2b5b22a1ca364ec89cd7e469673824606dec4" + integrity sha512-3vKQYJZ5cZYjy0870CPmbmKRBgATw2xCygxhn4m4UDCjOXVXcGUtYD51DMWsvBo3S0W8kH+FIJV4yuEDMFqLFQ== dependencies: - "@aws-sdk/types" "3.465.0" + "@aws-sdk/types" "3.489.0" "@smithy/property-provider" "^2.0.0" "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.5.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/credential-provider-sso@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.465.0.tgz#622d03eb5c8a0d7a48ba12e849351c841eb48ca6" - integrity sha512-tLIP/4JQIJpn8yIg6RZRQ2nmvj5i4wLZvYvY4RtaFv2JrQUkmmTfyOZJuOBrIFRwJjx0fHmFu8DJjcOhMzllIQ== +"@aws-sdk/credential-provider-sso@3.490.0": + version "3.490.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.490.0.tgz#0cb15aebf72bc7d253aa51dee6a888a2af38acda" + integrity sha512-3UUBUoPbFvT58IhS4Vb23omYj/QPNkjgxu9p9ruQ3KSjLkanI4w8t/l/jljA65q83P7CoLnM5UKG9L7RA8/V1Q== dependencies: - "@aws-sdk/client-sso" "3.465.0" - "@aws-sdk/token-providers" "3.465.0" - "@aws-sdk/types" "3.465.0" + "@aws-sdk/client-sso" "3.490.0" + "@aws-sdk/token-providers" "3.489.0" + "@aws-sdk/types" "3.489.0" "@smithy/property-provider" "^2.0.0" "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.5.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/credential-provider-web-identity@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.465.0.tgz#9db18766eeb0c58a99f7fb5d4bd95f0cf9008d4d" - integrity sha512-B4Y75fMTZIniEU0yyqat+9NsQbYlXdqP5Y3bShkaG3pGLOHzF/xMlWuG+D3kkQ806PLYi+BgfVls4BcO+NyVcA== +"@aws-sdk/credential-provider-web-identity@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.489.0.tgz#28e2ba4d1ee4de4d055875028ed205a2264611c1" + integrity sha512-mjIuE2Wg1H/ds0nXQ/7vfusEDudmdd8YzKZI1y5O4n60iZZtyB2RNIECtvLMx1EQAKclidY7/06qQkArrGau5Q== dependencies: - "@aws-sdk/types" "3.465.0" + "@aws-sdk/types" "3.489.0" "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.5.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/middleware-bucket-endpoint@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.465.0.tgz#215489d3cbdac037c3d5abaeabda173c3bc16b95" - integrity sha512-cyIR9Nwyie6giLypuLSUmZF3O5GqVRwia3Nq1B/6/Ho0LccH0/HT2x/nM8fFcnskWSNGTVZVvZzSrVYXynTtjA== +"@aws-sdk/middleware-bucket-endpoint@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.489.0.tgz#80a06f1c229fae364b5916e47178d118340e0f51" + integrity sha512-6rJ5bpNMKo7sEKQ6p2DMbQwM+ahMYASRxfdyH7hs18blvlcS20H1RYpNmJMqPPjxMwUWruty2JPMIRl4DFcv8w== dependencies: - "@aws-sdk/types" "3.465.0" + "@aws-sdk/types" "3.489.0" "@aws-sdk/util-arn-parser" "3.465.0" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" - "@smithy/util-config-provider" "^2.0.0" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/protocol-http" "^3.0.12" + "@smithy/types" "^2.8.0" + "@smithy/util-config-provider" "^2.1.0" tslib "^2.5.0" -"@aws-sdk/middleware-expect-continue@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.465.0.tgz#760aec6a19898972a4d97857ea2760bcd191c791" - integrity sha512-kthlPQDASsdtdVqKVKkJn9bHptcEpsQ6ptWeGBCYigicULvWI1fjSTeXrYczxNMVg+1Sv8xkb/bh+kUEu7mvZg== +"@aws-sdk/middleware-expect-continue@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.489.0.tgz#c03f1a867e836c8ca844fcb8c527d2d782d1b659" + integrity sha512-2RZfnVZFaGHwzPDQJsyf9SXufu1gUd4VsMhm7dC7SWF85XmpDrozbFznS/tD22QdtyWjerLoydZJMq229hpPqg== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/protocol-http" "^3.0.12" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/middleware-flexible-checksums@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.465.0.tgz#7b629873182bc107159665f0af5b6515161c8891" - integrity sha512-joWEWN0v1CpI4q9JlZki0AchVwLL8Les0+V+3JHVDcDgL4RQ04YUk9lMYbtldDwdyBNquKwW2+sGtIo/6ng0Tg== +"@aws-sdk/middleware-flexible-checksums@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.489.0.tgz#50413d053e7b62bef81772d6b0c00d5521cf4e30" + integrity sha512-Cy3rBUMr4P7raxzrJFWNRshfKrKV2EojawaC9Bfk/T8aFlV+FmVrRg4ISAXMOfS5pfy3xfAbvkzjOaeqCsGfrA== dependencies: "@aws-crypto/crc32" "3.0.0" "@aws-crypto/crc32c" "3.0.0" - "@aws-sdk/types" "3.465.0" + "@aws-sdk/types" "3.489.0" "@smithy/is-array-buffer" "^2.0.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" + "@smithy/protocol-http" "^3.0.12" + "@smithy/types" "^2.8.0" "@smithy/util-utf8" "^2.0.2" tslib "^2.5.0" -"@aws-sdk/middleware-host-header@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.465.0.tgz#4f353f6ea063e1ba1df968f9f0126a53d746217d" - integrity sha512-nnGva8eplwEJqdVzcb+xF2Fwua0PpiwxMEvpnIy73gNbetbJdgFIprryMLYes00xzJEqnew+LWdpcd3YyS34ZA== +"@aws-sdk/middleware-host-header@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.489.0.tgz#7c00fa49c6d359bdc9b4d27be09af29ac6700968" + integrity sha512-Cl7HJ1jhOfllwf0CRx1eB4ypRGMqdGKWpc0eSTXty7wWSvCdMZUhwfjQqu2bIOIlgYxg/gFu6TVmVZ6g4O8PlA== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/protocol-http" "^3.0.12" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/middleware-location-constraint@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.465.0.tgz#ce2f09d3257bd288990e567f406e74bbe08a5663" - integrity sha512-2+mwaI/ltE2ibr5poC+E9kJRfVIv7aHpAJkLu7uvESch9cpuFuGJu6fq0/gA82eKZ/gwpBj+AaXBsDFfsDWFsw== +"@aws-sdk/middleware-location-constraint@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.489.0.tgz#aa00bc9b2d7ab857b1b8405b1a688684f2c44ae7" + integrity sha512-NIVr+kHR2N6gxFeE3TNw2mEBxgj0N9xXBLy3dNYMMlAUvQlT/0z9HlC9+3XqcTS/Z5ElF/+pei6nqXTVt0He9A== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/middleware-logger@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.465.0.tgz#7d66b375ee343f00e35c64ba79b37656828bf171" - integrity sha512-aGMx1aSlzDDgjZ7fSxLhGD5rkyCfHwq04TSB5fQAgDBqUjj4IQXZwmNglX0sLRmArXZtDglUVESOfKvTANJTPg== +"@aws-sdk/middleware-logger@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.489.0.tgz#36855ec7ac8af4604f2a0b739358f0411878abea" + integrity sha512-+EVDnWese61MdImcBNAgz/AhTcIZJaska/xsU3GWU9CP905x4a4qZdB7fExFMDu1Jlz5pJqNteFYYHCFMJhHfg== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/middleware-recursion-detection@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.465.0.tgz#d0cb1fd9c63dbe997406253b5e0ce402103d910f" - integrity sha512-ol3dlsTnryBhV5qkUvK5Yg3dRaV1NXIxYJaIkShrl8XAv4wRNcDJDmO5NYq5eVZ3zgV1nv6xIpZ//dDnnf6Z+g== +"@aws-sdk/middleware-recursion-detection@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.489.0.tgz#bdcbfcebd3d27aad2e0b2808af7c1d3d380c52a2" + integrity sha512-m4rU+fTzziQcu9DKjRNZ4nQlXENEd2ZnJblJV4ONdWqqEjbmOgOj3P6aCCQlJdIbzuNvX1FBOZ5tY59ZpERo7Q== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/protocol-http" "^3.0.12" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/middleware-sdk-s3@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.465.0.tgz#fd417ce7a958066afb5c293b49eacd2f807e853e" - integrity sha512-P4cpNv0EcMSSLojjqKKQjKSGZc13QJQAscUs+fcvpBg2BNR9ByxrQgXXMqQiIqr8fgAhADqN2Tp8hJk0CzfnAg== +"@aws-sdk/middleware-sdk-s3@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.489.0.tgz#45c57501427f789cf8701b282e5e5136eb320c8b" + integrity sha512-/GGASx7mK9qEgy1znvleYMZKVqm3sOdGghqKdy2zgoGcH2jH+fZrLM0lDMT9bvdITmOCbJJs2rVHP3xm/ZWcXg== dependencies: - "@aws-sdk/types" "3.465.0" + "@aws-sdk/types" "3.489.0" "@aws-sdk/util-arn-parser" "3.465.0" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/protocol-http" "^3.0.9" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/protocol-http" "^3.0.12" "@smithy/signature-v4" "^2.0.0" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/util-config-provider" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/middleware-sdk-sts@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.465.0.tgz#73ad1f0940f924be1141125ceffcf4204c54c9bf" - integrity sha512-PmTM5ycUe1RLAPrQXLCR8JzKamJuKDB0aIW4rx4/skurzWsEGRI47WHggf9N7sPie41IBGUhRbXcf7sfPjvI3Q== - dependencies: - "@aws-sdk/middleware-signing" "3.465.0" - "@aws-sdk/types" "3.465.0" - "@smithy/types" "^2.5.0" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" + "@smithy/util-config-provider" "^2.1.0" tslib "^2.5.0" -"@aws-sdk/middleware-signing@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.465.0.tgz#2a040c39bfd6f2528ef9798944b4de2d33d2bdd1" - integrity sha512-d90KONWXSC3jA0kqJ6u8ygS4LoMg1TmSM7bPhHyibJVAEhnrlB4Aq1CWljNbbtphGpdKy5/XRM9O0/XCXWKQ4w== +"@aws-sdk/middleware-signing@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.489.0.tgz#ad92c3a4fb3afc2798b4f99a7ca6abaaf75461b8" + integrity sha512-rlHcWYZn6Ym3v/u0DvKNDiD7ogIzEsHlerm0lowTiQbszkFobOiUClRTALwvsUZdAAztl706qO1OKbnGnD6Ubw== dependencies: - "@aws-sdk/types" "3.465.0" + "@aws-sdk/types" "3.489.0" "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.9" + "@smithy/protocol-http" "^3.0.12" "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.5.0" - "@smithy/util-middleware" "^2.0.6" + "@smithy/types" "^2.8.0" + "@smithy/util-middleware" "^2.0.9" tslib "^2.5.0" -"@aws-sdk/middleware-ssec@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.465.0.tgz#ca6aff262527f0089a003afa33e901b81908f486" - integrity sha512-PHc1guBGp7fwoPlJkAEaHVkiYPfs93jffwsBvIevCsHcfYPv6L26/5Nk7KR+6IyuGQHpUbSC080SP1jYjOy01A== +"@aws-sdk/middleware-ssec@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.489.0.tgz#9991f6189fa1d3bf360e24f8d958f5456e4da7ce" + integrity sha512-5RQg8dqERAmi1OfVEV9fbTA5NKmcvKDYP79YtH08IEFIsHWU1Y5NoqL7mXkkNyBrJNBVyasYijAbTzOuM707eg== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/middleware-user-agent@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.465.0.tgz#2820d55ff7d774a4afe60f85fe88d959171f9052" - integrity sha512-1MvIWMj2nktLOJN8Kh4jiTK28oL85fTeoXHZ+V8xYMzont6C6Y8gQPtg7ka+RotHwqWMrovfnANisnX8EzEP/Q== +"@aws-sdk/middleware-user-agent@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.489.0.tgz#84b2f7e3038b631ecd9e3cddd0205d9b6a266444" + integrity sha512-M54Cv2fAN3GGgdfUjLtZ4wFUIrfM/ivbXv4DgpcNsacEQ2g4H+weQgKp41X7XZW8MWAzl+k1zJaryK69RYNQkQ== dependencies: - "@aws-sdk/types" "3.465.0" - "@aws-sdk/util-endpoints" "3.465.0" - "@smithy/protocol-http" "^3.0.9" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@aws-sdk/util-endpoints" "3.489.0" + "@smithy/protocol-http" "^3.0.12" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/region-config-resolver@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.465.0.tgz#87c3d2fe96e1e759d818f179f1e72204791145e6" - integrity sha512-h0Phd2Ae873dsPSWuxqxz2yRC5NMeeWxQiJPh4j42HF8g7dZK7tMQPkYznAoA/BzSBsEX87sbr3MmigquSyUTA== +"@aws-sdk/region-config-resolver@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.489.0.tgz#58bd9dfbe148e2de8bfd0e5e4a3719d56b594c85" + integrity sha512-UvrnB78XTz9ddby7mr0vuUHn2MO3VTjzaIu+GQhyedMGQU0QlIQrYOlzbbu4LC5rL1O8FxFLUxRe/AAjgwyuGw== dependencies: - "@smithy/node-config-provider" "^2.1.5" - "@smithy/types" "^2.5.0" - "@smithy/util-config-provider" "^2.0.0" - "@smithy/util-middleware" "^2.0.6" + "@aws-sdk/types" "3.489.0" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/types" "^2.8.0" + "@smithy/util-config-provider" "^2.1.0" + "@smithy/util-middleware" "^2.0.9" tslib "^2.5.0" -"@aws-sdk/s3-presigned-post@^3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.465.0.tgz#af12201f21768b12772d837537ef7e20d6beafee" - integrity sha512-choOjqcHAZePwIXdZAhfK1lJSg8nbpkqIoFxcfj6fK4vkPBFifdcQRur9K46f+DFhV9OA/SYB/9lE+WAPtw3jA== - dependencies: - "@aws-sdk/client-s3" "3.465.0" - "@aws-sdk/types" "3.465.0" - "@aws-sdk/util-format-url" "3.465.0" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.5.0" - "@smithy/util-hex-encoding" "^2.0.0" - "@smithy/util-utf8" "^2.0.2" +"@aws-sdk/s3-request-presigner@^3.490.0": + version "3.490.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.490.0.tgz#de5203f021e8f89463e0c68ea288dd7d9201ed93" + integrity sha512-ZHs+FlcTv9MKMM0b9svxxQio4FiRxDNstKYG8sbm9YEoahYV25h3K3butUiThaiOeYePOD7jHdbdXz4/XasxXg== + dependencies: + "@aws-sdk/signature-v4-multi-region" "3.489.0" + "@aws-sdk/types" "3.489.0" + "@aws-sdk/util-format-url" "3.489.0" + "@smithy/middleware-endpoint" "^2.3.0" + "@smithy/protocol-http" "^3.0.12" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/signature-v4-multi-region@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.465.0.tgz#c4c5b00d5998ea8e2a675cc592a0fb5eaa691147" - integrity sha512-p620S4YCr2CPNIdSnRvBqScAqWztjef9EwtD1MAkxTTrjNAyxSCf4apeQ2pdaWNNkJT1vSc/YKBAJ7l2SWn7rw== +"@aws-sdk/signature-v4-multi-region@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.489.0.tgz#d36574d34bc084f29c19d3bc51acbd821767dda0" + integrity sha512-kYFM7Opu36EkFlzXdVNOBFpQApgnuaTu/U/qYhGyuzeD+HNnYgZEsd/tDro1DQ074jVy3GN9ttJSYxq5I4oTkA== dependencies: - "@aws-sdk/middleware-sdk-s3" "3.465.0" - "@aws-sdk/types" "3.465.0" - "@smithy/protocol-http" "^3.0.9" + "@aws-sdk/middleware-sdk-s3" "3.489.0" + "@aws-sdk/types" "3.489.0" + "@smithy/protocol-http" "^3.0.12" "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.5.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/token-providers@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.465.0.tgz#e063a30c73878462a5a1542a3eb28ac5e72c5921" - integrity sha512-NaZbsyLs3whzRHGV27hrRwEdXB/tEK6tqn/aCNBy862LhVzocY1A+eYLKrnrvpraOOd2vyAuOtvvB3RMIdiL6g== +"@aws-sdk/token-providers@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.489.0.tgz#69897270f71595449f665b9f40754dfa21ea7be1" + integrity sha512-hSgjB8CMQoA8EIQ0ripDjDtbBcWDSa+7vSBYPIzksyknaGERR/GUfGXLV2dpm5t17FgFG6irT5f3ZlBzarL8Dw== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/middleware-host-header" "3.465.0" - "@aws-sdk/middleware-logger" "3.465.0" - "@aws-sdk/middleware-recursion-detection" "3.465.0" - "@aws-sdk/middleware-user-agent" "3.465.0" - "@aws-sdk/region-config-resolver" "3.465.0" - "@aws-sdk/types" "3.465.0" - "@aws-sdk/util-endpoints" "3.465.0" - "@aws-sdk/util-user-agent-browser" "3.465.0" - "@aws-sdk/util-user-agent-node" "3.465.0" - "@smithy/config-resolver" "^2.0.18" - "@smithy/fetch-http-handler" "^2.2.6" - "@smithy/hash-node" "^2.0.15" - "@smithy/invalid-dependency" "^2.0.13" - "@smithy/middleware-content-length" "^2.0.15" - "@smithy/middleware-endpoint" "^2.2.0" - "@smithy/middleware-retry" "^2.0.20" - "@smithy/middleware-serde" "^2.0.13" - "@smithy/middleware-stack" "^2.0.7" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/node-http-handler" "^2.1.9" + "@aws-sdk/middleware-host-header" "3.489.0" + "@aws-sdk/middleware-logger" "3.489.0" + "@aws-sdk/middleware-recursion-detection" "3.489.0" + "@aws-sdk/middleware-user-agent" "3.489.0" + "@aws-sdk/region-config-resolver" "3.489.0" + "@aws-sdk/types" "3.489.0" + "@aws-sdk/util-endpoints" "3.489.0" + "@aws-sdk/util-user-agent-browser" "3.489.0" + "@aws-sdk/util-user-agent-node" "3.489.0" + "@smithy/config-resolver" "^2.0.23" + "@smithy/fetch-http-handler" "^2.3.2" + "@smithy/hash-node" "^2.0.18" + "@smithy/invalid-dependency" "^2.0.16" + "@smithy/middleware-content-length" "^2.0.18" + "@smithy/middleware-endpoint" "^2.3.0" + "@smithy/middleware-retry" "^2.0.26" + "@smithy/middleware-serde" "^2.0.16" + "@smithy/middleware-stack" "^2.0.10" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/node-http-handler" "^2.2.2" "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.9" + "@smithy/protocol-http" "^3.0.12" "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/smithy-client" "^2.1.15" - "@smithy/types" "^2.5.0" - "@smithy/url-parser" "^2.0.13" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" + "@smithy/url-parser" "^2.0.16" "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.0" + "@smithy/util-body-length-browser" "^2.0.1" "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.19" - "@smithy/util-defaults-mode-node" "^2.0.25" - "@smithy/util-endpoints" "^1.0.4" - "@smithy/util-retry" "^2.0.6" + "@smithy/util-defaults-mode-browser" "^2.0.24" + "@smithy/util-defaults-mode-node" "^2.0.32" + "@smithy/util-endpoints" "^1.0.8" + "@smithy/util-retry" "^2.0.9" "@smithy/util-utf8" "^2.0.2" tslib "^2.5.0" -"@aws-sdk/types@3.465.0", "@aws-sdk/types@^3.222.0": +"@aws-sdk/types@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.489.0.tgz#0fa29adaace3e407ac15428524aa67e9bd229f65" + integrity sha512-kcDtLfKog/p0tC4gAeqJqWxAiEzfe2LRPnKamvSG2Mjbthx4R/alE2dxyIq/wW+nvRv0fqR3OD5kD1+eVfdr/w== + dependencies: + "@smithy/types" "^2.8.0" + tslib "^2.5.0" + +"@aws-sdk/types@^3.222.0": version "3.465.0" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.465.0.tgz#74977008020f3ed2e5fa0d61daef70d1cbfbfc37" integrity sha512-Clqu2eD50OOzwSftGpzJrIOGev/7VJhJpc02SeS4cqFgI9EVd+rnFKS/Ux0kcwjLQBMiPcCLtql3KAHApFHAIA== @@ -624,23 +628,24 @@ dependencies: tslib "^2.5.0" -"@aws-sdk/util-endpoints@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.465.0.tgz#b3800364bd856bdfe94e0a1c72979d1bda27a0b8" - integrity sha512-lDpBN1faVw8Udg5hIo+LJaNfllbBF86PCisv628vfcggO8/EArL/v2Eos0KeqVT8yaINXCRSagwfo5TNTuW0KQ== +"@aws-sdk/util-endpoints@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.489.0.tgz#8adfa6da0cac973a8ca0f2c4aa66f7d587310acb" + integrity sha512-uGyG1u84ATX03mf7bT4xD9XD/vlYJGD5+RxMN/UpzeTfzXfh+jvCQWbOQ44z8ttFJWYQQqrLxkfpF/JgvALzLA== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/util-endpoints" "^1.0.4" + "@aws-sdk/types" "3.489.0" + "@smithy/types" "^2.8.0" + "@smithy/util-endpoints" "^1.0.8" tslib "^2.5.0" -"@aws-sdk/util-format-url@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-format-url/-/util-format-url-3.465.0.tgz#fdd30792b788736fdb772e8e1e346bd97d9c595a" - integrity sha512-jJmIeMFQMADPw5DfyIzqqsIyS4p+duc1fhnOK+GfEkyja1cjafUQOcvovpQ4rCRHGffjTG2LCkOdyrSRDTFzfQ== +"@aws-sdk/util-format-url@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-format-url/-/util-format-url-3.489.0.tgz#c8f20a0cac55917bf08e32080906aa260d6c1422" + integrity sha512-yqIf9RMdOSxMUrv1BVDmrYp5kjLh4RxA17BTqzcQK8cXkRBqBP8ydbCQXENSv8LZSMH7AnrXNHBD1eiVuKRzZw== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/querystring-builder" "^2.0.13" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/querystring-builder" "^2.0.16" + "@smithy/types" "^2.8.0" tslib "^2.5.0" "@aws-sdk/util-locate-window@^3.0.0": @@ -650,24 +655,24 @@ dependencies: tslib "^2.5.0" -"@aws-sdk/util-user-agent-browser@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.465.0.tgz#2cb792c48770fe650cbb2b66ac21a8d65b0ca5ba" - integrity sha512-RM+LjkIsmUCBJ4yQeBnkJWJTjPOPqcNaKv8bpZxatIHdvzGhXLnWLNi3qHlBsJB2mKtKRet6nAUmKmzZR1sDzA== +"@aws-sdk/util-user-agent-browser@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.489.0.tgz#d59c3386c71ac08d658c123a1487cd6473c65627" + integrity sha512-85B9KMsuMpAZauzWQ16r52ZBAHYnznW6BVitnBglsibN7oJKn10Hggt4QGuRhvQFCxQ8YhvBl7r+vQGFO4hxIw== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/types" "^2.8.0" bowser "^2.11.0" tslib "^2.5.0" -"@aws-sdk/util-user-agent-node@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.465.0.tgz#5838e93a0f2102fb555131f40bd454707caba3f9" - integrity sha512-XsHbq7gLCiGdy6FQ7/5nGslK0ij3Iuh051djuIICvNurlds5cqKLiBe63gX3IUUwxJcrKh4xBGviQJ52KdVSeg== +"@aws-sdk/util-user-agent-node@3.489.0": + version "3.489.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.489.0.tgz#bc8f96710aadec4f5e327817cf5945c473150621" + integrity sha512-CYdkBHig8sFNc0dv11Ni9WXvZQHeI5+z77OrDHKkbidFx/V4BDTuwZw4K1vWg62pzFOEfzunJFiULRcDZWJR3w== dependencies: - "@aws-sdk/types" "3.465.0" - "@smithy/node-config-provider" "^2.1.5" - "@smithy/types" "^2.5.0" + "@aws-sdk/types" "3.489.0" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/types" "^2.8.0" tslib "^2.5.0" "@aws-sdk/util-utf8-browser@^3.0.0": @@ -677,11 +682,12 @@ dependencies: tslib "^2.3.1" -"@aws-sdk/xml-builder@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.465.0.tgz#0914bc0b9708848f1fa509dd6e474e8c691a0cb2" - integrity sha512-9TKW5ZgsReygePTnAUdvaqxr/k1HXsEz2yDnk/jTLaUeRPsd5la8fFjb6OfgYYlbEVNlxTcKzaqOdrqxpUkmyQ== +"@aws-sdk/xml-builder@3.485.0": + version "3.485.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.485.0.tgz#e1fc573d6cb6b76e0ba6a2b63bc67bc65e52c3e0" + integrity sha512-xQexPM6LINOIkf3NLFywplcbApifZRMWFN41TDWYSNgCUa5uC9fntfenw8N/HTx1n+McRCWSAFBTjDqY/2OLCQ== dependencies: + "@smithy/types" "^2.8.0" tslib "^2.5.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.13": @@ -2043,6 +2049,13 @@ ajv-formats "^2.1.1" fast-uri "^2.0.0" +"@fastify/busboy@^1.0.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-1.2.1.tgz#9c6db24a55f8b803b5222753b24fe3aea2ba9ca3" + integrity sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q== + dependencies: + text-decoding "^1.0.0" + "@fastify/busboy@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" @@ -2061,6 +2074,11 @@ resolved "https://registry.yarnpkg.com/@fastify/deepmerge/-/deepmerge-1.3.0.tgz#8116858108f0c7d9fd460d05a7d637a13fe3239a" integrity sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A== +"@fastify/error@^3.0.0": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@fastify/error/-/error-3.4.1.tgz#b14bb4cac3dd4ec614becbc643d1511331a6425c" + integrity sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ== + "@fastify/error@^3.2.0": version "3.4.0" resolved "https://registry.yarnpkg.com/@fastify/error/-/error-3.4.0.tgz#30df6601f4edce57a05ec5caaa90a28025a8554a" @@ -2091,6 +2109,18 @@ path-to-regexp "^6.1.0" reusify "^1.0.4" +"@fastify/multipart@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@fastify/multipart/-/multipart-8.1.0.tgz#92b1cd482202b469b6c08aba568f0cda6fc543ba" + integrity sha512-sRX9X4ZhAqRbe2kDvXY2NK7i6Wf1Rm2g/CjpGYYM7+Np8E6uWQXcj761j08qPfPO8PJXM+vJ7yrKbK1GPB+OeQ== + dependencies: + "@fastify/busboy" "^1.0.0" + "@fastify/deepmerge" "^1.0.0" + "@fastify/error" "^3.0.0" + fastify-plugin "^4.0.0" + secure-json-parse "^2.4.0" + stream-wormhole "^1.1.0" + "@fastify/send@^2.0.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@fastify/send/-/send-2.1.0.tgz#1aa269ccb4b0940a2dadd1f844443b15d8224ea0" @@ -2174,10 +2204,10 @@ resolved "https://registry.yarnpkg.com/@headlessui/tailwindcss/-/tailwindcss-0.2.0.tgz#2c55c98fd8eee4b4f21ec6eb35a014b840059eec" integrity sha512-fpL830Fln1SykOCboExsWr3JIVeQKieLJ3XytLe/tt1A0XzqUthOftDmjcCYLW62w7mQI7wXcoPXr3tZ9QfGxw== -"@hookform/resolvers@^3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.2.tgz#5c40f06fe8137390b071d961c66d27ee8f76f3bc" - integrity sha512-Tw+GGPnBp+5DOsSg4ek3LCPgkBOuOgS5DsDV7qsWNH9LZc433kgsWICjlsh2J9p04H2K66hsXPPb9qn9ILdUtA== +"@hookform/resolvers@^3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.4.tgz#de9b668c2835eb06892290192de6e2a5c906229b" + integrity sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ== "@humanwhocodes/config-array@^0.11.11": version "0.11.11" @@ -3179,12 +3209,12 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@smithy/abort-controller@^2.0.15": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-2.0.15.tgz#fcec9193da8b86eef1eedc3e71139a99c061db32" - integrity sha512-JkS36PIS3/UCbq/MaozzV7jECeL+BTt4R75bwY8i+4RASys4xOyUS1HsRyUNSqUXFP4QyCz5aNnh3ltuaxv+pw== +"@smithy/abort-controller@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-2.0.16.tgz#31a86748e0c55a97ead1d179040160c6fc55ba1b" + integrity sha512-4foO7738k8kM9flMHu3VLabqu7nPgvIj8TB909S0CnKx0YZz/dcDH3pZ/4JHdatfxlZdKF1JWOYCw9+v3HVVsw== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" "@smithy/chunked-blob-reader-native@^2.0.1": @@ -3202,18 +3232,32 @@ dependencies: tslib "^2.5.0" -"@smithy/config-resolver@^2.0.18", "@smithy/config-resolver@^2.0.20": - version "2.0.20" - resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-2.0.20.tgz#ffc50fec71430480969ff6b4e69297dd52573b5c" - integrity sha512-aV4FQW7PGd6BSDmSvPnTDi9IirTzZOC9hroindiNO/DP8cPQvCKe3dLs+5fGxcNm+rnVXFMRQdGjnWiBAMadiw== +"@smithy/config-resolver@^2.0.23": + version "2.0.23" + resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-2.0.23.tgz#45496bea277c00d52efcdf88a5f483b3d6a7e62d" + integrity sha512-XakUqgtP2YY8Mi+Nlif5BiqJgWdvfxJafSpOSQeCOMizu+PUhE4fBQSy6xFcR+eInrwVadaABNxoJyGUMn15ew== dependencies: - "@smithy/node-config-provider" "^2.1.7" - "@smithy/types" "^2.7.0" - "@smithy/util-config-provider" "^2.0.0" - "@smithy/util-middleware" "^2.0.8" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/types" "^2.8.0" + "@smithy/util-config-provider" "^2.1.0" + "@smithy/util-middleware" "^2.0.9" + tslib "^2.5.0" + +"@smithy/core@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-1.2.2.tgz#9e10d6055f2a05c2c677737b9b0c4f7507a80c75" + integrity sha512-uLjrskLT+mWb0emTR5QaiAIxVEU7ndpptDaVDrTwwhD+RjvHhjIiGQ3YL5jKk1a5VSDQUA2RGkXvJ6XKRcz6Dg== + dependencies: + "@smithy/middleware-endpoint" "^2.3.0" + "@smithy/middleware-retry" "^2.0.26" + "@smithy/middleware-serde" "^2.0.16" + "@smithy/protocol-http" "^3.0.12" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" + "@smithy/util-middleware" "^2.0.9" tslib "^2.5.0" -"@smithy/credential-provider-imds@^2.0.0", "@smithy/credential-provider-imds@^2.1.3": +"@smithy/credential-provider-imds@^2.0.0": version "2.1.3" resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.3.tgz#5c534a964b7c730269ef632b11216617b61ab0b4" integrity sha512-nbLFCeicw2FuvKj6TeIA1jbaTEqepNUWXOyRUhU3IJNn/N8QMGvZRIlFYbUEnuxpZYDhNuOWLph6R04hNJ7A2g== @@ -3224,6 +3268,17 @@ "@smithy/url-parser" "^2.0.15" tslib "^2.5.0" +"@smithy/credential-provider-imds@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.5.tgz#18e238067c0d9c5598a12fabb13ce1545554e691" + integrity sha512-VfvE6Wg1MUWwpTZFBnUD7zxvPhLY8jlHCzu6bCjlIYoWgXCDzZAML76IlZUEf45nib3rjehnFgg0s1rgsuN/bg== + dependencies: + "@smithy/node-config-provider" "^2.1.9" + "@smithy/property-provider" "^2.0.17" + "@smithy/types" "^2.8.0" + "@smithy/url-parser" "^2.0.16" + tslib "^2.5.0" + "@smithy/eventstream-codec@^2.0.15": version "2.0.15" resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-2.0.15.tgz#733e638fd38e7e264bc0429dbda139bab950bd25" @@ -3234,87 +3289,97 @@ "@smithy/util-hex-encoding" "^2.0.0" tslib "^2.5.0" -"@smithy/eventstream-serde-browser@^2.0.13": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.0.15.tgz#f62c891e6f8ad59f552a92d8aa14eb6b4541d418" - integrity sha512-WiFG5N9j3jmS5P0z5Xev6dO0c3lf7EJYC2Ncb0xDnWFvShwXNn741AF71ABr5EcZw8F4rQma0362MMjAwJeZog== +"@smithy/eventstream-codec@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-2.0.16.tgz#c213e25d7f05f1e6b40675835a141d23d3f3b6ca" + integrity sha512-umYh5pdCE9GHgiMAH49zu9wXWZKNHHdKPm/lK22WYISTjqu29SepmpWNmPiBLy/yUu4HFEGJHIFrDWhbDlApaw== dependencies: - "@smithy/eventstream-serde-universal" "^2.0.15" - "@smithy/types" "^2.7.0" + "@aws-crypto/crc32" "3.0.0" + "@smithy/types" "^2.8.0" + "@smithy/util-hex-encoding" "^2.0.0" tslib "^2.5.0" -"@smithy/eventstream-serde-config-resolver@^2.0.13": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.15.tgz#50e98c59aeb31a0702bad5dfab4009a15fc8b3bf" - integrity sha512-o65d2LRjgCbWYH+VVNlWXtmsI231SO99ZTOL4UuIPa6WTjbSHWtlXvUcJG9libhEKWmEV9DIUiH2IqyPWi7ubA== +"@smithy/eventstream-serde-browser@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.0.16.tgz#f265c3605a861d7f4feaa8657f475c8da7c9f45e" + integrity sha512-W+BdiN728R57KuZOcG0GczpIOEFf8S5RP/OdVH7T3FMCy8HU2bBU0vB5xZZR5c00VRdoeWrohNv3XlHoZuGRoA== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/eventstream-serde-universal" "^2.0.16" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/eventstream-serde-node@^2.0.13": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.0.15.tgz#8be1bd024048adcff4ccbb723c55fc42ce582d33" - integrity sha512-9OOXiIhHq1VeOG6xdHkn2ZayfMYM3vzdUTV3zhcCnt+tMqA3BJK3XXTJFRR2BV28rtRM778DzqbBTf+hqwQPTg== +"@smithy/eventstream-serde-config-resolver@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.16.tgz#0a6fd6312605de6f0505d3fdec4235f7c4388413" + integrity sha512-8qrE4nh+Tg6m1SMFK8vlzoK+8bUFTlIhXidmmQfASMninXW3Iu0T0bI4YcIk4nLznHZdybQ0qGydIanvVZxzVg== dependencies: - "@smithy/eventstream-serde-universal" "^2.0.15" - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/eventstream-serde-universal@^2.0.15": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.0.15.tgz#85cdff39abc630cb18b4d333913b7120651771ca" - integrity sha512-dP8AQp/pXlWBjvL0TaPBJC3rM0GoYv7O0Uim8d/7UKZ2Wo13bFI3/BhQfY/1DeiP1m23iCHFNFtOQxfQNBB8rQ== +"@smithy/eventstream-serde-node@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.0.16.tgz#24bca6ab0dbf7d81b42bc3435db42e36385c7480" + integrity sha512-NRNQuOa6mQdFSkqzY0IV37swHWx0SEoKxFtUfdZvfv0AVQPlSw4N7E3kcRSCpnHBr1kCuWWirdDlWcjWuD81MA== dependencies: - "@smithy/eventstream-codec" "^2.0.15" - "@smithy/types" "^2.7.0" + "@smithy/eventstream-serde-universal" "^2.0.16" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/fetch-http-handler@^2.2.6", "@smithy/fetch-http-handler@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-2.3.1.tgz#aa055db5bf4d78acec97abe6ef24283fa2c18430" - integrity sha512-6MNk16fqb8EwcYY8O8WxB3ArFkLZ2XppsSNo1h7SQcFdDDwIumiJeO6wRzm7iB68xvsOQzsdQKbdtTieS3hfSQ== +"@smithy/eventstream-serde-universal@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.0.16.tgz#e554aa59d0c3bdd6f8b9eae9b2e3d6c6ae702611" + integrity sha512-ZyLnGaYQMLc75j9kKEVMJ3X6bdBE9qWxhZdTXM5RIltuytxJC3FaOhawBxjE+IL1enmWSIohHGZCm/pLwEliQA== dependencies: - "@smithy/protocol-http" "^3.0.11" - "@smithy/querystring-builder" "^2.0.15" - "@smithy/types" "^2.7.0" + "@smithy/eventstream-codec" "^2.0.16" + "@smithy/types" "^2.8.0" + tslib "^2.5.0" + +"@smithy/fetch-http-handler@^2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-2.3.2.tgz#930ee473b2a43d0bcf62c3b659f38122442ad514" + integrity sha512-O9R/OlnAOTsnysuSDjt0v2q6DcSvCz5cCFC/CFAWWcLyBwJDeFyGTCTszgpQTb19+Fi8uRwZE5/3ziAQBFeDMQ== + dependencies: + "@smithy/protocol-http" "^3.0.12" + "@smithy/querystring-builder" "^2.0.16" + "@smithy/types" "^2.8.0" "@smithy/util-base64" "^2.0.1" tslib "^2.5.0" -"@smithy/hash-blob-browser@^2.0.14": - version "2.0.16" - resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-2.0.16.tgz#6cd3686e79f3c8d96a129076073bf20d06293152" - integrity sha512-cSYRi05LA7DZDwjB1HL0BP8B56eUNNeLglVH147QTXFyuXJq/7erAIiLRfsyXB8+GfFHkSS5BHbc76a7k/AYPA== +"@smithy/hash-blob-browser@^2.0.17": + version "2.0.17" + resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-2.0.17.tgz#4249f1fba27cb7c40dbf1fa38a31e61dbf9149b9" + integrity sha512-/mPpv1sRiRDdjO4zZuO8be6eeabmg5AVgKDfnmmqkpBtRyMGSJb968fjRuHt+FRAsIGywgIKJFmUUAYjhsi1oQ== dependencies: "@smithy/chunked-blob-reader" "^2.0.0" "@smithy/chunked-blob-reader-native" "^2.0.1" - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/hash-node@^2.0.15": - version "2.0.17" - resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-2.0.17.tgz#9ce5e3f137143e3658759d31a16e068ef94a14fc" - integrity sha512-Il6WuBcI1nD+e2DM7tTADMf01wEPGK8PAhz4D+YmDUVaoBqlA+CaH2uDJhiySifmuKBZj748IfygXty81znKhw== +"@smithy/hash-node@^2.0.18": + version "2.0.18" + resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-2.0.18.tgz#4bf4ec392b5d6715426338b6828e6b25cd939bd5" + integrity sha512-gN2JFvAgnZCyDN9rJgcejfpK0uPPJrSortVVVVWsru9whS7eQey6+gj2eM5ln2i6rHNntIXzal1Fm9XOPuoaKA== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" "@smithy/util-buffer-from" "^2.0.0" "@smithy/util-utf8" "^2.0.2" tslib "^2.5.0" -"@smithy/hash-stream-node@^2.0.15": - version "2.0.17" - resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-2.0.17.tgz#90375ed9c1a586118433c925a61d39b5555bf284" - integrity sha512-ey8DtnATzp1mOXgS7rqMwSmAki6iJA+jgNucKcxRkhMB1rrICfHg+rhmIF50iLPDHUhTcS5pBMOrLzzpZftvNQ== +"@smithy/hash-stream-node@^2.0.18": + version "2.0.18" + resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-2.0.18.tgz#6f7b214e785b8cf445e5095f9a9b9b38e689e7ce" + integrity sha512-OuFk+ITpv8CtxGjQcS8GA04faNycu9UMm6YobvQzjeEoXZ0dLF6sRfuzD+3S8RHPKpTyLuXtKG1+GiJycZ5TcA== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" "@smithy/util-utf8" "^2.0.2" tslib "^2.5.0" -"@smithy/invalid-dependency@^2.0.13": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-2.0.15.tgz#7653490047bf0ab6042fb812adfbcce857aa2d06" - integrity sha512-dlEKBFFwVfzA5QroHlBS94NpgYjXhwN/bFfun+7w3rgxNvVy79SK0w05iGc7UAeC5t+D7gBxrzdnD6hreZnDVQ== +"@smithy/invalid-dependency@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-2.0.16.tgz#b32a6284ef4ce48129d00a6d63f977ec3e05befb" + integrity sha512-apEHakT/kmpNo1VFHP4W/cjfeP9U0x5qvfsLJubgp7UM/gq4qYp0GbqdE7QhsjUaYvEnrftRqs7+YrtWreV0wA== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" "@smithy/is-array-buffer@^2.0.0": @@ -3324,69 +3389,69 @@ dependencies: tslib "^2.5.0" -"@smithy/md5-js@^2.0.15": - version "2.0.17" - resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-2.0.17.tgz#784c02da6cee539f5af0e45b1eaf9beb10ed8ad6" - integrity sha512-jmISTCnEkOnm2oCNx/rMkvBT/eQh3aA6nktevkzbmn/VYqYEuc5Z2n5sTTqsciMSO01Lvf56wG1A4twDqovYeQ== +"@smithy/md5-js@^2.0.18": + version "2.0.18" + resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-2.0.18.tgz#85f26fc83e75b440144341292cbb75ddf69dd0de" + integrity sha512-bHwZ8/m6RbERQdVW5rJ2LzeW8qxfXv6Q/S7Fiudhso4pWRrksqLx3nsGZw7bmqqfN4zLqkxydxSa9+4c7s5zxg== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" "@smithy/util-utf8" "^2.0.2" tslib "^2.5.0" -"@smithy/middleware-content-length@^2.0.15": - version "2.0.17" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-2.0.17.tgz#13479173a15d1cd4224e3e21071a27c66a74b653" - integrity sha512-OyadvMcKC7lFXTNBa8/foEv7jOaqshQZkjWS9coEXPRZnNnihU/Ls+8ZuJwGNCOrN2WxXZFmDWhegbnM4vak8w== +"@smithy/middleware-content-length@^2.0.18": + version "2.0.18" + resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-2.0.18.tgz#a3b13beb300290f5d0d48ace0f818e44261356fa" + integrity sha512-ZJ9uKPTfxYheTKSKYB+GCvcj+izw9WGzRLhjn8n254q0jWLojUzn7Vw0l4R/Gq7Wdpf/qmk/ptD+6CCXHNVCaw== dependencies: - "@smithy/protocol-http" "^3.0.11" - "@smithy/types" "^2.7.0" + "@smithy/protocol-http" "^3.0.12" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/middleware-endpoint@^2.2.0": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.2.tgz#83481e1b2a39a643301005d78b991f95358d5db1" - integrity sha512-fgB9bXoWWNBAArzPIx8ipwVQCprltbQY3X9zLZAJ3mRRgzXZQUN1ecO/cF1S87jNJ2vkXf/mftJIPFqFUJCLGg== - dependencies: - "@smithy/middleware-serde" "^2.0.15" - "@smithy/node-config-provider" "^2.1.7" - "@smithy/shared-ini-file-loader" "^2.2.6" - "@smithy/types" "^2.7.0" - "@smithy/url-parser" "^2.0.15" - "@smithy/util-middleware" "^2.0.8" +"@smithy/middleware-endpoint@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.3.0.tgz#135c30f38087ba52e692a73212854d0809ce1168" + integrity sha512-VsOAG2YQ8ykjSmKO+CIXdJBIWFo6AAvG6Iw95BakBTqk66/4BI7XyqLevoNSq/lZ6NgZv24sLmrcIN+fLDWBCg== + dependencies: + "@smithy/middleware-serde" "^2.0.16" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/shared-ini-file-loader" "^2.2.8" + "@smithy/types" "^2.8.0" + "@smithy/url-parser" "^2.0.16" + "@smithy/util-middleware" "^2.0.9" tslib "^2.5.0" -"@smithy/middleware-retry@^2.0.20": - version "2.0.23" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.0.23.tgz#3fa8365a98fa39290a8a7ebc716a091509a7a8f2" - integrity sha512-8rug8AVLa9usaNcCI9voQ0UpKgzYBVVjUrIQMnYgpOPq1t8Tbm8Fa2Qe9J3K+GlB6EHpn82yWBIXfyJ+nQ0YHw== - dependencies: - "@smithy/node-config-provider" "^2.1.7" - "@smithy/protocol-http" "^3.0.11" - "@smithy/service-error-classification" "^2.0.8" - "@smithy/smithy-client" "^2.1.18" - "@smithy/types" "^2.7.0" - "@smithy/util-middleware" "^2.0.8" - "@smithy/util-retry" "^2.0.8" +"@smithy/middleware-retry@^2.0.26": + version "2.0.26" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.0.26.tgz#894cf86b0f5bc742e09c52df8df4c2941fbd9883" + integrity sha512-Qzpxo0U5jfNiq9iD38U3e2bheXwvTEX4eue9xruIvEgh+UKq6dKuGqcB66oBDV7TD/mfoJi9Q/VmaiqwWbEp7A== + dependencies: + "@smithy/node-config-provider" "^2.1.9" + "@smithy/protocol-http" "^3.0.12" + "@smithy/service-error-classification" "^2.0.9" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" + "@smithy/util-middleware" "^2.0.9" + "@smithy/util-retry" "^2.0.9" tslib "^2.5.0" uuid "^8.3.2" -"@smithy/middleware-serde@^2.0.13", "@smithy/middleware-serde@^2.0.15": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-2.0.15.tgz#9deac4daad1f2a60d5c4e7097658f9ae2eb0a33f" - integrity sha512-FOZRFk/zN4AT4wzGuBY+39XWe+ZnCFd0gZtyw3f9Okn2CJPixl9GyWe98TIaljeZdqWkgrzGyPre20AcW2UMHQ== +"@smithy/middleware-serde@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-2.0.16.tgz#a127e7fa48c0106bd7a81e1ea27e7193cb08e701" + integrity sha512-5EAd4t30pcc4M8TSSGq7q/x5IKrxfXR5+SrU4bgxNy7RPHQo2PSWBUco9C+D9Tfqp/JZvprRpK42dnupZafk2g== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/middleware-stack@^2.0.7", "@smithy/middleware-stack@^2.0.9": - version "2.0.9" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-2.0.9.tgz#60e51697c74258fac087bc739d940f524921a15f" - integrity sha512-bCB5dUtGQ5wh7QNL2ELxmDc6g7ih7jWU3Kx6MYH1h4mZbv9xL3WyhKHojRltThCB1arLPyTUFDi+x6fB/oabtA== +"@smithy/middleware-stack@^2.0.10": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-2.0.10.tgz#fb7c660dcc921b61a77e6cb39ed3eada9ed38585" + integrity sha512-I2rbxctNq9FAPPEcuA1ntZxkTKOPQFy7YBPOaD/MLg1zCvzv21CoNxR0py6J8ZVC35l4qE4nhxB0f7TF5/+Ldw== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/node-config-provider@^2.1.5", "@smithy/node-config-provider@^2.1.7": +"@smithy/node-config-provider@^2.1.7": version "2.1.7" resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-2.1.7.tgz#8a4c690d815fa1c1aabc05b1638a515eca652db8" integrity sha512-jusNoTInSrrbqvqn+14WggfT41EUT5tZgLlkmmESsYmHkKox2HGehnciyWBF2zMJQNkye59NyDtOF7x5viY3cQ== @@ -3396,15 +3461,25 @@ "@smithy/types" "^2.7.0" tslib "^2.5.0" -"@smithy/node-http-handler@^2.1.9", "@smithy/node-http-handler@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-2.2.1.tgz#23f6540e565edcae8c558a854fffde3d003451c0" - integrity sha512-8iAKQrC8+VFHPAT8pg4/j6hlsTQh+NKOWlctJBrYtQa4ExcxX7aSg3vdQ2XLoYwJotFUurg/NLqFCmZaPRrogw== +"@smithy/node-config-provider@^2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-2.1.9.tgz#2e9e5ee7c4412be6696a74b26f9ed2a66e2a5fb4" + integrity sha512-tUyW/9xrRy+s7RXkmQhgYkAPMpTIF8izK4orhHjNFEKR3QZiOCbWB546Y8iB/Fpbm3O9+q0Af9rpywLKJOwtaQ== dependencies: - "@smithy/abort-controller" "^2.0.15" - "@smithy/protocol-http" "^3.0.11" - "@smithy/querystring-builder" "^2.0.15" - "@smithy/types" "^2.7.0" + "@smithy/property-provider" "^2.0.17" + "@smithy/shared-ini-file-loader" "^2.2.8" + "@smithy/types" "^2.8.0" + tslib "^2.5.0" + +"@smithy/node-http-handler@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-2.2.2.tgz#f9f8cd49f270bc50a0de8a4587bbdaae1c7c4e80" + integrity sha512-XO58TO/Eul/IBQKFKaaBtXJi0ItEQQCT+NI4IiKHCY/4KtqaUT6y/wC1EvDqlA9cP7Dyjdj7FdPs4DyynH3u7g== + dependencies: + "@smithy/abort-controller" "^2.0.16" + "@smithy/protocol-http" "^3.0.12" + "@smithy/querystring-builder" "^2.0.16" + "@smithy/types" "^2.8.0" tslib "^2.5.0" "@smithy/property-provider@^2.0.0", "@smithy/property-provider@^2.0.16": @@ -3415,20 +3490,28 @@ "@smithy/types" "^2.7.0" tslib "^2.5.0" -"@smithy/protocol-http@^3.0.11", "@smithy/protocol-http@^3.0.9": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-3.0.11.tgz#a9ea712fe7cc3375378ac68d9168a7b6cd0b6f65" - integrity sha512-3ziB8fHuXIRamV/akp/sqiWmNPR6X+9SB8Xxnozzj+Nq7hSpyKdFHd1FLpBkgfGFUTzzcBJQlDZPSyxzmdcx5A== +"@smithy/property-provider@^2.0.17": + version "2.0.17" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-2.0.17.tgz#288475021613649811dc79a9fab4894be01cd069" + integrity sha512-+VkeZbVu7qtQ2DjI48Qwaf9fPOr3gZIwxQpuLJgRRSkWsdSvmaTCxI3gzRFKePB63Ts9r4yjn4HkxSCSkdWmcQ== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/querystring-builder@^2.0.13", "@smithy/querystring-builder@^2.0.15": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-2.0.15.tgz#aa8c889bcaef274b8345be4ddabae3bfedf2cf33" - integrity sha512-e1q85aT6HutvouOdN+dMsN0jcdshp50PSCvxDvo6aIM57LqeXimjfONUEgfqQ4IFpYWAtVixptyIRE5frMp/2A== +"@smithy/protocol-http@^3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-3.0.12.tgz#9f606efd191593f6dbde58fa822465b92b8afbca" + integrity sha512-Xz4iaqLiaBfbQpB9Hgi3VcZYbP7xRDXYhd8XWChh4v94uw7qwmvlxdU5yxzfm6ACJM66phHrTbS5TVvj5uQ72w== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" + tslib "^2.5.0" + +"@smithy/querystring-builder@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-2.0.16.tgz#1a9a02b1fb938688cdab5e585cb7c62c8054bc41" + integrity sha512-Q/GsJT0C0mijXMRs7YhZLLCP5FcuC4797lYjKQkME5CZohnLC4bEhylAd2QcD3gbMKNjCw8+T2I27WKiV/wToA== + dependencies: + "@smithy/types" "^2.8.0" "@smithy/util-uri-escape" "^2.0.0" tslib "^2.5.0" @@ -3440,12 +3523,20 @@ "@smithy/types" "^2.7.0" tslib "^2.5.0" -"@smithy/service-error-classification@^2.0.8": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-2.0.8.tgz#c9e421312a2def84da025c5efe6de06679c5be95" - integrity sha512-jCw9+005im8tsfYvwwSc4TTvd29kXRFkH9peQBg5R/4DD03ieGm6v6Hpv9nIAh98GwgYg1KrztcINC1s4o7/hg== +"@smithy/querystring-parser@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-2.0.16.tgz#90d9589539ffe8fb4864c8bf6f1f1c9def962a40" + integrity sha512-c4ueAuL6BDYKWpkubjrQthZKoC3L5kql5O++ovekNxiexRXTlLIVlCR4q3KziOktLIw66EU9SQljPXd/oN6Okg== dependencies: - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" + tslib "^2.5.0" + +"@smithy/service-error-classification@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-2.0.9.tgz#4459433f6727f1b7e953a9bab189672b3b157224" + integrity sha512-0K+8GvtwI7VkGmmInPydM2XZyBfIqLIbfR7mDQ+oPiz8mIinuHbV6sxOLdvX1Jv/myk7XTK9orgt3tuEpBu/zg== + dependencies: + "@smithy/types" "^2.8.0" "@smithy/shared-ini-file-loader@^2.0.6", "@smithy/shared-ini-file-loader@^2.2.6": version "2.2.6" @@ -3455,6 +3546,14 @@ "@smithy/types" "^2.7.0" tslib "^2.5.0" +"@smithy/shared-ini-file-loader@^2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.8.tgz#b5fa153d4920a3c740215c60ad1667972d67a164" + integrity sha512-E62byatbwSWrtq9RJ7xN40tqrRKDGrEL4EluyNpaIDvfvet06a/QC58oHw2FgVaEgkj0tXZPjZaKrhPfpoU0qw== + dependencies: + "@smithy/types" "^2.8.0" + tslib "^2.5.0" + "@smithy/signature-v4@^2.0.0": version "2.0.17" resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.0.17.tgz#3ce17d8143f18670ca9bd5f99738dfadb3a7f3fc" @@ -3469,14 +3568,16 @@ "@smithy/util-utf8" "^2.0.2" tslib "^2.5.0" -"@smithy/smithy-client@^2.1.15", "@smithy/smithy-client@^2.1.18": - version "2.1.18" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.1.18.tgz#f8ce2c0e9614f207256ddcd992403aff40750546" - integrity sha512-7FqdbaJiVaHJDD9IfDhmzhSDbpjyx+ZsfdYuOpDJF09rl8qlIAIlZNoSaflKrQ3cEXZN2YxGPaNWGhbYimyIRQ== - dependencies: - "@smithy/middleware-stack" "^2.0.9" - "@smithy/types" "^2.7.0" - "@smithy/util-stream" "^2.0.23" +"@smithy/smithy-client@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.2.1.tgz#ed1aa11f36dae2ca9b3eabcbc498bcc96d79fdfd" + integrity sha512-SpD7FLK92XV2fon2hMotaNDa2w5VAy5/uVjP9WFmjGSgWM8pTPVkHcDl1yFs5Z8LYbij0FSz+DbCBK6i+uXXUA== + dependencies: + "@smithy/middleware-endpoint" "^2.3.0" + "@smithy/middleware-stack" "^2.0.10" + "@smithy/protocol-http" "^3.0.12" + "@smithy/types" "^2.8.0" + "@smithy/util-stream" "^2.0.24" tslib "^2.5.0" "@smithy/types@^2.5.0", "@smithy/types@^2.7.0": @@ -3486,7 +3587,14 @@ dependencies: tslib "^2.5.0" -"@smithy/url-parser@^2.0.13", "@smithy/url-parser@^2.0.15": +"@smithy/types@^2.8.0": + version "2.8.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.8.0.tgz#bdbaa0a54c9c3538d6c763c6f32d3e4f76fe0df9" + integrity sha512-h9sz24cFgt/W1Re22OlhQKmUZkNh244ApgRsUDYinqF8R+QgcsBIX344u2j61TPshsTz3CvL6HYU1DnQdsSrHA== + dependencies: + tslib "^2.5.0" + +"@smithy/url-parser@^2.0.15": version "2.0.15" resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-2.0.15.tgz#878d9b61f9eac8834cb611cf1a8a0e5d9a48038c" integrity sha512-sADUncUj9rNbOTrdDGm4EXlUs0eQ9dyEo+V74PJoULY4jSQxS+9gwEgsPYyiu8PUOv16JC/MpHonOgqP/IEDZA== @@ -3495,6 +3603,15 @@ "@smithy/types" "^2.7.0" tslib "^2.5.0" +"@smithy/url-parser@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-2.0.16.tgz#25f860effe465acbbe61beb69b6def052878ee58" + integrity sha512-Wfz5WqAoRT91TjRy1JeLR0fXtkIXHGsMbgzKFTx7E68SrZ55TB8xoG+vm11Ru4gheFTMXjAjwAxv1jQdC+pAQA== + dependencies: + "@smithy/querystring-parser" "^2.0.16" + "@smithy/types" "^2.8.0" + tslib "^2.5.0" + "@smithy/util-base64@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-2.0.1.tgz#57f782dafc187eddea7c8a1ff2a7c188ed1a02c4" @@ -3503,7 +3620,7 @@ "@smithy/util-buffer-from" "^2.0.0" tslib "^2.5.0" -"@smithy/util-body-length-browser@^2.0.0": +"@smithy/util-body-length-browser@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.1.tgz#424485cc81c640d18c17c683e0e6edb57e8e2ab9" integrity sha512-NXYp3ttgUlwkaug4bjBzJ5+yIbUbUx8VsSLuHZROQpoik+gRkIBeEG9MPVYfvPNpuXb/puqodeeUXcKFe7BLOQ== @@ -3525,44 +3642,44 @@ "@smithy/is-array-buffer" "^2.0.0" tslib "^2.5.0" -"@smithy/util-config-provider@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz#4dd6a793605559d94267312fd06d0f58784b4c38" - integrity sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg== +"@smithy/util-config-provider@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-2.1.0.tgz#c733a862892772aaeb373a3e8af5182556da0ef9" + integrity sha512-S6V0JvvhQgFSGLcJeT1CBsaTR03MM8qTuxMH9WPCCddlSo2W0V5jIHimHtIQALMLEDPGQ0ROSRr/dU0O+mxiQg== dependencies: tslib "^2.5.0" -"@smithy/util-defaults-mode-browser@^2.0.19": - version "2.0.22" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.22.tgz#8ef8c36b8c3c2f98f7a62278c3c684d659134269" - integrity sha512-qcF20IHHH96FlktvBRICDXDhLPtpVmtksHmqNGtotb9B0DYWXsC6jWXrkhrrwF7tH26nj+npVTqh9isiFV1gdA== +"@smithy/util-defaults-mode-browser@^2.0.24": + version "2.0.24" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.24.tgz#bfa8fa441db0d0d309c11d091ca9746f2b8e4797" + integrity sha512-TsP5mBuLgO2C21+laNG2nHYZEyUdkbGURv2tHvSuQQxLz952MegX95uwdxOY2jR2H4GoKuVRfdJq7w4eIjGYeg== dependencies: - "@smithy/property-provider" "^2.0.16" - "@smithy/smithy-client" "^2.1.18" - "@smithy/types" "^2.7.0" + "@smithy/property-provider" "^2.0.17" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" bowser "^2.11.0" tslib "^2.5.0" -"@smithy/util-defaults-mode-node@^2.0.25": - version "2.0.28" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.28.tgz#592a370e2d1472a1fbe81e5444deb1a50b512a0b" - integrity sha512-viVrlLz66fRvjzXpTFxv3bXlLUVsNcxRE5yHKAk3fVc56DuuWlUsgsRs87ENl8tjuZP04fuKs8GGvkRk/1fCbQ== - dependencies: - "@smithy/config-resolver" "^2.0.20" - "@smithy/credential-provider-imds" "^2.1.3" - "@smithy/node-config-provider" "^2.1.7" - "@smithy/property-provider" "^2.0.16" - "@smithy/smithy-client" "^2.1.18" - "@smithy/types" "^2.7.0" +"@smithy/util-defaults-mode-node@^2.0.32": + version "2.0.32" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.32.tgz#a0665ef2feed845de7825059072e312e22393698" + integrity sha512-d0S33dXA2cq1NyorVMroMrEtqKMr3MlyLITcfTBf9pXiigYiPMOtbSI7czHIfDbuVuM89Cg0urAgpt73QV9mPQ== + dependencies: + "@smithy/config-resolver" "^2.0.23" + "@smithy/credential-provider-imds" "^2.1.5" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/property-provider" "^2.0.17" + "@smithy/smithy-client" "^2.2.1" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/util-endpoints@^1.0.4": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-1.0.6.tgz#e8eff172b4fa4174c68dac45c57ddb5ea6a65566" - integrity sha512-90vy1Q0Z4pNcXUX9TrG50Y5zi0k8ONYTHZ/7KDliHOWUtdGH2a61cVMq/zIvt0Qlunsk8vshk4+DZLMEwIm92Q== +"@smithy/util-endpoints@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-1.0.8.tgz#10ec9b228e96fc67b42ed06dabdab118a5869532" + integrity sha512-l8zVuyZZ61IzZBYp5NWvsAhbaAjYkt0xg9R4xUASkg5SEeTT2meHOJwJHctKMFUXe4QZbn9fR2MaBYjP2119+w== dependencies: - "@smithy/node-config-provider" "^2.1.7" - "@smithy/types" "^2.7.0" + "@smithy/node-config-provider" "^2.1.9" + "@smithy/types" "^2.8.0" tslib "^2.5.0" "@smithy/util-hex-encoding@^2.0.0": @@ -3572,7 +3689,7 @@ dependencies: tslib "^2.5.0" -"@smithy/util-middleware@^2.0.6", "@smithy/util-middleware@^2.0.8": +"@smithy/util-middleware@^2.0.8": version "2.0.8" resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-2.0.8.tgz#2ec1da1190d09b69512ce0248ebd5e819e3c8a92" integrity sha512-qkvqQjM8fRGGA8P2ydWylMhenCDP8VlkPn8kiNuFEaFz9xnUKC2irfqsBSJrfrOB9Qt6pQsI58r3zvvumhFMkw== @@ -3580,23 +3697,31 @@ "@smithy/types" "^2.7.0" tslib "^2.5.0" -"@smithy/util-retry@^2.0.6", "@smithy/util-retry@^2.0.8": - version "2.0.8" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-2.0.8.tgz#61f8db11e4fe60975cb9fb2eada173f5024a06f3" - integrity sha512-cQTPnVaVFMjjS6cb44WV2yXtHVyXDC5icKyIbejMarJEApYeJWpBU3LINTxHqp/tyLI+MZOUdosr2mZ3sdziNg== +"@smithy/util-middleware@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-2.0.9.tgz#54a372fa723ace66046cdf91439fb1648a246d5c" + integrity sha512-PnCnBJ07noMX1lMDTEefmxSlusWJUiLfrme++MfK5TD0xz8NYmakgoXy5zkF/16zKGmiwOeKAztWT/Vjk1KRIQ== dependencies: - "@smithy/service-error-classification" "^2.0.8" - "@smithy/types" "^2.7.0" + "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@smithy/util-stream@^2.0.20", "@smithy/util-stream@^2.0.23": - version "2.0.23" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-2.0.23.tgz#468ad29913d091092317cfea2d8ac5b866326a07" - integrity sha512-OJMWq99LAZJUzUwTk+00plyxX3ESktBaGPhqNIEVab+53gLULiWN9B/8bRABLg0K6R6Xg4t80uRdhk3B/LZqMQ== +"@smithy/util-retry@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-2.0.9.tgz#ef6d6e41bcc5df330b76cca913d5e637c70497fc" + integrity sha512-46BFWe9RqB6g7f4mxm3W3HlqknqQQmWHKlhoqSFZuGNuiDU5KqmpebMbvC3tjTlUkqn4xa2Z7s3Hwb0HNs5scw== dependencies: - "@smithy/fetch-http-handler" "^2.3.1" - "@smithy/node-http-handler" "^2.2.1" - "@smithy/types" "^2.7.0" + "@smithy/service-error-classification" "^2.0.9" + "@smithy/types" "^2.8.0" + tslib "^2.5.0" + +"@smithy/util-stream@^2.0.24": + version "2.0.24" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-2.0.24.tgz#fa896c8df828ce7758963b758c1f374407d812be" + integrity sha512-hRpbcRrOxDriMVmbya+Mv77VZVupxRAsfxVDKS54XuiURhdiwCUXJP0X1iJhHinuUf6n8pBF0MkG9C8VooMnWw== + dependencies: + "@smithy/fetch-http-handler" "^2.3.2" + "@smithy/node-http-handler" "^2.2.2" + "@smithy/types" "^2.8.0" "@smithy/util-base64" "^2.0.1" "@smithy/util-buffer-from" "^2.0.0" "@smithy/util-hex-encoding" "^2.0.0" @@ -3618,13 +3743,13 @@ "@smithy/util-buffer-from" "^2.0.0" tslib "^2.5.0" -"@smithy/util-waiter@^2.0.13": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-2.0.15.tgz#b02a42bf1b82f07973d1756a0ee10fafa1fbf58e" - integrity sha512-9Y+btzzB7MhLADW7xgD6SjvmoYaRkrb/9SCbNGmNdfO47v38rxb90IGXyDtAK0Shl9bMthTmLgjlfYc+vtz2Qw== +"@smithy/util-waiter@^2.0.16": + version "2.0.16" + resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-2.0.16.tgz#3065566dd81951e24d843979ed1e6278794a955c" + integrity sha512-5i4YONHQ6HoUWDd+X0frpxTXxSXgJhUFl+z0iMy/zpUmVeCQY2or3Vss6DzHKKMMQL4pmVHpQm9WayHDorFdZg== dependencies: - "@smithy/abort-controller" "^2.0.15" - "@smithy/types" "^2.7.0" + "@smithy/abort-controller" "^2.0.16" + "@smithy/types" "^2.8.0" tslib "^2.5.0" "@sqltools/formatter@^1.2.5": @@ -7821,10 +7946,10 @@ fast-csv@^4.3.1: "@fast-csv/format" "4.3.5" "@fast-csv/parse" "4.3.6" -fast-csv@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-5.0.0.tgz#74b2c0ada46909dc35d341d1c19ee67d9be06a8a" - integrity sha512-CEwsCv2W+0AvlZRfS4rpagcP2gpMbMjbkFOn9SZ5T6F8gvl040fwPf7/8Rx9/Pxz3NQWbHYOsRFhK/MH/xjaaQ== +fast-csv@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-5.0.1.tgz#2beaa2437ea83bd335cc3a39a41e69d9936f3987" + integrity sha512-Q43zC4NdQD5MAWOVQOF8KA+D6ddvTJjX2ib8zqysm74jZhtk6+dc8C75/OqRV6Y9CLc4kgvbC3PLG8YL4YZfgw== dependencies: "@fast-csv/format" "5.0.0" "@fast-csv/parse" "5.0.0" @@ -8804,10 +8929,10 @@ i18next-fs-backend@^2.1.5: resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.2.0.tgz#016c865344632a666ea80653deae466fbfa6042c" integrity sha512-VOPHhdDX0M/csRqEw+9Ectpf6wvTIg1MZDfAHxc3JKnAlJz7fcZSAKAeyDohOq0xuLx57esYpJopIvBaRb0Bag== -i18next@^23.7.18: - version "23.7.18" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.7.18.tgz#4f219e3702b08342c5519bc2cda7d09c67edebb9" - integrity sha512-b9N2KjRCYQNlUvE1Kc83g8knyUkL5NiZQOp9BsTR/v/LXk6Fzz+doOzTg2/826XK28mCgBkYLNAtixjE58qpCw== +i18next@^23.9.0: + version "23.9.0" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.9.0.tgz#659cfcbf51a20158bb094d17a85f3c583d473901" + integrity sha512-f3MUciKqwzNV//mHG6EtdSlC65+nqH/3zK8sOSWqNV6FVu2tmHhF/rFOp9UF8S4m1odojtuipKaKJrP0Loh60g== dependencies: "@babel/runtime" "^7.23.2" @@ -10046,10 +10171,10 @@ jws@^3.2.2: jwa "^1.4.1" safe-buffer "^5.0.1" -jwt-decode@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" - integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== +jwt-decode@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" + integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== keyv@^3.0.0: version "3.1.0" @@ -10417,6 +10542,11 @@ macos-release@^2.5.0: resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.1.tgz#bccac4a8f7b93163a8d163b8ebf385b3c5f55bf9" integrity sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A== +magic-bytes.js@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.8.0.tgz#8362793c60cd77c2dd77db6420be727192df68e2" + integrity sha512-lyWpfvNGVb5lu8YUAbER0+UMBTdR63w2mcSUlhhBTyVbxJvjgqwyAf3AZD6MprgK0uHuBoWXSDAMWLupX83o3Q== + magic-string@0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.0.tgz#fd58a4748c5c4547338a424e90fa5dd17f4de529" @@ -13660,6 +13790,11 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" +stream-wormhole@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stream-wormhole/-/stream-wormhole-1.1.0.tgz#300aff46ced553cfec642a05251885417693c33d" + integrity sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew== + streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" @@ -14113,6 +14248,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-decoding@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-decoding/-/text-decoding-1.0.0.tgz#38a5692d23b5c2b12942d6e245599cb58b1bc52f" + integrity sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"