♻️(frontend) add modal confirmation restore version

Add modal confirmation restore version explaining
that the current version will be replaced
by the selected version, and that some data
may be lost.
This commit is contained in:
Anthony LC
2024-09-02 16:02:21 +02:00
committed by Anthony LC
parent accbda44e2
commit 296b5dbf59
6 changed files with 193 additions and 94 deletions

View File

@@ -119,6 +119,14 @@ test.describe('Doc Version', () => {
await panel.getByLabel('Open the version options').click();
await page.getByText('Restore the version').click();
await expect(page.getByText('Restore this version?')).toBeVisible();
await page
.getByRole('button', {
name: 'Restore',
})
.click();
await expect(panel.locator('li')).toHaveCount(3);
await panel.getByText('Current version').click();

View File

@@ -5,12 +5,9 @@ import * as Y from 'yjs';
import { useUpdateDoc } from '@/features/docs/doc-management/';
import { KEY_LIST_DOC_VERSIONS } from '@/features/docs/doc-versioning';
import { useDocStore } from '../stores';
import { toBase64 } from '../utils';
const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => {
const { forceSave, setForceSave } = useDocStore();
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
});
@@ -68,18 +65,6 @@ const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => {
});
}, [doc, docId, updateDoc]);
useEffect(() => {
if (forceSave === 'false') {
return;
}
setForceSave('false');
if ((forceSave === 'current' && hasChanged()) || forceSave === 'version') {
saveDoc();
}
}, [forceSave, hasChanged, saveDoc, setForceSave]);
const timeout = useRef<NodeJS.Timeout>();
const router = useRouter();

View File

@@ -1,2 +1,3 @@
export * from './components';
export * from './stores';
export * from './utils';

View File

