Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/legacy-libraries-migration/ConfirmationView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Container } from '@openedx/paragon';

export const ConfirmatiobView = () => (
<Container>
Confirmation View
</Container>
);
63 changes: 63 additions & 0 deletions src/legacy-libraries-migration/LegacyLibMigrationPage.test.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 153 additions & 0 deletions src/legacy-libraries-migration/LegacyLibMigrationPage.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 80 additions & 0 deletions src/legacy-libraries-migration/MigrationStepsViewer.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 0 deletions src/legacy-libraries-migration/SelectDestinationView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Container } from '@openedx/paragon';

export const SelectDestinationView = () => (
<Container>
SelectDestinationView
</Container>
);
7 changes: 7 additions & 0 deletions src/legacy-libraries-migration/SelectLegacyLibraryView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Container } from '@openedx/paragon';

export const SelectLegacyLibraryView = () => (
<Container>
Select Legacy LibraryStep
</Container>
);
Loading