From cf64f7154ce12ac54a5168d764baee6df906f513 Mon Sep 17 00:00:00 2001 From: AishDani Date: Wed, 5 Jun 2024 15:32:55 +0530 Subject: [PATCH 1/2] feat:created horizontal stepper component --- ui/src/components/DestinationStack/index.tsx | 6 +- ui/src/components/LegacyCms/index.tsx | 8 +- .../NewMigration/NewMigrationWrapper.tsx | 146 ++++++++---------- .../HorizontalStepper/HorizontalStepper.scss | 105 +++++++++++++ .../HorizontalStepper/HorizontalStepper.tsx | 142 +++++++++++++++++ 5 files changed, 320 insertions(+), 87 deletions(-) create mode 100644 ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.scss create mode 100644 ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx diff --git a/ui/src/components/DestinationStack/index.tsx b/ui/src/components/DestinationStack/index.tsx index 2384efbc..5a8ea2ff 100644 --- a/ui/src/components/DestinationStack/index.tsx +++ b/ui/src/components/DestinationStack/index.tsx @@ -29,12 +29,14 @@ type DestinationStackComponentProps = { destination_stack: string; org_id: string; projectData: MigrationResponse; + handleStepChange: (currentStep: number) => void; }; const DestinationStackComponent = ({ destination_stack, org_id, - projectData + projectData, + handleStepChange }: DestinationStackComponentProps) => { /** ALL HOOKS HERE */ const [isLoading, setIsLoading] = useState(true); @@ -66,7 +68,7 @@ const DestinationStackComponent = ({ await updateDestinationStack(selectedOrganisation?.value, projectId, { stack_api_key: newMigrationData?.destination_stack?.selectedStack?.value }); - + handleStepChange(2); const res = await updateCurrentStepData(selectedOrganisation?.value, projectId); if (res) { const url = `/projects/${projectId}/migration/steps/3`; diff --git a/ui/src/components/LegacyCms/index.tsx b/ui/src/components/LegacyCms/index.tsx index 31275556..70869ba0 100644 --- a/ui/src/components/LegacyCms/index.tsx +++ b/ui/src/components/LegacyCms/index.tsx @@ -28,9 +28,10 @@ import { setMigrationData, setNewMigrationData, updateMigrationData } from '../. type LegacyCMSComponentProps = { legacyCMSData: any; projectData: MigrationResponse; + handleStepChange: (currentStep: number) => void; }; -const LegacyCMSComponent = ({ legacyCMSData, projectData }: LegacyCMSComponentProps) => { +const LegacyCMSComponent = ({ legacyCMSData, projectData, handleStepChange }: LegacyCMSComponentProps) => { //react-redux apis const migrationData = useSelector((state:RootState)=>state?.migration?.migrationData); @@ -59,7 +60,7 @@ const LegacyCMSComponent = ({ legacyCMSData, projectData }: LegacyCMSComponentPr }; // handle on proceed to destination stack - const handleOnClick = async (event: MouseEvent) => { + const handleOnClick = async (event: MouseEvent,handleStepChange:any ) => { event.preventDefault(); //Update Data in backend @@ -67,6 +68,7 @@ const LegacyCMSComponent = ({ legacyCMSData, projectData }: LegacyCMSComponentPr legacy_cms: newMigrationData?.legacy_cms?.selectedCms?.cms_id }); const res = await updateCurrentStepData(selectedOrganisation.value, projectId); + handleStepChange(1); if (res) { const url = `/projects/${projectId}/migration/steps/2`; navigate(url, { replace: true }); @@ -243,7 +245,7 @@ const LegacyCMSComponent = ({ legacyCMSData, projectData }: LegacyCMSComponentPr diff --git a/ui/src/components/Migrations/NewMigration/NewMigrationWrapper.tsx b/ui/src/components/Migrations/NewMigration/NewMigrationWrapper.tsx index 683a7c3f..7afa7bfb 100644 --- a/ui/src/components/Migrations/NewMigration/NewMigrationWrapper.tsx +++ b/ui/src/components/Migrations/NewMigration/NewMigrationWrapper.tsx @@ -1,10 +1,10 @@ // Libraries -import { lazy, useContext, useEffect, useState } from 'react'; -import { Navigate, Outlet, Params, useNavigate, useParams } from 'react-router'; -import { UseDispatch, useSelector } from 'react-redux'; +import { lazy, useEffect, useState, useRef } from 'react'; +import { Outlet, Params, useNavigate, useParams } from 'react-router'; +import { useSelector } from 'react-redux'; //venus components -import { PageLayout, Stepper } from '@contentstack/venus-components'; +import { Button, PageLayout, Stepper } from '@contentstack/venus-components'; // Services import { getMigrationData } from '../../../services/api/migration.service'; @@ -15,8 +15,6 @@ import { getCMSDataFromFile } from '../../../cmsData/cmsSelector'; import { CS_ENTRIES } from '../../../utilities/constants'; import { isEmptyString, validateArray } from '../../../utilities/functions'; -// Context -import { AppContext } from '../../../context/app/app.context'; // Interface import { @@ -36,7 +34,9 @@ import { defaultMigrationResponse } from '../../../services/api/service.interface'; import { useDispatch } from 'react-redux'; -import { setMigrationData, updateMigrationData } from '../../../store/slice/migrationDataSlice'; +import { updateMigrationData } from '../../../store/slice/migrationDataSlice'; +import HorizontalStepper from '../../Stepper/HorizontalStepper/HorizontalStepper'; +import { RootState } from '../../../store'; const defaultStep = '1'; @@ -51,57 +51,62 @@ const MigrationExecutionComponentLazyLoaded = lazy( () => import('../../../components/MigrationExecution') ); -const getComponent = ( - params: Params, - projectData: MigrationResponse, - stepId = defaultStep -) => { - switch (stepId) { - case '1': { - return ( - - ); - } - case '2': { - return ( - - ); - } - case '3': { - return ; - } - case '4': { - return ; - } - case '5': { - return ; - } - default: { - const url = `/projects/${params?.projectId}/migration/steps/${defaultStep}`; - return ; - } - } -}; +const createStepper = (projectData:any,handleStepChange: (currentStep: number) => void) => { + const steps = [ + { + data: , + id:'1', + title:'Legacy CMS' + }, + { + data: , + id:'2', + title:'Destination Stack' + }, + { + data: , + id:'3', + title:'Content Mapping' + }, + { + data: , + id:'4', + title:'Test Migration' + }, + { + data: , + id:'5', + title:'Migration Execution' + }, + + + ] + return steps; +} const NewMigrationWrapper = () => { const params: Params = useParams(); const navigate = useNavigate(); const dispatch = useDispatch(); + const stepperRef = useRef(null); const [showInfo, setShowInfo] = useState(false); const [isLoading, setIsLoading] = useState(false); const [projectData, setProjectData] = useState(defaultMigrationResponse); - const migrationData = useSelector((state:any)=>state?.migration?.migrationData); - const selectedOrganisation = useSelector((state:any)=>state?.authentication?.selectedOrganisation); + /******** ALL CONTEXT DATA **********/ + //const { migrationData, updateMigrationData, selectedOrganisation } = useContext(AppContext); + const migrationData = useSelector((state: RootState)=>state?.migration?.migrationData); + const selectedOrganisation = useSelector((state: RootState)=>state?.authentication?.selectedOrganisation); //Fetch project data const fetchProjectData = async () => { @@ -133,7 +138,7 @@ const NewMigrationWrapper = () => { ? data?.all_steps?.find((step: IFlowStep) => `${step.name}` === params?.stepId) : DEFAULT_IFLOWSTEP; - + dispatch(updateMigrationData({ allFlowSteps: data?.all_steps, currentFlowStep: currentFlowStep, @@ -149,6 +154,14 @@ const NewMigrationWrapper = () => { navigate(`settings`); }; + const handleClick = () => { + + // Call handleStepChange function + const x : string | undefined= params.stepId + const currentStep : number = parseInt(x || ''); + stepperRef?.current?.handleStepChange(currentStep-1); +}; + useEffect(() => { fetchData(); }, []); @@ -158,32 +171,7 @@ const NewMigrationWrapper = () => { }, [params?.stepId, params?.projectId, selectedOrganisation.value]); const { settings, migration_steps_heading } = migrationData; - const steps = [ - { - data: ()=> { return ( - - )}, - id:'1', - title:'Legacy CMS' - } - ] - const content = { - component: ( - <> - {isLoading ? ( - <> // Consider using a loading indicator here - ) : ( -
- {getComponent(params, projectData, params?.stepId)} - -
- )} - - ) - }; + const leftSidebar = { component: ( @@ -248,13 +236,7 @@ const NewMigrationWrapper = () => {
- +
@@ -263,4 +245,4 @@ const NewMigrationWrapper = () => { ); }; -export default NewMigrationWrapper; +export default NewMigrationWrapper; \ No newline at end of file diff --git a/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.scss b/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.scss new file mode 100644 index 00000000..31cbcefb --- /dev/null +++ b/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.scss @@ -0,0 +1,105 @@ +@import '../../../scss/variables'; + +.stepper { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0px; + margin: 10px; + @media (max-width: 768px) { + flex-direction: column; + align-items: flex-start; + } + +} + +.stepWrapperContainer { + display: flex; + align-items: center; + justify-content: center; + position: relative; + flex: 0.6; + margin: 0 ; + +} + +.stepWrapper { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + position: relative; + z-index: 1; + + &.completed .badge{ + background-color:$color-brand-primary-base; + } + &.active .badge { + background-color: white; + border: 0.063rem solid $color-brand-primary-base; + color: #6E6B86; + width: 40px; + height: 40px; + } + + .badge { + display: flex; + justify-content: center; + align-items: center; + background-color: white; + border: 0.063rem solid $color-brand-primary-base; + color: #6E6B86; + width: 40px; + height: 40px; + border-radius: 50%; + margin-right: 0px; + } + + .stepper-title { + margin-top: 8px; + text-align: center; + font-size: 14px; + overflow: hidden; + white-space: nowrap; + + } + &.disableEvents{ + cursor: not-allowed; + opacity: 1 !important; + color: #475161 !important; + } +} +.stepWrapperContainer:has(.disableEvents) { + cursor: not-allowed; +} + +.connector { + height: 4px; + background-color: $color-brand-primary-base; + flex-grow: 1; + margin: 0; + margin-bottom: 30px; + border-radius: 4px; + margin-left: -40px; + margin-right: -40px; +} + +.circle-title-wrapper { + display: flex; + flex-direction: column; + align-items: center; +} +.icon-wrapper{ + width: 16px; + height: 11px; +} +.disabled-connector{ + background-color: $color-base-gray-40; + flex-grow: 1; + margin: 0; + margin-bottom: 30px; + border-radius: 4px; + height: 4px; + margin-left: -40px; + margin-right: -40px; +} \ No newline at end of file diff --git a/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx b/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx new file mode 100644 index 00000000..d9f6657b --- /dev/null +++ b/ui/src/components/Stepper/HorizontalStepper/HorizontalStepper.tsx @@ -0,0 +1,142 @@ +import React, { useState, useImperativeHandle, forwardRef, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import './HorizontalStepper.scss'; +import { Icon } from '@contentstack/venus-components'; + +export enum StepStatus { + ACTIVE = "ACTIVE", + COMPLETED = "COMPLETED", + DISABLED = "DISABLED" +} + +export type stepsArray = { + id: string; + title?: JSX.Element | string; + data?: React.ReactElement; + status?: StepStatus; +}; + +export type stepperProps = { + steps: Array; + className?: string; + emptyStateMsg?: string | JSX.Element; + stepComponentProps?: any; + hideTabView?: boolean; + stepContentClassName?: string; + stepTitleClassName?: string; + testId?: string; +}; + +export type HorizontalStepperHandles = { + handleStepChange: (currentStep: number) => void; +}; + +const HorizontalStepper = forwardRef( + (props: stepperProps, ref: React.ForwardedRef) => { + const { steps, className, emptyStateMsg, stepComponentProps, hideTabView, testId } = props; + const [showStep, setShowStep] = useState(0); + const [stepsCompleted, setStepsCompleted] = useState([]); + const { stepId } = useParams<{ stepId: any }>(); + + const navigate = useNavigate(); + const { projectId = '' } = useParams(); + + useEffect(() => { + const stepIndex = parseInt(stepId, 10) - 1; + if (!isNaN(stepIndex) && stepIndex >= 0 && stepIndex < steps?.length) { + setShowStep(stepIndex); + setStepsCompleted(prev => { + const updatedStepsCompleted = [...prev]; + for (let i = 0; i < stepIndex; i++) { + if (!updatedStepsCompleted?.includes(i)) { + updatedStepsCompleted?.push(i); + } + } + return updatedStepsCompleted; + }); + + } + }, [stepId]); + + useImperativeHandle(ref, () => ({ + handleStepChange: (currentStep: number) => { + setShowStep(currentStep+ 1); + setStepsCompleted(prev => { + const updatedStepsCompleted = [...prev]; + for (let i = 0; i <= currentStep; i++) { + if (!updatedStepsCompleted?.includes(i)) { + updatedStepsCompleted?.push(i); + } + } + return updatedStepsCompleted; + }); + } + })); + + const setTabStep = (idx: number) => { + + if (stepsCompleted?.includes(idx) || stepsCompleted?.length === idx) { + setShowStep(idx); + const url = `/projects/${projectId}/migration/steps/${idx + 1}`; + navigate(url, { replace: true }); + } + }; + + const StepsTitleCreator: React.FC = () => ( +
+ {steps?.map(({ id, title }, idx: number) => { + + + const completedClass = stepsCompleted?.includes(idx) ? 'completed' : ''; + const activeClass = idx === showStep && !stepsCompleted?.includes(idx)? 'active' : ''; + const disableClass = + !stepsCompleted.includes(idx) && idx !== showStep && !stepsCompleted?.includes(idx - 1) + ? 'disableEvents' + : ''; + + return ( + +
+
setTabStep(idx)} + > +
+
+ {completedClass ? ( +
+ +
+ ) : ( + <>{idx + 1} + )} +
+
{title}
+
+
+
+ {idx < steps?.length - 1 &&
} +
+ ); + })} +
+ ); + + return ( +
+ {steps?.length ? ( + <> + {!hideTabView && } +
+ {steps[showStep]?.data} +
+ + ) : ( + emptyStateMsg + )} +
+ ); + } +); +HorizontalStepper.displayName = 'HorizontalStepper'; +export default HorizontalStepper; From b7ad5198e7de6612db157f5ad06f49f1ef45e8b4 Mon Sep 17 00:00:00 2001 From: AishDani Date: Wed, 5 Jun 2024 15:49:14 +0530 Subject: [PATCH 2/2] refactor:removed commented code --- .../components/Migrations/NewMigration/NewMigrationWrapper.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/src/components/Migrations/NewMigration/NewMigrationWrapper.tsx b/ui/src/components/Migrations/NewMigration/NewMigrationWrapper.tsx index 7afa7bfb..3fe3c48b 100644 --- a/ui/src/components/Migrations/NewMigration/NewMigrationWrapper.tsx +++ b/ui/src/components/Migrations/NewMigration/NewMigrationWrapper.tsx @@ -103,8 +103,6 @@ const NewMigrationWrapper = () => { const [isLoading, setIsLoading] = useState(false); const [projectData, setProjectData] = useState(defaultMigrationResponse); - /******** ALL CONTEXT DATA **********/ - //const { migrationData, updateMigrationData, selectedOrganisation } = useContext(AppContext); const migrationData = useSelector((state: RootState)=>state?.migration?.migrationData); const selectedOrganisation = useSelector((state: RootState)=>state?.authentication?.selectedOrganisation);