Skip to content

Commit 296b5db

Browse files
committed
♻️(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.
1 parent accbda4 commit 296b5db

File tree

6 files changed

+193
-94
lines changed

6 files changed

+193
-94
lines changed

src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ test.describe('Doc Version', () => {
119119
await panel.getByLabel('Open the version options').click();
120120
await page.getByText('Restore the version').click();
121121

122+
await expect(page.getByText('Restore this version?')).toBeVisible();
123+
124+
await page
125+
.getByRole('button', {
126+
name: 'Restore',
127+
})
128+
.click();
129+
122130
await expect(panel.locator('li')).toHaveCount(3);
123131

124132
await panel.getByText('Current version').click();

src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ import * as Y from 'yjs';
55
import { useUpdateDoc } from '@/features/docs/doc-management/';
66
import { KEY_LIST_DOC_VERSIONS } from '@/features/docs/doc-versioning';
77

8-
import { useDocStore } from '../stores';
98
import { toBase64 } from '../utils';
109

1110
const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => {
12-
const { forceSave, setForceSave } = useDocStore();
13-
1411
const { mutate: updateDoc } = useUpdateDoc({
1512
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
1613
});
@@ -68,18 +65,6 @@ const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => {
6865
});
6966
}, [doc, docId, updateDoc]);
7067

71-
useEffect(() => {
72-
if (forceSave === 'false') {
73-
return;
74-
}
75-
76-
setForceSave('false');
77-
78-
if ((forceSave === 'current' && hasChanged()) || forceSave === 'version') {
79-
saveDoc();
80-
}
81-
}, [forceSave, hasChanged, saveDoc, setForceSave]);
82-
8368
const timeout = useRef<NodeJS.Timeout>();
8469
const router = useRouter();
8570

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './components';
22
export * from './stores';
3+
export * from './utils';

src/frontend/apps/impress/src/features/docs/doc-editor/stores/useDocStore.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,16 @@ interface DocStore {
1111
editor?: BlockNoteEditor;
1212
}
1313

14-
type ForceSaveState = 'false' | 'version' | 'current';
15-
1614
export interface UseDocStore {
1715
docsStore: {
1816
[storeId: string]: DocStore;
1917
};
2018
createProvider: (storeId: string, initialDoc: Base64) => WebrtcProvider;
2119
setStore: (storeId: string, props: Partial<DocStore>) => void;
22-
forceSave: ForceSaveState;
23-
setForceSave: (forceSave: ForceSaveState) => void;
2420
}
2521

2622
export const useDocStore = create<UseDocStore>((set, get) => ({
2723
docsStore: {},
28-
forceSave: 'false',
29-
setForceSave: (forceSave) => {
30-
set(() => ({ forceSave }));
31-
},
3224
createProvider: (storeId: string, initialDoc: Base64) => {
3325
const doc = new Y.Doc({
3426
guid: storeId,
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
Alert,
3+
Button,
4+
Modal,
5+
ModalSize,
6+
VariantType,
7+
useToastProvider,
8+
} from '@openfun/cunningham-react';
9+
import { t } from 'i18next';
10+
import { useRouter } from 'next/navigation';
11+
import * as Y from 'yjs';
12+
13+
import { Box, Text } from '@/components';
14+
import { toBase64, useDocStore } from '@/features/docs/doc-editor';
15+
import { Doc, useUpdateDoc } from '@/features/docs/doc-management';
16+
17+
import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions';
18+
import { Versions } from '../types';
19+
import { revertUpdate } from '../utils';
20+
21+
interface ModalVersionProps {
22+
onClose: () => void;
23+
docId: Doc['id'];
24+
25+
versionId: Versions['version_id'];
26+
}
27+
28+
export const ModalVersion = ({
29+
onClose,
30+
docId,
31+
versionId,
32+
}: ModalVersionProps) => {
33+
const { toast } = useToastProvider();
34+
const router = useRouter();
35+
const { docsStore, setStore } = useDocStore();
36+
const { mutate: updateDoc } = useUpdateDoc({
37+
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
38+
onSuccess: () => {
39+
const onDisplaySuccess = () => {
40+
toast(t('Version restored successfully'), VariantType.SUCCESS);
41+
router.push(`/docs/${docId}`);
42+
};
43+
44+
if (!docsStore?.[docId]?.provider || !docsStore?.[versionId]?.provider) {
45+
onDisplaySuccess();
46+
return;
47+
}
48+
49+
setStore(docId, {
50+
editor: undefined,
51+
});
52+
53+
revertUpdate(
54+
docsStore[docId].provider.doc,
55+
docsStore[docId].provider.doc,
56+
docsStore[versionId].provider.doc,
57+
);
58+
59+
onDisplaySuccess();
60+
},
61+
});
62+
63+
return (
64+
<Modal
65+
isOpen
66+
closeOnClickOutside
67+
hideCloseButton
68+
leftActions={
69+
<Button
70+
aria-label={t('Close the modal')}
71+
color="secondary"
72+
fullWidth
73+
onClick={() => onClose()}
74+
>
75+
{t('Cancel')}
76+
</Button>
77+
}
78+
onClose={() => onClose()}
79+
rightActions={
80+
<Button
81+
aria-label={t('Restore')}
82+
color="primary"
83+
fullWidth
84+
onClick={() => {
85+
const newDoc = toBase64(
86+
Y.encodeStateAsUpdate(docsStore?.[versionId]?.provider.doc),
87+
);
88+
89+
updateDoc({
90+
id: docId,
91+
content: newDoc,
92+
});
93+
}}
94+
>
95+
{t('Restore')}
96+
</Button>
97+
}
98+
size={ModalSize.MEDIUM}
99+
title={
100+
<Box $gap="1rem">
101+
<Text $isMaterialIcon $size="36px" $theme="primary">
102+
restore
103+
</Text>
104+
<Text as="h2" $size="h3" $margin="none">
105+
{t('Restore this version?')}
106+
</Text>
107+
</Box>
108+
}
109+
>
110+
<Box aria-label={t('Modal confirmation to restore the version')}>
111+
<Alert canClose={false} type={VariantType.WARNING}>
112+
<Box>
113+
<Text>
114+
{t('Your current document will revert to this version.')}
115+
</Text>
116+
<Text>{t('If a member is editing, his works can be lost.')}</Text>
117+
</Box>
118+
</Alert>
119+
</Box>
120+
</Modal>
121+
);
122+
};

src/frontend/apps/impress/src/features/docs/doc-versioning/components/VersionItem.tsx

Lines changed: 62 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import React, { PropsWithChildren, useState } from 'react';
44

55
import { Box, DropButton, IconOptions, StyledLink, Text } from '@/components';
66
import { useCunninghamTheme } from '@/cunningham';
7-
import { useDocStore } from '@/features/docs/doc-editor';
87
import { Doc } from '@/features/docs/doc-management';
98

109
import { Versions } from '../types';
11-
import { revertUpdate } from '../utils';
10+
11+
import { ModalVersion } from './ModalVersion';
1212

1313
interface VersionItemProps {
1414
docId: Doc['id'];
@@ -25,87 +25,78 @@ export const VersionItem = ({
2525
link,
2626
isActive,
2727
}: VersionItemProps) => {
28-
const { setForceSave, docsStore, setStore } = useDocStore();
2928
const { colorsTokens } = useCunninghamTheme();
3029
const [isDropOpen, setIsDropOpen] = useState(false);
30+
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
3131

3232
return (
33-
<Box
34-
as="li"
35-
$background={isActive ? colorsTokens()['primary-300'] : 'transparent'}
36-
$css={`
33+
<>
34+
<Box
35+
as="li"
36+
$background={isActive ? colorsTokens()['primary-300'] : 'transparent'}
37+
$css={`
3738
border-left: 4px solid transparent;
3839
border-bottom: 1px solid ${colorsTokens()['primary-100']};
3940
&:hover{
4041
border-left: 4px solid ${colorsTokens()['primary-400']};
4142
background: ${colorsTokens()['primary-300']};
4243
}
4344
`}
44-
$hasTransition
45-
$minWidth="13rem"
46-
>
47-
<Link href={link} isActive={isActive}>
48-
<Box
49-
$padding={{ vertical: '0.7rem', horizontal: 'small' }}
50-
$align="center"
51-
$direction="row"
52-
$justify="space-between"
53-
$width="100%"
54-
>
55-
<Box $direction="row" $gap="0.5rem" $align="center">
56-
<Text $isMaterialIcon $size="24px" $theme="primary">
57-
description
58-
</Text>
59-
<Text $weight="bold" $theme="primary" $size="m">
60-
{text}
61-
</Text>
45+
$hasTransition
46+
$minWidth="13rem"
47+
>
48+
<Link href={link} isActive={isActive}>
49+
<Box
50+
$padding={{ vertical: '0.7rem', horizontal: 'small' }}
51+
$align="center"
52+
$direction="row"
53+
$justify="space-between"
54+
$width="100%"
55+
>
56+
<Box $direction="row" $gap="0.5rem" $align="center">
57+
<Text $isMaterialIcon $size="24px" $theme="primary">
58+
description
59+
</Text>
60+
<Text $weight="bold" $theme="primary" $size="m">
61+
{text}
62+
</Text>
63+
</Box>
64+
{isActive && versionId && (
65+
<DropButton
66+
button={
67+
<IconOptions
68+
isOpen={isDropOpen}
69+
aria-label={t('Open the version options')}
70+
/>
71+
}
72+
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
73+
isOpen={isDropOpen}
74+
>
75+
<Box>
76+
<Button
77+
onClick={() => {
78+
setIsModalVersionOpen(true);
79+
}}
80+
color="primary-text"
81+
icon={<span className="material-icons">save</span>}
82+
size="small"
83+
>
84+
<Text $theme="primary">{t('Restore the version')}</Text>
85+
</Button>
86+
</Box>
87+
</DropButton>
88+
)}
6289
</Box>
63-
{isActive && versionId && (
64-
<DropButton
65-
button={
66-
<IconOptions
67-
isOpen={isDropOpen}
68-
aria-label={t('Open the version options')}
69-
/>
70-
}
71-
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
72-
isOpen={isDropOpen}
73-
>
74-
<Box>
75-
<Button
76-
onClick={() => {
77-
setIsDropOpen(false);
78-
setForceSave(versionId ? 'version' : 'current');
79-
80-
if (
81-
!docsStore?.[docId]?.provider ||
82-
!docsStore?.[versionId]?.provider
83-
) {
84-
return;
85-
}
86-
87-
setStore(docId, {
88-
editor: undefined,
89-
});
90-
91-
revertUpdate(
92-
docsStore[docId].provider.doc,
93-
docsStore[docId].provider.doc,
94-
docsStore[versionId].provider.doc,
95-
);
96-
}}
97-
color="primary-text"
98-
icon={<span className="material-icons">save</span>}
99-
size="small"
100-
>
101-
<Text $theme="primary">{t('Restore the version')}</Text>
102-
</Button>
103-
</Box>
104-
</DropButton>
105-
)}
106-
</Box>
107-
</Link>
108-
</Box>
90+
</Link>
91+
</Box>
92+
{isModalVersionOpen && versionId && (
93+
<ModalVersion
94+
onClose={() => setIsModalVersionOpen(false)}
95+
docId={docId}
96+
versionId={versionId}
97+
/>
98+
)}
99+
</>
109100
);
110101
};
111102

0 commit comments

Comments
 (0)