Skip to content

Commit de922e1

Browse files
committed
♻️(frontend) create a doc from a modal
We refacto the create doc feature to use a modal instead of a page and a card component. It is more consistent with the other features.
1 parent 8007c45 commit de922e1

File tree

9 files changed

+214
-244
lines changed

9 files changed

+214
-244
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to
1515
## Changed
1616

1717
- ♻️(frontend) replace docs panel with docs grid #120
18+
- ♻️(frontend) create a doc from a modal #132
1819

1920
## [1.0.0] - 2024-07-02
2021

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

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,12 @@ test.describe('Doc Create', () => {
1414
await buttonCreateHomepage.click();
1515
await expect(buttonCreateHomepage).toBeHidden();
1616

17-
const card = page.getByLabel('Create new document card').first();
18-
19-
await expect(card.getByLabel('Document name')).toBeVisible();
20-
21-
await expect(card.getByLabel('icon group')).toBeVisible();
17+
const card = page.getByRole('dialog').first();
2218

2319
await expect(
24-
card.getByRole('heading', {
25-
name: 'Name the document',
26-
level: 3,
27-
}),
20+
card.locator('h2').getByText('Create a new document'),
2821
).toBeVisible();
22+
await expect(card.getByLabel('Document name')).toBeVisible();
2923

3024
await expect(card.getByText('Is it public ?')).toBeVisible();
3125

@@ -35,13 +29,7 @@ test.describe('Doc Create', () => {
3529
}),
3630
).toBeVisible();
3731

38-
await expect(
39-
card.getByRole('button', {
40-
name: 'Cancel',
41-
}),
42-
).toBeVisible();
43-
44-
await expect(page).toHaveURL('/docs/create/');
32+
await expect(card.getByLabel('Close the modal')).toBeVisible();
4533
});
4634

