Skip to content

Commit 7475b7c

Browse files
committed
✨(frontend) link to create new doc
We create a special URL to create a new doc, we can set the doc with the URL param to set the visibility, the permission and the title.
1 parent c13f0e9 commit 7475b7c

File tree

8 files changed

+253
-42
lines changed

8 files changed

+253
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to
1010

1111
- ✨ Add comments feature to the editor #1330
1212
- ✨(backend) Comments on text editor #1330
13+
- ✨(frontend) link to create new doc #1574
1314

1415
### Changed
1516

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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
randomName,
88
verifyDocName,
99
} from './utils-common';
10+
import { connectOtherUserToDoc } from './utils-share';
1011

1112
test.beforeEach(async ({ page }) => {
1213
await page.goto('/');
@@ -73,6 +74,82 @@ test.describe('Doc Create', () => {
7374
page.locator('.c__tree-view--row-content').getByText('Untitled document'),
7475
).toBeVisible();
7576
});
77+
78+
test('it creates a doc with link "/doc/new/', async ({
79+
page,
80+
browserName,
81+
}) => {
82+
test.slow();
83+
84+
// Private doc creation
85+
await page.goto('/docs/new/?title=My+private+doc+from+url');
86+
87+
await verifyDocName(page, 'My private doc from url');
88+
89+
await page.getByRole('button', { name: 'Share' }).click();
90+
91+
await expect(
92+
page.getByTestId('doc-visibility').getByText('Private').first(),
93+
).toBeVisible();
94+
95+
// Public editing doc creation
96+
await page.goto(
97+
'/docs/new/?title=My+public+doc+from+url&link-reach=public&link-role=editor',
98+
);
99+
100+
await verifyDocName(page, 'My public doc from url');
101+
102+
await page.getByRole('button', { name: 'Share' }).click();
103+
104+
await expect(
105+
page.getByTestId('doc-visibility').getByText('Public').first(),
106+
).toBeVisible();
107+
108+
await expect(
109+
page.getByTestId('doc-access-mode').getByText('Editing').first(),
110+
).toBeVisible();
111+
112+
// Authenticated reading doc creation
113+
await page.goto(
114+
'/docs/new/?title=My+authenticated+doc+from+url&link-reach=authenticated&link-role=reader',
115+
);
116+
117+
await verifyDocName(page, 'My authenticated doc from url');
118+
119+
await page.getByRole('button', { name: 'Share' }).click();
120+
121+
await expect(
122+
page.getByTestId('doc-visibility').getByText('Connected').first(),
123+
).toBeVisible();
124+
125+
await expect(
126+
page.getByTestId('doc-access-mode').getByText('Reading').first(),
127+
).toBeVisible();
128+
129+
const { cleanup, otherPage, otherBrowserName } =
130+
await connectOtherUserToDoc({
131+
docUrl:
132+
'/docs/new/?title=From+unlogged+doc+from+url&link-reach=authenticated&link-role=reader',
133+
browserName,
134+
withoutSignIn: true,
135+
});
136+
137+
await keyCloakSignIn(otherPage, otherBrowserName, false);
138+
139+
await verifyDocName(otherPage, 'From unlogged doc from url');
140+
141+
await otherPage.getByRole('button', { name: 'Share' }).click();
142+
143+
await expect(
144+
otherPage.getByTestId('doc-visibility').getByText('Connected').first(),
145+
).toBeVisible();
146+
147+
await expect(
148+
otherPage.getByTestId('doc-access-mode').getByText('Reading').first(),
149+
).toBeVisible();
150+
151+
await cleanup();
152+
});
76153
});
77154

