diff --git a/.github/workflows/reusable-api-e2e.yml b/.github/workflows/reusable-api-e2e.yml index 2202c9033..8feb34b91 100644 --- a/.github/workflows/reusable-api-e2e.yml +++ b/.github/workflows/reusable-api-e2e.yml @@ -48,6 +48,8 @@ jobs: - name: Install dependencies run: pnpm install + env: + CI: false # Runs a set of commands using the runners shell - name: Run a test diff --git a/apps/api/src/app/shared/constants.ts b/apps/api/src/app/shared/constants.ts index ef75a8cac..7379b8bd9 100644 --- a/apps/api/src/app/shared/constants.ts +++ b/apps/api/src/app/shared/constants.ts @@ -9,6 +9,7 @@ export const APIMessages = { INVALID_TEMPLATE_ID_CODE_SUFFIX: 'is not valid TemplateId or CODE.', FILE_MAPPING_REMAINING: 'File mapping is not yet done, please finalize mapping before.', UPLOAD_NOT_FOUND: 'Upload information not found with specified uploadId.', + TEMPLATE_NOT_FOUND: 'Template not found with specified templateId.', FILE_NOT_FOUND_IN_STORAGE: "File not found, make sure you're using the same storage provider, that you were using before.", DO_MAPPING_FIRST: 'You may landed to wrong place, Please finalize mapping and proceed ahead.', diff --git a/apps/api/src/app/shared/helpers/common.helper.ts b/apps/api/src/app/shared/helpers/common.helper.ts index ff0be43fe..46bf62e1e 100644 --- a/apps/api/src/app/shared/helpers/common.helper.ts +++ b/apps/api/src/app/shared/helpers/common.helper.ts @@ -2,12 +2,14 @@ import { BadRequestException } from '@nestjs/common'; import { APIMessages } from '../constants'; import { PaginationResult, Defaults } from '@impler/shared'; -export function validateNotFound(data: any, entityName: 'upload'): boolean { +export function validateNotFound(data: any, entityName: 'upload' | 'template'): boolean { if (data) return true; else { switch (entityName) { case 'upload': throw new BadRequestException(APIMessages.UPLOAD_NOT_FOUND); + case 'template': + throw new BadRequestException(APIMessages.TEMPLATE_NOT_FOUND); default: throw new BadRequestException(); } diff --git a/apps/api/src/app/template/dtos/duplicate-template-request.dto.ts b/apps/api/src/app/template/dtos/duplicate-template-request.dto.ts new file mode 100644 index 000000000..8be08b6ae --- /dev/null +++ b/apps/api/src/app/template/dtos/duplicate-template-request.dto.ts @@ -0,0 +1,46 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDefined, IsString, IsOptional, IsBoolean } from 'class-validator'; + +export class DuplicateTemplateRequestDto { + @ApiProperty({ + description: 'Name of the template', + }) + @IsDefined() + @IsString() + name: string; + + @ApiProperty({ + description: '_projectId where the template will be created', + }) + @IsString() + @IsDefined() + _projectId: string; + + @ApiProperty({ + description: 'Whether to duplicate columns?', + }) + @IsBoolean() + @IsOptional() + duplicateColumns?: boolean; + + @ApiProperty({ + description: 'Whether to duplicate output?', + }) + @IsBoolean() + @IsOptional() + duplicateOutput?: boolean; + + @ApiProperty({ + description: 'Whether to duplicate webhook?', + }) + @IsBoolean() + @IsOptional() + duplicateWebhook?: boolean; + + @ApiProperty({ + description: 'Whether to duplicate validation code?', + }) + @IsBoolean() + @IsOptional() + duplicateValidator?: boolean; +} diff --git a/apps/api/src/app/template/template.controller.ts b/apps/api/src/app/template/template.controller.ts index 2962579b7..d64d88784 100644 --- a/apps/api/src/app/template/template.controller.ts +++ b/apps/api/src/app/template/template.controller.ts @@ -11,6 +11,7 @@ import { DocumentNotFoundException } from '@shared/exceptions/document-not-found import { GetUploads, + DuplicateTemplate, GetTemplateColumns, CreateTemplate, DeleteTemplate, @@ -27,6 +28,7 @@ import { GetValidations, DownloadSample, UpdateValidations, + DuplicateTemplateCommand, UpdateValidationsCommand, } from './usecases'; @@ -38,6 +40,7 @@ import { ValidationsResponseDto } from './dtos/validations-response.dto'; import { CustomizationResponseDto } from './dtos/customization-response.dto'; import { CreateTemplateRequestDto } from './dtos/create-template-request.dto'; import { UpdateTemplateRequestDto } from './dtos/update-template-request.dto'; +import { DuplicateTemplateRequestDto } from './dtos/duplicate-template-request.dto'; import { UpdateValidationResponseDto } from './dtos/update-validation-response.dto'; import { UpdateValidationsRequestDto } from './dtos/update-validations-request.dto'; import { UpdateCustomizationRequestDto } from './dtos/update-customization-request.dto'; @@ -54,6 +57,7 @@ export class TemplateController { private getCustomization: GetCustomization, private updateValidations: UpdateValidations, private syncCustomization: SyncCustomization, + private duplicateTemplate: DuplicateTemplate, private createTemplateUsecase: CreateTemplate, private updateTemplateUsecase: UpdateTemplate, private deleteTemplateUsecase: DeleteTemplate, @@ -121,6 +125,20 @@ export class TemplateController { ); } + @Post(':templateId/duplicate') + @ApiOperation({ + summary: 'Duplicate template', + }) + @ApiOkResponse({ + type: TemplateResponseDto, + }) + async duplicateTemplateRoute( + @Param('templateId', ValidateMongoId) templateId: string, + @Body() body: DuplicateTemplateRequestDto + ): Promise { + return this.duplicateTemplate.execute(templateId, DuplicateTemplateCommand.create(body)); + } + @Put(':templateId') @ApiOperation({ summary: 'Update template', diff --git a/apps/api/src/app/template/usecases/duplicate-template/duplicate-template.command.ts b/apps/api/src/app/template/usecases/duplicate-template/duplicate-template.command.ts new file mode 100644 index 000000000..69d1bbac5 --- /dev/null +++ b/apps/api/src/app/template/usecases/duplicate-template/duplicate-template.command.ts @@ -0,0 +1,28 @@ +import { IsBoolean, IsDefined, IsOptional, IsString } from 'class-validator'; +import { BaseCommand } from '@shared/commands/base.command'; + +export class DuplicateTemplateCommand extends BaseCommand { + @IsDefined() + @IsString() + name: string; + + @IsString() + @IsDefined() + _projectId: string; + + @IsBoolean() + @IsOptional() + duplicateColumns?: boolean; + + @IsBoolean() + @IsOptional() + duplicateOutput?: boolean; + + @IsBoolean() + @IsOptional() + duplicateWebhook?: boolean; + + @IsBoolean() + @IsOptional() + duplicateValidator?: boolean; +} diff --git a/apps/api/src/app/template/usecases/duplicate-template/duplicate-template.usecase.ts b/apps/api/src/app/template/usecases/duplicate-template/duplicate-template.usecase.ts new file mode 100644 index 000000000..f577328e3 --- /dev/null +++ b/apps/api/src/app/template/usecases/duplicate-template/duplicate-template.usecase.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@nestjs/common'; +import { TemplateRepository, CustomizationRepository, ColumnRepository, ValidatorRepository } from '@impler/dal'; +import { validateNotFound } from '@shared/helpers/common.helper'; +import { DuplicateTemplateCommand } from './duplicate-template.command'; +import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase'; + +@Injectable() +export class DuplicateTemplate { + constructor( + private saveSampleFile: SaveSampleFile, + private columnRepository: ColumnRepository, + private templateRepository: TemplateRepository, + private validatorRepository: ValidatorRepository, + private customizationRepository: CustomizationRepository + ) {} + + async execute(_templateId: string, command: DuplicateTemplateCommand) { + const templateData = await this.templateRepository.findById( + _templateId, + 'name callbackUrl chunkSize authHeaderName chunkSize' + ); + + validateNotFound(templateData, 'template'); + + const newTemplate = await this.templateRepository.create({ + name: command.name, + _projectId: command._projectId, + chunkSize: templateData.chunkSize, + ...(command.duplicateWebhook && { + callbackUrl: templateData.callbackUrl, + authHeaderName: templateData.authHeaderName, + }), + }); + + if (command.duplicateColumns) { + const columns = await this.columnRepository.find( + { + _templateId, + }, + '-_id name key alternateKeys isRequired isUnique type regex regexDescription selectValues dateFormats sequence defaultValue' + ); + + await this.columnRepository.createMany( + columns.map((column) => { + return { + ...column, + _templateId: newTemplate._id, + }; + }) + ); + + await this.saveSampleFile.execute(columns, newTemplate._id); + } + + if (command.duplicateOutput) { + const validation = await this.customizationRepository.findOne( + { + _templateId, + }, + '-_id recordVariables chunkVariables recordFormat chunkFormat combinedFormat isRecordFormatUpdated isChunkFormatUpdated isCombinedFormatUpdated' + ); + + await this.customizationRepository.create({ + _templateId: newTemplate._id, + ...validation, + }); + } + + if (command.duplicateValidator) { + const validator = await this.validatorRepository.findOne({ _templateId }, '-_id onBatchInitialize'); + await this.validatorRepository.create({ + _templateId: newTemplate._id, + ...validator, + }); + } + + return newTemplate; + } +} diff --git a/apps/api/src/app/template/usecases/index.ts b/apps/api/src/app/template/usecases/index.ts index 922550243..312b5bce7 100644 --- a/apps/api/src/app/template/usecases/index.ts +++ b/apps/api/src/app/template/usecases/index.ts @@ -8,6 +8,7 @@ import { GetValidations } from './get-validations/get-validations.usecase'; import { GetCustomization } from './get-customization/get-customization.usecase'; import { SyncCustomization } from './sync-customization/sync-customization.usecase'; import { UpdateValidations } from './update-validations/update-validations.usecase'; +import { DuplicateTemplate } from './duplicate-template/duplicate-template.usecase'; import { GetTemplateDetails } from './get-template-details/get-template-details.usecase'; import { UpdateCustomization } from './update-customization/update-customization.usecase'; import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase'; @@ -16,6 +17,7 @@ import { UpdateTemplateColumns } from './update-template-columns/update-template import { GetUploadsCommand } from './get-uploads/get-uploads.command'; import { CreateTemplateCommand } from './create-template/create-template.command'; import { UpdateTemplateCommand } from './update-template/update-template.command'; +import { DuplicateTemplateCommand } from './duplicate-template/duplicate-template.command'; import { UpdateValidationsCommand } from './update-validations/update-validations.command'; import { UpdateCustomizationCommand } from './update-customization/update-customization.command'; @@ -23,6 +25,7 @@ export const USE_CASES = [ CreateTemplate, UpdateTemplate, DeleteTemplate, + DuplicateTemplate, GetTemplateDetails, GetUploads, SyncCustomization, @@ -44,6 +47,7 @@ export { SyncCustomization, GetTemplateDetails, GetUploads, + DuplicateTemplate, GetTemplateColumns, UpdateTemplateColumns, UpdateCustomization, @@ -57,5 +61,6 @@ export { UpdateValidationsCommand, UpdateTemplateCommand, GetUploadsCommand, + DuplicateTemplateCommand, UpdateCustomizationCommand, }; diff --git a/apps/web/assets/icons/Copy.icon.tsx b/apps/web/assets/icons/Copy.icon.tsx index 1a48c1873..45acc6a5e 100644 --- a/apps/web/assets/icons/Copy.icon.tsx +++ b/apps/web/assets/icons/Copy.icon.tsx @@ -1,7 +1,7 @@ import { IconType } from '@types'; import { IconSizes } from 'config'; -export const CopyIcon = ({ size = 'sm', color }: IconType) => { +export const CopyIcon = ({ size = 'sm', color, className }: IconType) => { return ( { color={color} viewBox="0 0 20 20" fill="none" + className={className} xmlns="http://www.w3.org/2000/svg" > void; +} + +export function DuplicateImportForm({ onSubmit, profile, projects }: DuplicateImportFormProps) { + const focusTrapRef = useFocusTrap(); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + _projectId: profile?._projectId, + duplicateColumns: true, + duplicateOutput: true, + duplicateWebhook: true, + duplicateValidator: true, + }, + }); + + return ( +
+ + + +
+ ); +} diff --git a/apps/web/config/constants.config.ts b/apps/web/config/constants.config.ts index 6712cd638..1b2a12bb7 100644 --- a/apps/web/config/constants.config.ts +++ b/apps/web/config/constants.config.ts @@ -21,6 +21,7 @@ export const VARIABLES = { }; export const MODAL_KEYS = { + IMPORT_DUPLICATE: 'IMPORT_DUPLICATE', IMPORT_CREATE: 'IMPORT_CREATE', IMPORT_UPDATE: 'IMPORT_UPDATE', COLUMN_CREATE: 'COLUMN_CREATE', @@ -31,6 +32,7 @@ export const MODAL_KEYS = { }; export const MODAL_TITLES = { + IMPORT_DUPLICATE: 'Duplicate Import', IMPORT_CREATE: 'Start with a new Import', COLUMN_CREATE: 'Add a new Column', IMPORT_UPDATE: 'Update Import', @@ -59,6 +61,7 @@ export const API_KEYS = { TEMPLATE_DETAILS: 'TEMPLATE_DETAILS', TEMPLATE_UPDATE: 'TEMPLATE_UPDATE', TEMPLATE_DELETE: 'TEMPLATE_DELETE', + TEMPLATES_DUPLICATE: 'TEMPLATES_DUPLICATE', TEMPLATE_COLUMNS_LIST: 'TEMPLATE_COLUMNS_LIST', TEMPLATE_CUSTOMIZATION_GET: 'CUSTOMIZATION_GET', TEMPLATE_COLUMNS_UPDATE: 'TEMPLATE_COLUMNS_UPDATE', @@ -81,6 +84,7 @@ export const API_KEYS = { }; export const NOTIFICATION_KEYS = { + IMPORT_DUPLICATED: 'IMPORT_DUPLICATED', IMPORT_UPDATED: 'IMPORT_UPDATED', IMPORT_CREATED: 'IMPORT_CREATED', IMPORT_DELETED: 'IMPORT_DELETED', diff --git a/apps/web/design-system/icon-button/IconButton.tsx b/apps/web/design-system/icon-button/IconButton.tsx index 3ec4ac512..abc1eb800 100644 --- a/apps/web/design-system/icon-button/IconButton.tsx +++ b/apps/web/design-system/icon-button/IconButton.tsx @@ -1,15 +1,23 @@ -import { Tooltip, UnstyledButton } from '@mantine/core'; -import { PropsWithChildren } from 'react'; +import { MouseEvent, PropsWithChildren } from 'react'; +import { Tooltip, UnstyledButton, UnstyledButtonProps } from '@mantine/core'; -interface IconButtonProps { +interface IconButtonProps extends UnstyledButtonProps { label: string; withArrow?: boolean; - onClick?: () => void; + onClick?: (e: MouseEvent) => void; } -export function IconButton({ label, onClick, withArrow = true, children }: PropsWithChildren) { +export function IconButton({ + label, + onClick, + withArrow = true, + children, + ...buttonProps +}: PropsWithChildren) { return ( - {children} + + {children} + ); } diff --git a/apps/web/design-system/import-card/ImportCard.styles.tsx b/apps/web/design-system/import-card/ImportCard.styles.tsx index 84f3b92f5..b878abd4e 100644 --- a/apps/web/design-system/import-card/ImportCard.styles.tsx +++ b/apps/web/design-system/import-card/ImportCard.styles.tsx @@ -1,5 +1,5 @@ import { colors } from '@config'; -import { createStyles, MantineTheme } from '@mantine/core'; +import { createStyles, CSSObject, MantineTheme } from '@mantine/core'; const getRootStyles = (theme: MantineTheme) => ({ height: '100%', @@ -21,18 +21,26 @@ const getRootStyles = (theme: MantineTheme) => ({ }, }); -const getNameStyles = (theme: MantineTheme): React.CSSProperties => ({ +const getNameStyles = (theme: MantineTheme): CSSObject => ({ color: theme.colorScheme === 'dark' ? colors.white : colors.black, fontWeight: 600, fontSize: theme.fontSizes.xl, }); -const getKeyStyles = (theme: MantineTheme): React.CSSProperties => ({ +const getKeyStyles = (theme: MantineTheme): CSSObject => ({ color: colors.TXTSecondaryDark, fontSize: theme.fontSizes.sm, }); -const getValueStyles = (theme: MantineTheme): React.CSSProperties => ({ +const getDuplicateButtonStyles = (theme: MantineTheme): CSSObject => ({ + transition: 'color 0.2s ease-in-out', + color: theme.colorScheme === 'dark' ? colors.white : colors.black, + '&:hover': { + color: colors.blueDark, + }, +}); + +const getValueStyles = (theme: MantineTheme): CSSObject => ({ color: theme.colorScheme === 'dark' ? colors.white : colors.black, fontWeight: 600, fontSize: theme.fontSizes.sm, @@ -40,9 +48,10 @@ const getValueStyles = (theme: MantineTheme): React.CSSProperties => ({ export default createStyles((theme: MantineTheme): Record => { return { + key: getKeyStyles(theme), root: getRootStyles(theme), name: getNameStyles(theme), - key: getKeyStyles(theme), value: getValueStyles(theme), + duplicate: getDuplicateButtonStyles(theme), }; }); diff --git a/apps/web/design-system/import-card/ImportCard.tsx b/apps/web/design-system/import-card/ImportCard.tsx index 441bb40d4..360738ced 100644 --- a/apps/web/design-system/import-card/ImportCard.tsx +++ b/apps/web/design-system/import-card/ImportCard.tsx @@ -1,36 +1,47 @@ -import { Flex, Group, Text } from '@mantine/core'; -import useStyles from './ImportCard.styles'; import Link from 'next/link'; +import { MouseEvent } from 'react'; +import { Divider, Flex, Stack, Text } from '@mantine/core'; + +import useStyles from './ImportCard.styles'; +import { IconButton } from '@ui/icon-button'; +import { CopyIcon } from '@assets/icons/Copy.icon'; interface ImportCardProps { + href: string; title: string; imports: number; totalRecords: number; errorRecords: number; - href: string; + onDuplicateClick?: (e: MouseEvent) => void; } -export function ImportCard({ title, imports, totalRecords, errorRecords, href }: ImportCardProps) { +export function ImportCard({ title, imports, totalRecords, errorRecords, onDuplicateClick, href }: ImportCardProps) { const { classes } = useStyles(); return ( - - {title} - - - Imports + + {title} + + + + + + + + {imports} - - - Total Records + Imports + + {totalRecords} - - - Error Records + Records Imported + + {errorRecords} - + Error Records + ); diff --git a/apps/web/design-system/select/Select.tsx b/apps/web/design-system/select/Select.tsx index bc0a089df..1cac9f38e 100644 --- a/apps/web/design-system/select/Select.tsx +++ b/apps/web/design-system/select/Select.tsx @@ -5,18 +5,20 @@ interface SelectProps { data: SelectItem[]; register?: any; label?: string; + required?: boolean; autoFocus?: boolean; onFocus?: () => void; variant?: Variants<'default' | 'filled' | 'unstyled'>; } -export function Select({ placeholder, data, register, label, autoFocus, onFocus, variant }: SelectProps) { +export function Select({ required, placeholder, data, register, label, autoFocus, onFocus, variant }: SelectProps) { return ( (); const [limit, setLimit] = useState(VARIABLES.TEN); const [search, setSearch] = useDebouncedState('', 500); + const { data: projects } = useQuery( + [API_KEYS.PROJECTS_LIST], + () => commonApi(API_KEYS.PROJECTS_LIST as any, {}) + ); const { mutate: createImport, isLoading: isCreateImportLoading } = useMutation< ITemplate, IErrorObject, @@ -27,7 +33,6 @@ export function useImports() { >( [API_KEYS.TEMPLATES_CREATE, profileInfo?._projectId], (data) => - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion commonApi(API_KEYS.TEMPLATES_CREATE as any, { body: { ...data, _projectId: profileInfo!._projectId! }, }), @@ -47,6 +52,34 @@ export function useImports() { }, } ); + const { mutate: duplicateImport } = useMutation< + ITemplate, + IErrorObject, + [string, IDuplicateTemplateData], + (string | undefined)[] + >( + [API_KEYS.TEMPLATES_DUPLICATE, profileInfo?._projectId], + ([templateId, data]) => + commonApi(API_KEYS.TEMPLATES_DUPLICATE as any, { + body: { ...data }, + parameters: [templateId], + }), + { + onSuccess: (data) => { + modals.close(MODAL_KEYS.IMPORT_DUPLICATE); + queryClient.setQueryData([API_KEYS.TEMPLATES_LIST, data._projectId], (oldData) => [ + ...(oldData || []), + data, + ]); + track({ + name: 'IMPORT DUPLICATE', + properties: {}, + }); + push(`/imports/${data._id}`); + notify(NOTIFICATION_KEYS.IMPORT_DUPLICATED); + }, + } + ); const { data: importsData, isLoading: isImportsLoading } = useQuery< unknown, IErrorObject, @@ -96,6 +129,19 @@ export function useImports() { children: , }); } + function onDuplicateClick(importId: string) { + modals.open({ + modalId: MODAL_KEYS.IMPORT_DUPLICATE, + title: MODAL_TITLES.IMPORT_DUPLICATE, + children: ( + duplicateImport([importId, data])} + /> + ), + }); + } return { page, @@ -105,6 +151,7 @@ export function useImports() { onSearchChange, onCreateClick, onLimitChange, + onDuplicateClick, isImportsLoading, onPageChange: setPage, isCreateImportLoading, diff --git a/apps/web/libs/amplitude.ts b/apps/web/libs/amplitude.ts index 1e263714d..2efeaa72a 100644 --- a/apps/web/libs/amplitude.ts +++ b/apps/web/libs/amplitude.ts @@ -114,6 +114,10 @@ type TrackData = limit?: number; text?: string; }; + } + | { + name: 'IMPORT DUPLICATE'; + properties: Record; }; export function track({ name, properties }: TrackData) { diff --git a/apps/web/libs/api.ts b/apps/web/libs/api.ts index 310cbffe8..4508078ba 100644 --- a/apps/web/libs/api.ts +++ b/apps/web/libs/api.ts @@ -60,6 +60,10 @@ const routes: Record = { url: (templateId) => `/v1/template/${templateId}`, method: 'PUT', }, + [API_KEYS.TEMPLATES_DUPLICATE]: { + url: (templateId) => `/v1/template/${templateId}/duplicate`, + method: 'POST', + }, [API_KEYS.TEMPLATE_COLUMNS_UPDATE]: { url: (templateId) => `/v1/template/${templateId}/columns`, method: 'PUT', diff --git a/apps/web/libs/notify.ts b/apps/web/libs/notify.ts index 7d0fc3079..43e4233c2 100644 --- a/apps/web/libs/notify.ts +++ b/apps/web/libs/notify.ts @@ -10,6 +10,10 @@ const Messages: Record = { title: 'Import created', message: 'Import has been created successfully', }, + [NOTIFICATION_KEYS.IMPORT_DUPLICATED]: { + title: 'Import duplicated', + message: 'Import has been duplicated successfully', + }, [NOTIFICATION_KEYS.IMPORT_DELETED]: { title: 'Import deleted', message: 'Import has been deleted successfully', diff --git a/apps/web/pages/imports/index.tsx b/apps/web/pages/imports/index.tsx index f0d62e9b5..a5ac48792 100644 --- a/apps/web/pages/imports/index.tsx +++ b/apps/web/pages/imports/index.tsx @@ -16,12 +16,13 @@ export default function Imports() { const { search, importsData, + onPageChange, onCreateClick, - isCreateImportLoading, - isImportsLoading, onLimitChange, - onPageChange, onSearchChange, + onDuplicateClick, + isImportsLoading, + isCreateImportLoading, } = useImports(); return ( @@ -67,9 +68,13 @@ export default function Imports() { { + e.preventDefault(); + onDuplicateClick(importItem._id); + }} totalRecords={importItem.totalRecords} errorRecords={importItem.totalInvalidRecords} - href={`/imports/${importItem._id}`} /> ))} diff --git a/apps/web/types/component.types.ts b/apps/web/types/component.types.ts index b05061018..e6df7b3da 100644 --- a/apps/web/types/component.types.ts +++ b/apps/web/types/component.types.ts @@ -3,5 +3,6 @@ import { IconSizes } from '@config'; export interface IconType { name?: string; color?: string; + className?: string; size?: keyof typeof IconSizes; } diff --git a/apps/web/types/global.d.ts b/apps/web/types/global.d.ts index d980aa633..6a6e80760 100644 --- a/apps/web/types/global.d.ts +++ b/apps/web/types/global.d.ts @@ -32,7 +32,14 @@ interface ICreateProjectData { interface ICreateTemplateData { name: string; - code: string; +} +interface IDuplicateTemplateData { + name: string; + _projectId: string; + duplicateColumns?: boolean; + duplicateOutput?: boolean; + duplicateWebhook?: boolean; + duplicateValidator?: boolean; } interface IUpdateTemplateData { name: string; diff --git a/apps/widget/src/hooks/Phase1/usePhase1.ts b/apps/widget/src/hooks/Phase1/usePhase1.ts index e9317edcb..844618070 100644 --- a/apps/widget/src/hooks/Phase1/usePhase1.ts +++ b/apps/widget/src/hooks/Phase1/usePhase1.ts @@ -53,6 +53,7 @@ export function usePhase1({ goNext }: IUsePhase1Props) { onError(error: IErrorObject) { notifier.showError({ message: error.message, title: error.error }); }, + refetchOnMount: 'always', } ); const { isLoading: isUploadLoading, mutate: submitUpload } = useMutation(