4735
test('checks the cancel button interaction', async ({ page }) => {
@@ -51,13 +39,9 @@ test.describe('Doc Create', () => {
5139
await buttonCreateHomepage.click();
5240
await expect(buttonCreateHomepage).toBeHidden();
5341

54-
const card = page.getByLabel('Create new document card').first();
42+
const card = page.getByRole('dialog').first();
5543

56-
await card
57-
.getByRole('button', {
58-
name: 'Cancel',
59-
})
60-
.click();
44+
await card.getByLabel('Close the modal').click();
6145

6246
await expect(buttonCreateHomepage).toBeVisible();
6347
});

src/frontend/apps/impress/src/features/docs/doc-management/api/useCreateDoc.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Doc } from '../types';
66

77
import { KEY_LIST_DOC } from './useDocs';
88

9-
type CreateDocParam = Pick<Doc, 'title' | 'is_public'>;
9+
export type CreateDocParam = Pick<Doc, 'title' | 'is_public'>;
1010

1111
export const createDoc = async ({
1212
title,

src/frontend/apps/impress/src/features/docs/doc-management/components/CardCreateDoc.tsx

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import {
2+
Alert,
3+
Button,
4+
Modal,
5+
ModalSize,
6+
Switch,
7+
VariantType,
8+
useToastProvider,
9+
} from '@openfun/cunningham-react';
10+
import { UseMutationResult } from '@tanstack/react-query';
11+
import { useRouter } from 'next/navigation';
12+
import { useState } from 'react';
13+
import { useTranslation } from 'react-i18next';
14+
15+
import { APIError } from '@/api';
16+
import { Box, Text } from '@/components';
17+
import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
18+
19+
import { KEY_DOC, KEY_LIST_DOC } from '../api';
20+
import { useCreateDoc } from '../api/useCreateDoc';
21+
import { useUpdateDoc } from '../api/useUpdateDoc';
22+
import IconEdit from '../assets/icon-edit.svg';
23+
import { Doc } from '../types';
24+
25+
import { InputDocName } from './InputDocName';
26+
27+
interface ModalCreateDocProps {
28+
onClose: () => void;
29+
}
30+
31+
export const ModalCreateDoc = ({ onClose }: ModalCreateDocProps) => {
32+
const router = useRouter();
33+
const api = useCreateDoc({
34+
onSuccess: (doc) => {
35+
router.push(`/docs/${doc.id}`);
36+
},
37+
});
38+
const { t } = useTranslation();
39+
40+
return (
41+
<ModalDoc
42+
{...{
43+
buttonText: t('Create the document'),
44+
onClose,
45+
isPublic: false,
46+
titleModal: t('Create a new document'),
47+
validate: (title, is_public) =>
48+
api.mutate({
49+
is_public,
50+
title,
51+
}),
52+
...api,
53+
}}
54+
/>
55+
);
56+
};
57+
58+
interface ModalUpdateDocProps {
59+
onClose: () => void;
60+
doc: Doc;
61+
}
62+
63+
export const ModalUpdateDoc = ({ onClose, doc }: ModalUpdateDocProps) => {
64+
const { toast } = useToastProvider();
65+
const { t } = useTranslation();
66+
67+
const api = useUpdateDoc({
68+
onSuccess: () => {
69+
toast(t('The document has been updated.'), VariantType.SUCCESS, {
70+
duration: 4000,
71+
});
72+
onClose();
73+
},
74+
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
75+
});
76+
77+
return (
78+
<ModalDoc
79+
{...{
80+
buttonText: t('Validate the modification'),
81+
onClose,
82+
initialTitle: doc.title,
83+
isPublic: doc.is_public,
84+
infoText: t('Enter the new name of the selected document.'),
85+
titleModal: t('Update document "{{documentTitle}}"', {
86+
documentTitle: doc.title,
87+
}),
88+
validate: (title, is_public) =>
89+
api.mutate({
90+
is_public,
91+
title,
92+
id: doc.id,
93+
}),
94+
...api,
95+
}}
96+
/>
97+
);
98+
};
99+
100+
type ModalDoc<T> = {
101+
buttonText: string;
102+
isPublic: boolean;
103+
onClose: () => void;
104+
titleModal: string;
105+
validate: (title: string, is_public: boolean) => void;
106+
initialTitle?: string;
107+
infoText?: string;
108+
} & UseMutationResult<Doc, APIError<unknown>, T, unknown>;
109+
110+
const ModalDoc = <T,>({
111+
buttonText,
112+
infoText,
113+
initialTitle,
114+
isPublic,
115+
onClose,
116+
titleModal,
117+
validate,
118+
...api
119+
}: ModalDoc<T>) => {
120+
const { colorsTokens } = useCunninghamTheme();
121+
const { t } = useTranslation();
122+
const [title, setTitle] = useState(initialTitle || '');
123+
124+
const [docPublic, setDocPublic] = useState(isPublic);
125+
126+
return (
127+
<Modal
128+
isOpen
129+
closeOnClickOutside
130+
hideCloseButton
131+
leftActions={
132+
<Button
133+
aria-label={t('Close the modal')}
134+
color="secondary"
135+
fullWidth
136+
onClick={() => onClose()}
137+
>
138+
{t('Cancel')}
139+
</Button>
140+
}
141+
onClose={() => onClose()}
142+
rightActions={
143+
<Button
144+
aria-label={buttonText}
145+
color="primary"
146+
fullWidth
147+
onClick={() => validate(title, docPublic)}
148+
>
149+
{buttonText}
150+
</Button>
151+
}
152+
size={ModalSize.MEDIUM}
153+
title={
154+
<Box $align="center" $gap="1rem" $margin={{ bottom: '2.5rem' }}>
155+
<IconEdit width={48} color={colorsTokens()['primary-text']} />
156+
<Text as="h2" $size="h3" $margin="none">
157+
{titleModal}
158+
</Text>
159+
</Box>
160+
}
161+
>
162+
<Box $margin={{ bottom: 'xl' }} $gap="1rem">
163+
{infoText && (
164+
<Alert canClose={false} type={VariantType.INFO}>
165+
<Text>{infoText}</Text>
166+
</Alert>
167+
)}
168+
169+
<Box $gap="1rem">
170+
<InputDocName
171+
label={t('Document name')}
172+
defaultValue={title}
173+
{...{
174+
error: api.error,
175+
isError: api.isError,
176+
isPending: api.isPending,
177+
setDocName: setTitle,
178+
}}
179+
/>
180+
<Switch
181+
label={t('Is it public ?')}
182+
labelSide="right"
183+
defaultChecked={docPublic}
184+
onChange={() => setDocPublic(!docPublic)}
185+
/>
186+
</Box>
187+
</Box>
188+
</Modal>
189+
);
190+
};

0 commit comments

Comments
 (0)