Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c1e4598
sitecore nested zip issue resolved
sauravraw Sep 22, 2025
b767523
remove hardcode value from config
sauravraw Sep 22, 2025
2537325
Merge branch 'feature/new-logo-update' of https://github.com/contents…
sauravraw Sep 25, 2025
e6f12b1
fixed sitecore issues
sauravraw Sep 25, 2025
5a4de41
Update contenttypes.js
sauravraw Sep 26, 2025
54c45d8
added prefix support
sauravraw Sep 26, 2025
fa5bd36
[CMG-726]
sayalijoshi27 Oct 10, 2025
1dab9fa
Merge branch 'dev' of https://github.com/contentstack/migration-v2-no…
sayalijoshi27 Oct 10, 2025
0f4eb92
Merge branch 'dev' of https://github.com/contentstack/migration-v2-no…
sayalijoshi27 Oct 10, 2025
585e624
Merge branch 'dev' of https://github.com/contentstack/migration-v2-no…
sayalijoshi27 Oct 15, 2025
2799cba
Disabled mandatory toggle for Modular blocks
sayalijoshi27 Oct 15, 2025
a91eb7b
Refactor project service and UI components for improved readability a…
sauravraw Oct 29, 2025
ac72604
Merge branch 'dev' of https://github.com/contentstack/migration-v2 in…
sauravraw Oct 31, 2025
1bfde71
chore: update package-lock.json to mark several dependencies as dev d…
sauravraw Oct 31, 2025
cdcedf4
refactor: enhance error handling and validation in project service me…
sauravraw Nov 3, 2025
5f1eeff
fix: correct comment typo in contentTypeMapper for dropdown advanced …
sauravraw Nov 3, 2025
3eb1eb0
chore: update @contentstack/cli and related dependencies in package.j…
sauravraw Nov 3, 2025
8e01696
refactor: simplify nested zip file validation logic in sitecoreValida…
sauravraw Nov 3, 2025
aa8e183
bugfix: added initial key to enable advance field reset
yashin4112 Nov 3, 2025
860ee72
Merge pull request #821 from contentstack/bugfix/sitecore-zip-issue
sayalijoshi27 Nov 3, 2025
f8ac7db
Merge pull request #822 from contentstack/bugfix/aem-752
sayalijoshi27 Nov 3, 2025
b3fa09a
refactor: add isNumber field to SearchComponent and TeaserComponent f…
shobhitupadhyayy Nov 4, 2025
087d330
[CMG-736] - Multiple clicks on buttons or links that return results s…
sayalijoshi27 Nov 4, 2025
a6d356e
[CMG-736] - Multiple clicks on buttons or links that return results s…
sayalijoshi27 Nov 4, 2025
d6c63ee
conflict resolved
sayalijoshi27 Nov 4, 2025
b46532a
Replaced Master word with default
sayalijoshi27 Nov 4, 2025
595e9f4
refactor: improve state management and formatting in AdvancePropertis…
shobhitupadhyayy Nov 4, 2025
fd4d592
Merge pull request #823 from contentstack/feat/custom-app
umeshmore45 Nov 4, 2025
c46761f
conflict resolved
sayalijoshi27 Nov 4, 2025
d12b956
copilot suggestions added
sayalijoshi27 Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[CMG-736] - Multiple clicks on buttons or links that return results s…
…hould be prevented.
  • Loading branch information
sayalijoshi27 committed Nov 4, 2025
commit a6d356eef3130e0400d892ef08f99de2ffea23c1
257 changes: 151 additions & 106 deletions ui/src/components/ContentMapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,8 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref:
const [isCsCTypeUpdated, setsCsCTypeUpdated] = useState<boolean>(false);
const [isLoadingSaveButton, setisLoadingSaveButton] = useState<boolean>(false);
const [activeFilter, setActiveFilter] = useState<string>('');
const [isAllCheck, setIsAllCheck] = useState<boolean>(false);
const [isAllCheck, setIsAllCheck] = useState<boolean>(false);
const [isResetFetch, setIsResetFetch] = useState<boolean>(false);


/** ALL HOOKS Here */
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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 {
Expand All @@ -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({
Expand All @@ -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'
});
Expand All @@ -2256,13 +2194,17 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref:
} catch (error) {
console.error(error);
return error;
} finally {
// Re-enable icon after API completes
setIsResetFetch(false);
}
} else {
try {
const { data, status } = await getExistingGlobalFields(projectId, otherContentType?.id ?? '');

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({
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -2621,7 +2666,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref:
<Tooltip content={'Fetch content types from destination stack'} position="top">
<Button className='icon-padding' buttonType="light" icon={onlyIcon ? "v2-FetchTemplate" : ''}
version="v2" onlyIcon={true} onlyIconHoverColor={'primary'}
size='small' onClick={handleFetchContentType}>
size='small' onClick={handleFetchContentType} disabled={isResetFetch}>
</Button>
</Tooltip>
</>
Expand All @@ -2630,7 +2675,7 @@ const ContentMapper = forwardRef(({ handleStepChange }: contentMapperProps, ref:
<Tooltip content={'Reset to system mapping'} position="top">
<Button className='icon-padding' buttonType="light" icon={onlyIcon ? "v2-ResetReverse" : ''}
version="v2" onlyIcon={true} onlyIconHoverColor={'primary'}
size='small' onClick={handleResetContentType}></Button>
size='small' onClick={handleResetContentType} disabled={isResetFetch}></Button>
</Tooltip>
</div>
),
Expand Down