@@ -11,24 +11,16 @@ interface DocStore {
editor?: BlockNoteEditor;
}
type ForceSaveState = 'false' | 'version' | 'current';
export interface UseDocStore {
docsStore: {
[storeId: string]: DocStore;
};
createProvider: (storeId: string, initialDoc: Base64) => WebrtcProvider;
setStore: (storeId: string, props: Partial<DocStore>) => void;
forceSave: ForceSaveState;
setForceSave: (forceSave: ForceSaveState) => void;
}
export const useDocStore = create<UseDocStore>((set, get) => ({
docsStore: {},
forceSave: 'false',
setForceSave: (forceSave) => {
set(() => ({ forceSave }));
},
createProvider: (storeId: string, initialDoc: Base64) => {
const doc = new Y.Doc({
guid: storeId,

View File

@@ -0,0 +1,122 @@
import {
Alert,
Button,
Modal,
ModalSize,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { t } from 'i18next';
import { useRouter } from 'next/navigation';
import * as Y from 'yjs';
import { Box, Text } from '@/components';
import { toBase64, useDocStore } from '@/features/docs/doc-editor';
import { Doc, useUpdateDoc } from '@/features/docs/doc-management';
import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions';
import { Versions } from '../types';
import { revertUpdate } from '../utils';
interface ModalVersionProps {
onClose: () => void;
docId: Doc['id'];
versionId: Versions['version_id'];
}
export const ModalVersion = ({
onClose,
docId,
versionId,
}: ModalVersionProps) => {
const { toast } = useToastProvider();
const router = useRouter();
const { docsStore, setStore } = useDocStore();
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
onSuccess: () => {
const onDisplaySuccess = () => {
toast(t('Version restored successfully'), VariantType.SUCCESS);
router.push(`/docs/${docId}`);
};
if (!docsStore?.[docId]?.provider || !docsStore?.[versionId]?.provider) {
onDisplaySuccess();
return;
}
setStore(docId, {
editor: undefined,
});
revertUpdate(
docsStore[docId].provider.doc,
docsStore[docId].provider.doc,
docsStore[versionId].provider.doc,
);
onDisplaySuccess();
},
});
return (
<Modal
isOpen
closeOnClickOutside
hideCloseButton
leftActions={
<Button
aria-label={t('Close the modal')}
color="secondary"
fullWidth
onClick={() => onClose()}
>
{t('Cancel')}
</Button>
}
onClose={() => onClose()}
rightActions={
<Button
aria-label={t('Restore')}
color="primary"
fullWidth
onClick={() => {
const newDoc = toBase64(
Y.encodeStateAsUpdate(docsStore?.[versionId]?.provider.doc),
);
updateDoc({
id: docId,
content: newDoc,
});
}}
>
{t('Restore')}
</Button>
}
size={ModalSize.MEDIUM}
title={
<Box $gap="1rem">
<Text $isMaterialIcon $size="36px" $theme="primary">
restore
</Text>
<Text as="h2" $size="h3" $margin="none">
{t('Restore this version?')}
</Text>
</Box>
}
>
<Box aria-label={t('Modal confirmation to restore the version')}>
<Alert canClose={false} type={VariantType.WARNING}>
<Box>
<Text>
{t('Your current document will revert to this version.')}
</Text>
<Text>{t('If a member is editing, his works can be lost.')}</Text>
</Box>
</Alert>
</Box>
</Modal>
);
};

View File

@@ -4,11 +4,11 @@ import React, { PropsWithChildren, useState } from 'react';
import { Box, DropButton, IconOptions, StyledLink, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useDocStore } from '@/features/docs/doc-editor';
import { Doc } from '@/features/docs/doc-management';
import { Versions } from '../types';
import { revertUpdate } from '../utils';
import { ModalVersion } from './ModalVersion';
interface VersionItemProps {
docId: Doc['id'];
@@ -25,15 +25,16 @@ export const VersionItem = ({
link,
isActive,
}: VersionItemProps) => {
const { setForceSave, docsStore, setStore } = useDocStore();
const { colorsTokens } = useCunninghamTheme();
const [isDropOpen, setIsDropOpen] = useState(false);
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
return (
<Box
as="li"
$background={isActive ? colorsTokens()['primary-300'] : 'transparent'}
$css={`
<>
<Box
as="li"
$background={isActive ? colorsTokens()['primary-300'] : 'transparent'}
$css={`
border-left: 4px solid transparent;
border-bottom: 1px solid ${colorsTokens()['primary-100']};
&:hover{
@@ -41,71 +42,61 @@ export const VersionItem = ({
background: ${colorsTokens()['primary-300']};
}
`}
$hasTransition
$minWidth="13rem"
>
<Link href={link} isActive={isActive}>
<Box
$padding={{ vertical: '0.7rem', horizontal: 'small' }}
$align="center"
$direction="row"
$justify="space-between"
$width="100%"
>
<Box $direction="row" $gap="0.5rem" $align="center">
<Text $isMaterialIcon $size="24px" $theme="primary">
description
</Text>
<Text $weight="bold" $theme="primary" $size="m">
{text}
</Text>
$hasTransition
$minWidth="13rem"
>
<Link href={link} isActive={isActive}>
<Box
$padding={{ vertical: '0.7rem', horizontal: 'small' }}
$align="center"
$direction="row"
$justify="space-between"
$width="100%"
>
<Box $direction="row" $gap="0.5rem" $align="center">
<Text $isMaterialIcon $size="24px" $theme="primary">
description
</Text>
<Text $weight="bold" $theme="primary" $size="m">
{text}
</Text>
</Box>
{isActive && versionId && (
<DropButton
button={
<IconOptions
isOpen={isDropOpen}
aria-label={t('Open the version options')}
/>
}
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
isOpen={isDropOpen}
>
<Box>
<Button
onClick={() => {
setIsModalVersionOpen(true);
}}
color="primary-text"
icon={<span className="material-icons">save</span>}
size="small"
>
<Text $theme="primary">{t('Restore the version')}</Text>
</Button>
</Box>
</DropButton>
)}
</Box>
{isActive && versionId && (
<DropButton
button={
<IconOptions
isOpen={isDropOpen}
aria-label={t('Open the version options')}
/>
}
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
isOpen={isDropOpen}
>
<Box>
<Button
onClick={() => {
setIsDropOpen(false);
setForceSave(versionId ? 'version' : 'current');
if (
!docsStore?.[docId]?.provider ||
!docsStore?.[versionId]?.provider
) {
return;
}
setStore(docId, {
editor: undefined,
});
revertUpdate(
docsStore[docId].provider.doc,
docsStore[docId].provider.doc,
docsStore[versionId].provider.doc,
);
}}
color="primary-text"
icon={<span className="material-icons">save</span>}
size="small"
>
<Text $theme="primary">{t('Restore the version')}</Text>
</Button>
</Box>
</DropButton>
)}
</Box>
</Link>
</Box>
</Link>
</Box>
{isModalVersionOpen && versionId && (
<ModalVersion
onClose={() => setIsModalVersionOpen(false)}
docId={docId}
versionId={versionId}
/>
)}
</>
);
};