From fa5bd36996e54fc4743b65b2b5ef54a79b4ec453 Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Fri, 10 Oct 2025 20:07:24 +0530 Subject: [PATCH 1/6] [CMG-726] --- ui/src/components/AdvancePropertise/index.tsx | 8 +++++--- ui/src/components/ContentMapper/index.scss | 2 +- ui/src/components/ContentMapper/index.tsx | 12 ++++++------ ui/src/components/LogScreen/MigrationLogViewer.tsx | 5 ----- ui/src/components/Modal/index.tsx | 6 ++++++ ui/src/pages/Login/index.tsx | 13 ++++--------- ui/src/pages/Migration/index.tsx | 2 +- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index a33a25540..1e377e3ca 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -316,8 +316,6 @@ const AdvancePropertise = (props: SchemaProps) => { }); }; - - const handleDrop = (index: number) => { if (draggedIndex === null) return; @@ -576,7 +574,11 @@ const AdvancePropertise = (props: SchemaProps) => { ); }} options={ - option} + (Array.isArray(props?.data?.referenceTo) && props?.data?.referenceTo?.length) ? [props.data.referenceTo.map((item: any) => ({ + label: item, + value: item + })), ...option] + : option ?? []} placeholder="Add Content Type(s)" version="v2" isSearchable={true} diff --git a/ui/src/components/ContentMapper/index.scss b/ui/src/components/ContentMapper/index.scss index dbc864689..41470909e 100644 --- a/ui/src/components/ContentMapper/index.scss +++ b/ui/src/components/ContentMapper/index.scss @@ -320,7 +320,7 @@ div .table-row { display: block; } .advanced-setting-button { - padding: 5px; + padding: 5px 10px; } .field-count { font-size: $size-font-large; diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index 93da754ad..d7b381ba0 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -1340,14 +1340,14 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: diff --git a/ui/src/components/LogScreen/MigrationLogViewer.tsx b/ui/src/components/LogScreen/MigrationLogViewer.tsx index 4ca2acc5c..0eea6be8b 100644 --- a/ui/src/components/LogScreen/MigrationLogViewer.tsx +++ b/ui/src/components/LogScreen/MigrationLogViewer.tsx @@ -12,15 +12,10 @@ import { updateNewMigrationData } from '../../store/slice/migrationDataSlice'; // Utilities import { CS_URL } from '../../utilities/constants'; -// Service -import { updateCurrentStepData } from '../../services/api/migration.service'; - // Interface import { INewMigration } from '../../context/app/app.interface'; -import { ModalObj } from '../Modal/modal.interface'; // Components -import MigrationCompletionModal from '../Common/MigrationCompletionModal'; import useBlockNavigation from '../../hooks/userNavigation'; // CSS diff --git a/ui/src/components/Modal/index.tsx b/ui/src/components/Modal/index.tsx index f5e135bf2..66e1e60c3 100644 --- a/ui/src/components/Modal/index.tsx +++ b/ui/src/components/Modal/index.tsx @@ -100,12 +100,18 @@ const Modal = (props: ProjectModalProps) => { if (result) { Notification({ notificationContent: { text: result?.data?.message }, + notificationProps: { + hideProgressBar: true + }, type: 'success' }); closeModal(); } else { Notification({ notificationContent: { text: 'Error occurred while creating project.' }, + notificationProps: { + hideProgressBar: true + }, type: 'error' }); closeModal(); diff --git a/ui/src/pages/Login/index.tsx b/ui/src/pages/Login/index.tsx index 24ec20efe..bbfde4a23 100644 --- a/ui/src/pages/Login/index.tsx +++ b/ui/src/pages/Login/index.tsx @@ -92,7 +92,10 @@ const Login: FC = () => { if (res?.status === 200 && res?.data?.notice === TFA_VIA_SMS_MESSAGE) { Notification({ notificationContent: { text: res?.data?.notice }, - type: 'success' + type: 'success', + notificationProps: { + hideProgressBar: true + } }); } @@ -198,14 +201,6 @@ const Login: FC = () => { } }; - // useEffect(()=>{ - // if(region && loginStates?.tfa){ - // setIsBlock(true); - - // } - - // },[loginStates]); - // Function for TFA validation const TFAValidation = (value: string): string | undefined => { if (value?.length) { diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index 4ff1fadc0..dde79bdaf 100644 --- a/ui/src/pages/Migration/index.tsx +++ b/ui/src/pages/Migration/index.tsx @@ -736,7 +736,7 @@ const Migration = () => { notificationContent: { text: 'Migration Execution process started' }, notificationProps: { position: 'bottom-center', - hideProgressBar: false + hideProgressBar: true }, type: 'message' }); From 2799cba814333d93992f39d7783c1acf14799d19 Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Wed, 15 Oct 2025 19:06:59 +0530 Subject: [PATCH 2/6] Disabled mandatory toggle for Modular blocks --- ui/src/components/AdvancePropertise/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index 7e5eb1ecf..233fed7a5 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -661,6 +661,7 @@ const AdvancePropertise = (props: SchemaProps) => { true )) } + disabled={props?.fieldtype === 'Modular Blocks' || props?.fieldtype === 'Block'} /> )} From 087d3301455b93d8e17d4dbf4d0aac1a333bae31 Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Tue, 4 Nov 2025 14:44:38 +0530 Subject: [PATCH 3/6] [CMG-736] - Multiple clicks on buttons or links that return results should be prevented. --- .gitignore | 4 +++- ui/src/components/AdvancePropertise/index.tsx | 7 +------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index cda7033d5..cd6e74c93 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,6 @@ upload-api/extracted_files *copy* .qodo .vscode -app.json \ No newline at end of file +app.json +# Snyk Security Extension - AI Rules (auto-generated) +.cursor/rules/snyk_rules.mdc diff --git a/ui/src/components/AdvancePropertise/index.tsx b/ui/src/components/AdvancePropertise/index.tsx index 233fed7a5..9a114f0cb 100644 --- a/ui/src/components/AdvancePropertise/index.tsx +++ b/ui/src/components/AdvancePropertise/index.tsx @@ -573,12 +573,7 @@ const AdvancePropertise = (props: SchemaProps) => { props?.data?.contentstackFieldUid ); }} - options={ - (Array.isArray(props?.data?.referenceTo) && props?.data?.referenceTo?.length) ? [props.data.referenceTo.map((item: any) => ({ - label: item, - value: item - })), ...option] - : option ?? []} + options={option ?? []} placeholder="Add Content Type(s)" version="v2" isSearchable={true} From a6d356eef3130e0400d892ef08f99de2ffea23c1 Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Tue, 4 Nov 2025 15:59:01 +0530 Subject: [PATCH 4/6] [CMG-736] - Multiple clicks on buttons or links that return results should be prevented. --- ui/src/components/ContentMapper/index.tsx | 257 +++++++++++++--------- 1 file changed, 151 insertions(+), 106 deletions(-) diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index b78bf8f7b..f68cdd156 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -301,7 +301,8 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: const [isCsCTypeUpdated, setsCsCTypeUpdated] = useState(false); const [isLoadingSaveButton, setisLoadingSaveButton] = useState(false); const [activeFilter, setActiveFilter] = useState(''); - const [isAllCheck, setIsAllCheck] = useState(false); + const [isAllCheck, setIsAllCheck] = useState(false); + const [isResetFetch, setIsResetFetch] = useState(false); /** ALL HOOKS Here */ @@ -496,6 +497,20 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: }; }, []); + /** + * Debounces a function call by delaying its execution until after the specified delay has elapsed since the last invocation. + * @param fn - The function to debounce + * @param delay - The delay in milliseconds to wait before executing the function + * @returns A debounced version of the function + */ + const debounce = (fn: (...args: any[]) => any, delay: number | undefined) => { + let timeoutId: string | number | NodeJS.Timeout | undefined; + return (...args: any[]) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => fn(...args), delay); + }; + }; + const checkAndUpdateField = ( item: any, value: any, @@ -1998,11 +2013,14 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: handleDropdownState })); - const handleResetContentType = async () => { + const handleResetContentType = debounce(async () => { + // Prevent duplicate clicks + if (isResetFetch) return; + const orgId = selectedOrganisation?.value; const projectID = projectId; setIsDropDownChanged(false); - + setIsResetFetch(true); const updatedRows: FieldMapType[] = tableData?.map?.((row) => { return { ...row, @@ -2056,6 +2074,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: }); if (status === 200) { + setIsResetFetch(false); const updatedContentMapping = { ...newMigrationData?.content_mapping?.content_type_mapping }; delete updatedContentMapping[selectedContentType?.contentstackUid]; @@ -2089,9 +2108,10 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: notificationContent: { text: data?.message }, notificationProps: { position: 'bottom-center', - hideProgressBar: false + hideProgressBar: true }, - type: 'success' + type: 'success', + autoClose: 2 }); try { @@ -2106,112 +2126,30 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } catch (error) { console.error(error); return error; + } finally { + // Re-enable icon after API completes + setIsResetFetch(false); } } - }; - - const handleCTDeleted = async (isContentType: boolean, contentTypes: ContentTypeList[]) => { - const updatedContentTypeMapping = Object.fromEntries( - Object.entries(newMigrationData?.content_mapping?.content_type_mapping || {})?.filter( - ([key]) => !selectedContentType?.contentstackUid?.includes?.(key) - ) - ); - - const orgId = selectedOrganisation?.value; - const projectID = projectId; - setIsDropDownChanged(false); - - const updatedRows: FieldMapType[] = tableData.map((row) => { - return { ...row, contentstackFieldType: row?.backupFieldType }; - }); - setTableData(updatedRows); - setSelectedEntries(updatedRows); - - const dataCs = { - contentTypeData: { - status: selectedContentType?.status, - id: selectedContentType?.id, - projectId: projectId, - otherCmsTitle: otherCmsTitle, - otherCmsUid: selectedContentType?.otherCmsUid, - isUpdated: true, - updateAt: new Date(), - contentstackTitle: selectedContentType?.contentstackTitle, - contentstackUid: selectedContentType?.contentstackUid, - fieldMapping: updatedRows - } - }; - let newstate = {}; - setContentTypeMapped((prevState: ContentTypeMap) => { - const newState = { ...prevState }; - - delete newState[selectedContentType?.contentstackUid ?? '']; - newstate = newState; - - return newstate; - }); - - if (orgId && selectedContentType) { - try { - const { data, status } = await resetToInitialMapping( - orgId, - projectID, - selectedContentType?.id ?? '', - dataCs - ); - - setExistingField({}); - setContentTypeSchema([]); - setOtherContentType({ - label: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Destination Stack`, - value: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Destination Stack` - }); - - if (status === 200) { - const resetCT = filteredContentTypes?.map?.(ct => - ct?.id === selectedContentType?.id ? { ...ct, status: data?.data?.status } : ct - ); - setFilteredContentTypes(resetCT); - setContentTypes(resetCT); - - try { - await updateContentMapper(orgId, projectID, { ...newstate }); - } catch (err) { - console.error(err); - return err; - } - - } - } catch (error) { - console.error(error); - return error; - } - } - - const newMigrationDataObj: INewMigration = { - ...newMigrationData, - content_mapping: { - ...newMigrationData?.content_mapping, - [isContentType ? 'existingCT' : 'existingGlobal']: contentTypes, - content_type_mapping: updatedContentTypeMapping - - } - - } - dispatch(updateNewMigrationData(newMigrationDataObj)); - - } + }, 1500); +/** + * Retrieves existing content types for a given project. + * @returns An array containing the retrieved content types or global fields based on condition if itContentType true and if existing content type or global field id is passed then returns an object containing title, uid and schema of that particular content type or global field. + */ + const handleFetchContentType = debounce(async () => { + // Prevent duplicate clicks + if (isResetFetch) return; - /** - * Retrieves existing content types for a given project. - * @returns An array containing the retrieved content types or global fields based on condition if itContentType true and if existing content type or global field id is passed then returns an object containing title, uid and schema of that particular content type or global field. - */ - const handleFetchContentType = async () => { + setIsResetFetch(true); + if (isContentType) { try { + + const { data, status } = await getExistingContentTypes(projectId, otherContentType?.id ?? ''); if (status == 201 && data?.contentTypes?.length > 0) { (otherContentType?.id === data?.selectedContentType?.uid) && setsCsCTypeUpdated(false); + setIsResetFetch(false); (otherContentType?.id && otherContentType?.label !== data?.selectedContentType?.title && data?.selectedContentType?.title) && setOtherContentType({ @@ -2233,7 +2171,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: notificationContent: { text: 'Content Types fetched successfully' }, notificationProps: { position: 'bottom-center', - hideProgressBar: false + hideProgressBar: true }, type: 'success' }); @@ -2256,6 +2194,9 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } catch (error) { console.error(error); return error; + } finally { + // Re-enable icon after API completes + setIsResetFetch(false); } } else { try { @@ -2263,6 +2204,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: if (status == 201 && data?.globalFields?.length > 0) { (otherContentType?.id === data?.selectedGlobalField?.uid) && setsCsCTypeUpdated(false); + setIsResetFetch(false); (otherContentType?.id && otherContentType?.label !== data?.selectedGlobalField?.title && data?.selectedGlobalField?.title) && setOtherContentType({ @@ -2311,6 +2253,9 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: } catch (error) { console.error(error); return error; + } finally { + // Re-enable icon after API completes + setIsResetFetch(false); } } @@ -2340,6 +2285,106 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: }); } + }, 1500); + + /** + * Handles the deletion of a content type or global field. + * @param isContentType - Whether the content type is a content type or global field. + * @param contentTypes - The content types to delete. + */ + const handleCTDeleted = async (isContentType: boolean, contentTypes: ContentTypeList[]) => { + // Prevent duplicate clicks + if (isResetFetch) return; + + const updatedContentTypeMapping = Object.fromEntries( + Object.entries(newMigrationData?.content_mapping?.content_type_mapping || {})?.filter( + ([key]) => !selectedContentType?.contentstackUid?.includes?.(key) + ) + ); + + const orgId = selectedOrganisation?.value; + const projectID = projectId; + setIsDropDownChanged(false); + + const updatedRows: FieldMapType[] = tableData.map((row) => { + return { ...row, contentstackFieldType: row?.backupFieldType }; + }); + setTableData(updatedRows); + setSelectedEntries(updatedRows); + + const dataCs = { + contentTypeData: { + status: selectedContentType?.status, + id: selectedContentType?.id, + projectId: projectId, + otherCmsTitle: otherCmsTitle, + otherCmsUid: selectedContentType?.otherCmsUid, + isUpdated: true, + updateAt: new Date(), + contentstackTitle: selectedContentType?.contentstackTitle, + contentstackUid: selectedContentType?.contentstackUid, + fieldMapping: updatedRows + } + }; + let newstate = {}; + setContentTypeMapped((prevState: ContentTypeMap) => { + const newState = { ...prevState }; + + delete newState[selectedContentType?.contentstackUid ?? '']; + newstate = newState; + + return newstate; + }); + + if (orgId && selectedContentType) { + try { + const { data, status } = await resetToInitialMapping( + orgId, + projectID, + selectedContentType?.id ?? '', + dataCs + ); + + setExistingField({}); + setContentTypeSchema([]); + setOtherContentType({ + label: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Destination Stack`, + value: `Select ${isContentType ? 'Content Type' : 'Global Field'} from Destination Stack` + }); + + if (status === 200) { + const resetCT = filteredContentTypes?.map?.(ct => + ct?.id === selectedContentType?.id ? { ...ct, status: data?.data?.status } : ct + ); + setFilteredContentTypes(resetCT); + setContentTypes(resetCT); + + try { + await updateContentMapper(orgId, projectID, { ...newstate }); + } catch (err) { + console.error(err); + return err; + } + + } + } catch (error) { + console.error(error); + return error; + } + } + + const newMigrationDataObj: INewMigration = { + ...newMigrationData, + content_mapping: { + ...newMigrationData?.content_mapping, + [isContentType ? 'existingCT' : 'existingGlobal']: contentTypes, + content_type_mapping: updatedContentTypeMapping + + } + + } + dispatch(updateNewMigrationData(newMigrationDataObj)); + } const columns = [ @@ -2621,7 +2666,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: @@ -2630,7 +2675,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref: + size='small' onClick={handleResetContentType} disabled={isResetFetch}> ), From b46532a71c44e6df331b9aca0b2fa8b16b38c892 Mon Sep 17 00:00:00 2001 From: Sayali Joshi Date: Tue, 4 Nov 2025 17:02:37 +0530 Subject: [PATCH 5/6] Replaced Master word with default --- ui/src/components/Common/AddStack/addStack.tsx | 12 ++++++++---- .../DestinationStack/Actions/LoadLanguageMapper.tsx | 6 +----- .../DestinationStack/Actions/LoadStacks.tsx | 8 ++++---- .../components/LegacyCms/Actions/LoadUploadFile.tsx | 2 +- ui/src/components/LegacyCms/index.tsx | 2 +- ui/src/pages/Migration/index.tsx | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ui/src/components/Common/AddStack/addStack.tsx b/ui/src/components/Common/AddStack/addStack.tsx index 2d0c78417..5d460602a 100644 --- a/ui/src/components/Common/AddStack/addStack.tsx +++ b/ui/src/components/Common/AddStack/addStack.tsx @@ -24,7 +24,7 @@ import { getCMSDataFromFile } from '../../../cmsData/cmsSelector'; import { CS_ENTRIES } from '../../../utilities/constants'; // Interface -import { AddStackCMSData, defaultAddStackCMSData } from './addStack.interface'; +import { AddStackCMSData, defaultAddStackCMSData, Errors, Stack } from './addStack.interface'; // Styles import './addStack.scss'; @@ -129,8 +129,12 @@ const AddStack = (props: any): JSX.Element => { { - const errors: any = {}; + validate={(values: Stack) => { + const errors: Errors = { + name: '', + locale: '', + description: '' + }; if (!values?.name || values?.name?.trim().length < 1) { errors.name = 'Stack name required'; } @@ -263,7 +267,7 @@ const AddStack = (props: any): JSX.Element => { version={'v2'} placeholder={addStackCMSData?.stack_locale_description} /> -
Important: The master language cannot be changed after the stack has been created.
+
Important: The default language cannot be changed after the stack has been created.
{meta?.error && meta?.touched && ( { - const csLocaleKey = existingField?.[index]?.value; const selectedLocaleKey = selectedValue?.value; const existingLabel = existingField?.[index]; //const selectedLocaleKey = selectedMappings[index]; @@ -392,7 +389,7 @@ const Mapper = ({ {locale?.value === 'master_locale' ? (
@@ -531,7 +528,6 @@ const LanguageMapper = ({stack, uid} :{ stack : IDropDown, uid : string}) => { const [currentStack, setCurrentStack] = useState(stack); const [previousStack, setPreviousStack] = useState(); const [isStackChanged, setisStackChanged] = useState(false); - const [stackValue, setStackValue] = useState(stack?.value) const prevStackRef:any = useRef(null); diff --git a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx index c5dfa427b..1f6dab984 100644 --- a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx @@ -84,7 +84,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { const [isError, setIsError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [placeholder] = useState('Select a stack'); - const [localePlaceholder, setlocalePlaceholder ] = useState('Master Locale will be set after stack selection'); + const [localePlaceholder, setlocalePlaceholder ] = useState('Default Locale will be set after stack selection'); const newMigrationDataRef = useRef(newMigrationData); const [isStackLoading, setIsStackLoading] = useState(true); @@ -97,7 +97,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { setlocalePlaceholder('') } else{ - setlocalePlaceholder('Master Locale will be set after stack selection'); + setlocalePlaceholder('Default Locale will be set after stack selection'); } },[selectedStack]) @@ -334,10 +334,10 @@ const LoadStacks = (props: LoadFileFormatProps) => {
-