78155
test.describe('Doc Create: Not logged', () => {

src/frontend/apps/impress/src/features/auth/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const setAuthUrl = () => {
2727
window.location.pathname !== '/' &&
2828
window.location.pathname !== `${HOME_URL}/`
2929
) {
30-
localStorage.setItem(PATH_AUTH_LOCAL_STORAGE, window.location.pathname);
30+
localStorage.setItem(PATH_AUTH_LOCAL_STORAGE, window.location.href);
3131
}
3232
};
3333

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
import { useMutation, useQueryClient } from '@tanstack/react-query';
1+
import {
2+
UseMutationOptions,
3+
useMutation,
4+
useQueryClient,
5+
} from '@tanstack/react-query';
26

37
import { APIError, errorCauses, fetchAPI } from '@/api';
48

59
import { Doc } from '../types';
610

711
import { KEY_LIST_DOC } from './useDocs';
812

9-
export const createDoc = async (): Promise<Doc> => {
13+
type CreateDocParams = {
14+
title?: string;
15+
} | void;
16+
17+
export const createDoc = async (params: CreateDocParams): Promise<Doc> => {
1018
const response = await fetchAPI(`documents/`, {
1119
method: 'POST',
20+
body: JSON.stringify({ title: params?.title }),
1221
});
1322

1423
if (!response.ok) {
@@ -18,23 +27,17 @@ export const createDoc = async (): Promise<Doc> => {
1827
return response.json() as Promise<Doc>;
1928
};
2029

21-
interface CreateDocProps {
22-
onSuccess: (data: Doc) => void;
23-
onError?: (error: APIError) => void;
24-
}
30+
type UseCreateDocOptions = UseMutationOptions<Doc, APIError, CreateDocParams>;
2531

26-
export function useCreateDoc({ onSuccess, onError }: CreateDocProps) {
32+
export function useCreateDoc(options?: UseCreateDocOptions) {
2733
const queryClient = useQueryClient();
28-
return useMutation<Doc, APIError>({
34+
return useMutation<Doc, APIError, CreateDocParams>({
2935
mutationFn: createDoc,
30-
onSuccess: (data) => {
36+
onSuccess: (data, variables, onMutateResult, context) => {
3137
void queryClient.resetQueries({
3238
queryKey: [KEY_LIST_DOC],
3339
});
34-
onSuccess(data);
35-
},
36-
onError: (error) => {
37-
onError?.(error);
40+
options?.onSuccess?.(data, variables, onMutateResult, context);
3841
},
3942
});
4043
}
Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
2-
import { useMutation, useQueryClient } from '@tanstack/react-query';
3-
import { useTranslation } from 'react-i18next';
1+
import {
2+
UseMutationOptions,
3+
useMutation,
4+
useQueryClient,
5+
} from '@tanstack/react-query';
46

57
import { APIError, errorCauses, fetchAPI } from '@/api';
6-
import { Doc } from '@/docs/doc-management';
8+
import { Doc, LinkReach, LinkRole } from '@/docs/doc-management';
79

810
export type UpdateDocLinkParams = Pick<Doc, 'id' | 'link_reach'> &
911
Partial<Pick<Doc, 'link_role'>>;
1012

13+
type UpdateDocLinkResponse = { link_role: LinkRole; link_reach: LinkReach };
14+
1115
export const updateDocLink = async ({
1216
id,
1317
...params
14-
}: UpdateDocLinkParams): Promise<Doc> => {
18+
}: UpdateDocLinkParams): Promise<UpdateDocLinkResponse> => {
1519
const response = await fetchAPI(`documents/${id}/link-configuration/`, {
1620
method: 'PUT',
1721
body: JSON.stringify({
@@ -26,40 +30,31 @@ export const updateDocLink = async ({
2630
);
2731
}
2832

29-
return response.json() as Promise<Doc>;
33+
return response.json() as Promise<UpdateDocLinkResponse>;
3034
};
3135

32-
interface UpdateDocLinkProps {
33-
onSuccess?: (data: Doc) => void;
36+
type UseUpdateDocLinkOptions = UseMutationOptions<
37+
UpdateDocLinkResponse,
38+
APIError,
39+
UpdateDocLinkParams
40+
> & {
3441
listInvalidQueries?: string[];
35-
}
42+
};
3643

37-
export function useUpdateDocLink({
38-
onSuccess,
39-
listInvalidQueries,
40-
}: UpdateDocLinkProps = {}) {
44+
export function useUpdateDocLink(options?: UseUpdateDocLinkOptions) {
4145
const queryClient = useQueryClient();
42-
const { toast } = useToastProvider();
43-
const { t } = useTranslation();
4446

45-
return useMutation<Doc, APIError, UpdateDocLinkParams>({
47+
return useMutation<UpdateDocLinkResponse, APIError, UpdateDocLinkParams>({
4648
mutationFn: updateDocLink,
47-
onSuccess: (data) => {
48-
listInvalidQueries?.forEach((queryKey) => {
49+
...options,
50+
onSuccess: (data, variables, onMutateResult, context) => {
51+
options?.listInvalidQueries?.forEach((queryKey) => {
4952
void queryClient.invalidateQueries({
5053
queryKey: [queryKey],
5154
});
5255
});
5356

54-
toast(
55-
t('The document visibility has been updated.'),
56-
VariantType.SUCCESS,
57-
{
58-
duration: 2000,
59-
},
60-
);
61-
62-
onSuccess?.(data);
57+
options?.onSuccess?.(data, variables, onMutateResult, context);
6358
},
6459
});
6560
}

src/frontend/apps/impress/src/features/docs/doc-share/components/DocDesynchronized.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { Button } from '@openfun/cunningham-react';
1+
import {
2+
Button,
3+
VariantType,
4+
useToastProvider,
5+
} from '@openfun/cunningham-react';
26
import { useTranslation } from 'react-i18next';
37

48
import { Box, Card, Text } from '@/components';
@@ -17,9 +21,15 @@ interface DocDesynchronizedProps {
1721
export const DocDesynchronized = ({ doc }: DocDesynchronizedProps) => {
1822
const { t } = useTranslation();
1923
const { spacingsTokens } = useCunninghamTheme();
24+
const { toast } = useToastProvider();
2025

2126
const { mutate: updateDocLink } = useUpdateDocLink({
2227
listInvalidQueries: [KEY_LIST_DOC, KEY_DOC],
28+
onSuccess: () => {
29+
toast(t('The document visibility restored.'), VariantType.SUCCESS, {
30+
duration: 2000,
31+
});
32+
},
2333
});
2434

2535
return (

src/frontend/apps/impress/src/features/docs/doc-share/components/DocVisibility.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
12
import { useMemo } from 'react';
23
import { useTranslation } from 'react-i18next';
34
import { css } from 'styled-components';
@@ -41,6 +42,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
4142
const { isDesynchronized } = useDocUtils(doc);
4243
const { linkModeTranslations, linkReachChoices, linkReachTranslations } =
4344
useTranslatedShareSettings();
45+
const { toast } = useToastProvider();
4446

4547
const description =
4648
docLinkRole === LinkRole.READER
@@ -49,6 +51,15 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
4951

5052
const { mutate: updateDocLink } = useUpdateDocLink({
5153
listInvalidQueries: [KEY_LIST_DOC, KEY_DOC],
54+
onSuccess: () => {
55+
toast(
56+
t('The document visibility has been updated.'),
57+
VariantType.SUCCESS,
58+
{
59+
duration: 2000,
60+
},
61+
);
62+
},
5263
});
5364

5465
const linkReachOptions: DropdownMenuOption[] = useMemo(() => {

0 commit comments

Comments
 (0)