Compare commits
1 Commits
master
...
chris/FAL-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49c28de286 |
@@ -35,6 +35,7 @@ import { ContentType } from './library-authoring/routes';
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import './index.scss';
|
||||
import { LegacyLibMigrationPage } from './legacy-libraries-migration/LegacyLibMigrationPage';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -65,6 +66,7 @@ const App = () => {
|
||||
<Route path="/home" element={<StudioHome />} />
|
||||
<Route path="/libraries" element={<StudioHome />} />
|
||||
<Route path="/libraries-v1" element={<StudioHome />} />
|
||||
<Route path="/libraries-v1/migrate" element={<LegacyLibMigrationPage />} />
|
||||
<Route path="/library/create" element={<CreateLibrary />} />
|
||||
<Route path="/library/:libraryId/*" element={<LibraryLayout />} />
|
||||
<Route
|
||||
|
||||
7
src/legacy-libraries-migration/ConfirmationView.tsx
Normal file
7
src/legacy-libraries-migration/ConfirmationView.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Container } from '@openedx/paragon';
|
||||
|
||||
export const ConfirmatiobView = () => (
|
||||
<Container>
|
||||
Confirmation View
|
||||
</Container>
|
||||
);
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
initializeMocks,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@src/testUtils';
|
||||
|
||||
import { LegacyLibMigrationPage } from './LegacyLibMigrationPage';
|
||||
|
||||
const path = '/libraries-v1/migrate/*';
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
const renderPage = () => (
|
||||
render(<LegacyLibMigrationPage />, { path })
|
||||
);
|
||||
|
||||
describe('<LegacyLibMigrationPage />', () => {
|
||||
beforeEach(() => {
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
it('should render legacy library migration page', async () => {
|
||||
renderPage();
|
||||
// Should render the title
|
||||
expect(await screen.findByText('Migrate Legacy Libraries')).toBeInTheDocument();
|
||||
// Should render the Migration Steps Viewer
|
||||
expect(screen.getByText(/select legacy libraries/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/select destination/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/confirm/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should cancel the migration', async () => {
|
||||
renderPage();
|
||||
expect(await screen.findByText('Migrate Legacy Libraries')).toBeInTheDocument();
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: /cancel/i });
|
||||
cancelButton.click();
|
||||
|
||||
// Should show exit confirmation modal
|
||||
expect(await screen.findByText('Exit Migration?')).toBeInTheDocument();
|
||||
|
||||
// Close exit confirmation modal
|
||||
const continueButton = screen.getByRole('button', { name: /continue migrating/i });
|
||||
continueButton.click();
|
||||
expect(mockNavigate).not.toHaveBeenCalled();
|
||||
|
||||
cancelButton.click();
|
||||
|
||||
// Should navigate to legacy libraries tab on studio home
|
||||
expect(await screen.findByText('Exit Migration?')).toBeInTheDocument();
|
||||
const exitButton = screen.getByRole('button', { name: /exit/i });
|
||||
exitButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/libraries-v1');
|
||||
});
|
||||
});
|
||||
});
|
||||
153
src/legacy-libraries-migration/LegacyLibMigrationPage.tsx
Normal file
153
src/legacy-libraries-migration/LegacyLibMigrationPage.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
ActionRow,
|
||||
Button,
|
||||
Container,
|
||||
ModalDialog,
|
||||
Stepper,
|
||||
useToggle,
|
||||
} from '@openedx/paragon';
|
||||
import Header from '@src/header';
|
||||
import SubHeader from '@src/generic/sub-header/SubHeader';
|
||||
|
||||
import messages from './messages';
|
||||
import { SelectLegacyLibraryView } from './SelectLegacyLibraryView';
|
||||
import { SelectDestinationView } from './SelectDestinationView';
|
||||
import { ConfirmatiobView } from './ConfirmationView';
|
||||
import { MigrationStepsViewer } from './MigrationStepsViewer';
|
||||
|
||||
export type MigrationStep = 'select-libraries' | 'select-destination' | 'confirmation-view';
|
||||
|
||||
const ExitModal = ({
|
||||
isExitModalOpen,
|
||||
closeExitModal,
|
||||
}: {
|
||||
isExitModalOpen: boolean,
|
||||
closeExitModal: () => void,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleExit = useCallback(() => {
|
||||
navigate('/libraries-v1');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ModalDialog
|
||||
title={intl.formatMessage(messages.exitModalTitle)}
|
||||
isOpen={isExitModalOpen}
|
||||
onClose={closeExitModal}
|
||||
isOverflowVisible={false}
|
||||
hasCloseButton={false}
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title>
|
||||
{intl.formatMessage(messages.exitModalTitle)}
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>
|
||||
{intl.formatMessage(messages.exitModalBodyText)}
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="tertiary">
|
||||
{intl.formatMessage(messages.exitModalCancelText)}
|
||||
</ModalDialog.CloseButton>
|
||||
<Button onClick={handleExit}>
|
||||
{intl.formatMessage(messages.exitModalConfirmText)}
|
||||
</Button>
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const LegacyLibMigrationPage = () => {
|
||||
const intl = useIntl();
|
||||
const [currentStep, setCurrentStep] = useState<MigrationStep>('select-libraries');
|
||||
const [isExitModalOpen, openExitModal, closeExitModal] = useToggle(false);
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
switch (currentStep) {
|
||||
case 'select-libraries':
|
||||
setCurrentStep('select-destination');
|
||||
break;
|
||||
case 'select-destination':
|
||||
setCurrentStep('confirmation-view');
|
||||
break;
|
||||
case 'confirmation-view':
|
||||
// Handle confirm
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, [currentStep, setCurrentStep]);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
switch (currentStep) {
|
||||
case 'select-libraries':
|
||||
openExitModal();
|
||||
break;
|
||||
case 'select-destination':
|
||||
setCurrentStep('select-libraries');
|
||||
break;
|
||||
case 'confirmation-view':
|
||||
setCurrentStep('select-destination');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, [currentStep, setCurrentStep]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="d-flex">
|
||||
<div className="flex-grow-1">
|
||||
<Helmet>
|
||||
<title>
|
||||
{intl.formatMessage(messages.siteTitle)}
|
||||
</title>
|
||||
</Helmet>
|
||||
<Header isHiddenMainMenu />
|
||||
<Container className="px-6 mt-5 mb-5">
|
||||
<SubHeader
|
||||
title={intl.formatMessage(messages.siteTitle)}
|
||||
/>
|
||||
<MigrationStepsViewer currentStep={currentStep} />
|
||||
<Stepper activeKey={currentStep}>
|
||||
<Stepper.Step eventKey="select-libraries" title="Select Legacy Libraries">
|
||||
<SelectLegacyLibraryView />
|
||||
</Stepper.Step>
|
||||
<Stepper.Step eventKey="select-destination" title="Select Destination">
|
||||
<SelectDestinationView />
|
||||
</Stepper.Step>
|
||||
<Stepper.Step eventKey="confirmation-view" title="Confirmation">
|
||||
<ConfirmatiobView />
|
||||
</Stepper.Step>
|
||||
</Stepper>
|
||||
<div className="d-flex justify-content-between">
|
||||
<Button variant="outline-primary" onClick={handleBack}>
|
||||
{currentStep === 'select-libraries'
|
||||
? intl.formatMessage(messages.cancel)
|
||||
: intl.formatMessage(messages.back)}
|
||||
</Button>
|
||||
<Button onClick={handleNext}>
|
||||
{currentStep === 'confirmation-view'
|
||||
? intl.formatMessage(messages.confirm)
|
||||
: intl.formatMessage(messages.next)}
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
<ExitModal
|
||||
isExitModalOpen={isExitModalOpen}
|
||||
closeExitModal={closeExitModal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
80
src/legacy-libraries-migration/MigrationStepsViewer.tsx
Normal file
80
src/legacy-libraries-migration/MigrationStepsViewer.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import type { MessageDescriptor } from 'react-intl';
|
||||
import {
|
||||
Bubble,
|
||||
Container,
|
||||
Icon,
|
||||
Stack,
|
||||
} from '@openedx/paragon';
|
||||
import { Check } from '@openedx/paragon/icons';
|
||||
|
||||
import type { MigrationStep } from './LegacyLibMigrationPage';
|
||||
import messages from './messages';
|
||||
|
||||
export const MigrationStepsViewer = ({ currentStep }: { currentStep: MigrationStep }) => {
|
||||
const intl = useIntl();
|
||||
const stepNumbers: Record<MigrationStep, number> = {
|
||||
'select-libraries': 1,
|
||||
'select-destination': 2,
|
||||
'confirmation-view': 3,
|
||||
};
|
||||
const stepNames: Record<MigrationStep, MessageDescriptor> = {
|
||||
'select-libraries': messages.selectLegacyLibrariesStepTitle,
|
||||
'select-destination': messages.selectDestinationStepTitle,
|
||||
'confirmation-view': messages.confirmStepTitle,
|
||||
};
|
||||
|
||||
const checkStep = (step: MigrationStep) => {
|
||||
if (currentStep === step) {
|
||||
return 'current';
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 'select-libraries':
|
||||
// If is not current, then is done.
|
||||
return 'done';
|
||||
case 'select-destination':
|
||||
if (currentStep === 'select-libraries') {
|
||||
return 'disabled';
|
||||
}
|
||||
return 'done';
|
||||
case 'confirmation-view':
|
||||
// If is not current, then is disabled.
|
||||
return 'disabled';
|
||||
default:
|
||||
return 'disabled';
|
||||
}
|
||||
|
||||
return 'disabled';
|
||||
};
|
||||
|
||||
const buildStep = (step: MigrationStep) => {
|
||||
const stepStatus = checkStep(step);
|
||||
return (
|
||||
<Stack direction="horizontal">
|
||||
<Bubble className="mr-2" disabled={stepStatus === 'disabled'}>
|
||||
{stepStatus === 'done' ? (
|
||||
<Icon src={Check} />
|
||||
) : (
|
||||
stepNumbers[step]
|
||||
)}
|
||||
</Bubble>
|
||||
<div>
|
||||
<span>
|
||||
{intl.formatMessage(stepNames[step])}
|
||||
</span>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className="d-flex justify-content-center migration-steps-viewer">
|
||||
{buildStep('select-libraries')}
|
||||
<hr className="ml-3 mr-3" style={{ width: '80px' }} />
|
||||
{buildStep('select-destination')}
|
||||
<hr className="ml-3 mr-3" style={{ width: '80px' }} />
|
||||
{buildStep('confirmation-view')}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
7
src/legacy-libraries-migration/SelectDestinationView.tsx
Normal file
7
src/legacy-libraries-migration/SelectDestinationView.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Container } from '@openedx/paragon';
|
||||
|
||||
export const SelectDestinationView = () => (
|
||||
<Container>
|
||||
SelectDestinationView
|
||||
</Container>
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Container } from '@openedx/paragon';
|
||||
|
||||
export const SelectLegacyLibraryView = () => (
|
||||
<Container>
|
||||
Select Legacy LibraryStep
|
||||
</Container>
|
||||
);
|
||||
66
src/legacy-libraries-migration/messages.ts
Normal file
66
src/legacy-libraries-migration/messages.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
siteTitle: {
|
||||
id: 'legacy-libraries-migration.site-title',
|
||||
defaultMessage: 'Migrate Legacy Libraries',
|
||||
description: 'Title for the page to migrate legacy libraries.',
|
||||
},
|
||||
cancel: {
|
||||
id: 'legacy-libraries-migration.button.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
description: 'Text of the button to cancel the migration.',
|
||||
},
|
||||
next: {
|
||||
id: 'legacy-libraries-migration.button.next',
|
||||
defaultMessage: 'Next',
|
||||
description: 'Text of the button to go to the next step of the migration.',
|
||||
},
|
||||
back: {
|
||||
id: 'legacy-libraries-migration.button.back',
|
||||
defaultMessage: 'Back',
|
||||
description: 'Text of the button to go back to the previous step of the migration.',
|
||||
},
|
||||
confirm: {
|
||||
id: 'legacy-libraries-migration.button.confirm',
|
||||
defaultMessage: 'Confirm',
|
||||
description: 'Text of the button to confirm the migration.',
|
||||
},
|
||||
selectLegacyLibrariesStepTitle: {
|
||||
id: 'legacy-libraries-migration.select-legacy-libraries-step.title',
|
||||
defaultMessage: 'Select Legacy Libraries',
|
||||
description: 'Title of the Select Legacy Libraries step',
|
||||
},
|
||||
selectDestinationStepTitle: {
|
||||
id: 'legacy-libraries-migration.select-destination-step.title',
|
||||
defaultMessage: 'Select Destination',
|
||||
description: 'Title of the Select Destination step',
|
||||
},
|
||||
confirmStepTitle: {
|
||||
id: 'legacy-libraries-migration.confirm-step.title',
|
||||
defaultMessage: 'Confirm',
|
||||
description: 'Title of the Confirm step',
|
||||
},
|
||||
exitModalTitle: {
|
||||
id: 'legacy-libraries-migration.exit-modal.title',
|
||||
defaultMessage: 'Exit Migration?',
|
||||
description: 'Title of the modal to confirm exit the migration.',
|
||||
},
|
||||
exitModalBodyText: {
|
||||
id: 'legacy-libraries-migration.exit-modal.body',
|
||||
defaultMessage: 'By exiting, all changes will be lost and no libraries will be migrated.',
|
||||
description: 'Body text of the modal to confirm exit the migration.',
|
||||
},
|
||||
exitModalCancelText: {
|
||||
id: 'legacy-libraries-migration.exit-modal.button.cancel.text',
|
||||
defaultMessage: 'Continue Migrating',
|
||||
description: 'Text for the button to close the modal to confirm exit the migration.',
|
||||
},
|
||||
exitModalConfirmText: {
|
||||
id: 'legacy-libraries-migration.exit-modal.button.confirm.text',
|
||||
defaultMessage: 'Exit',
|
||||
description: 'Text for the button to confirm exit the migration.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
Reference in New Issue
Block a user