Skip to content

Commit df27658

Browse files
committed
✨(frontend) can restore from trashbin list actions
We can now restore a doc from the trashbin list actions.
1 parent 3b95b9d commit df27658

File tree

9 files changed

+251
-27
lines changed

9 files changed

+251
-27
lines changed

src/frontend/apps/impress/src/components/TextErrors.tsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,40 +25,54 @@ export const TextErrors = ({
2525
canClose = false,
2626
...textProps
2727
}: TextErrorsProps) => {
28-
const { t } = useTranslation();
29-
3028
return (
3129
<AlertStyled
3230
canClose={canClose}
3331
type={VariantType.ERROR}
3432
icon={icon}
3533
className="--docs--text-errors"
3634
>
37-
<Box $direction="column" $gap="0.2rem">
38-
{causes &&
39-
causes.map((cause, i) => (
40-
<Text
41-
key={`causes-${i}`}
42-
$theme="danger"
43-
$variation="600"
44-
$textAlign="center"
45-
{...textProps}
46-
>
47-
{cause}
48-
</Text>
49-
))}
35+
<TextOnlyErrors
36+
causes={causes}
37+
defaultMessage={defaultMessage}
38+
{...textProps}
39+
/>
40+
</AlertStyled>
41+
);
42+
};
43+
44+
export const TextOnlyErrors = ({
45+
causes,
46+
defaultMessage,
47+
...textProps
48+
}: TextErrorsProps) => {
49+
const { t } = useTranslation();
5050

51-
{!causes && (
51+
return (
52+
<Box $direction="column" $gap="0.2rem">
53+
{causes &&
54+
causes.map((cause, i) => (
5255
<Text
56+
key={`causes-${i}`}
5357
$theme="danger"
5458
$variation="600"
5559
$textAlign="center"
5660
{...textProps}
5761
>
58-
{defaultMessage || t('Something bad happens, please retry.')}
62+
{cause}
5963
</Text>
60-
)}
61-
</Box>
62-
</AlertStyled>
64+
))}
65+
66+
{!causes && (
67+
<Text
68+
$theme="danger"
69+
$variation="600"
70+
$textAlign="center"
71+
{...textProps}
72+
>
73+
{defaultMessage || t('Something bad happens, please retry.')}
74+
</Text>
75+
)}
76+
</Box>
6377
);
6478
};

src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ export * from './useDeleteFavoriteDoc';
55
export * from './useDoc';
66
export * from './useDocOptions';
77
export * from './useDocs';
8-
export * from './useSubDocs';
98
export * from './useDuplicateDoc';
9+
export * from './useRestoreDoc';
10+
export * from './useSubDocs';
1011
export * from './useUpdateDoc';
1112
export * from './useUpdateDocLink';

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { UseQueryOptions, useQuery } from '@tanstack/react-query';
33
import {
44
APIError,
55
APIList,
6+
InfiniteQueryConfig,
67
errorCauses,
78
fetchAPI,
89
useAPIInfiniteQuery,
@@ -54,17 +55,20 @@ export const getDocs = async (params: DocsParams): Promise<DocsResponse> => {
5455

5556
export const KEY_LIST_DOC = 'docs';
5657

57-
export function useDocs(
58-
params: DocsParams,
59-
queryConfig?: UseQueryOptions<DocsResponse, APIError, DocsResponse>,
60-
) {
58+
type UseDocsOptions = UseQueryOptions<DocsResponse, APIError, DocsResponse>;
59+
type UseInfiniteDocsOptions = InfiniteQueryConfig<DocsResponse>;
60+
61+
export function useDocs(params: DocsParams, queryConfig?: UseDocsOptions) {
6162
return useQuery<DocsResponse, APIError, DocsResponse>({
6263
queryKey: [KEY_LIST_DOC, params],
6364
queryFn: () => getDocs(params),
6465
...queryConfig,
6566
});
6667
}
6768

68-
export const useInfiniteDocs = (params: DocsParams) => {
69-
return useAPIInfiniteQuery(KEY_LIST_DOC, getDocs, params);
69+
export const useInfiniteDocs = (
70+
params: DocsParams,
71+
queryConfig?: UseInfiniteDocsOptions,
72+
) => {
73+
return useAPIInfiniteQuery(KEY_LIST_DOC, getDocs, params, queryConfig);
7074
};

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@tanstack/react-query';
66

77
import { APIError, errorCauses, fetchAPI } from '@/api';
8+
import { KEY_LIST_DOC_TRASHBIN } from '@/docs/docs-grid';
89

910
import { KEY_LIST_DOC } from './useDocs';
1011

@@ -33,6 +34,9 @@ export const useRemoveDoc = (options?: UseRemoveDocOptions) => {
3334
void queryClient.invalidateQueries({
3435
queryKey: [KEY_LIST_DOC],
3536
});
37+
void queryClient.invalidateQueries({
38+
queryKey: [KEY_LIST_DOC_TRASHBIN],
39+
});
3640
if (options?.onSuccess) {
3741
void options.onSuccess(data, variables, context);
3842
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
UseMutationOptions,
3+
useMutation,
4+
useQueryClient,
5+
} from '@tanstack/react-query';
6+
7+
import { APIError, errorCauses, fetchAPI } from '@/api';
8+
9+
interface RestoreDocProps {
10+
docId: string;
11+
}
12+
13+
export const restoreDoc = async ({ docId }: RestoreDocProps): Promise<void> => {
14+
const response = await fetchAPI(`documents/${docId}/restore/`, {
15+
method: 'POST',
16+
});
17+
18+
if (!response.ok) {
19+
throw new APIError(
20+
'Failed to restore the doc',
21+
await errorCauses(response),
22+
);
23+
}
24+
};
25+
26+
type UseRestoreDocOptions = UseMutationOptions<void, APIError, RestoreDocProps>;
27+
28+
export const useRestoreDoc = ({
29+
listInvalideQueries,
30+
options,
31+
}: {
32+
listInvalideQueries?: string[];
33+
options?: UseRestoreDocOptions;
34+
}) => {
35+
const queryClient = useQueryClient();
36+
return useMutation<void, APIError, RestoreDocProps>({
37+
mutationFn: restoreDoc,
38+
...options,
39+
onSuccess: (data, variables, context) => {
40+
listInvalideQueries?.forEach((queryKey) => {
41+
void queryClient.invalidateQueries({
42+
queryKey: [queryKey],
43+
});
44+
});
45+
if (options?.onSuccess) {
46+
void options.onSuccess(data, variables, context);
47+
}
48+
},
49+
onError: (error, variables, context) => {
50+
if (options?.onError) {
51+
void options.onError(error, variables, context);
52+
}
53+
},
54+
});
55+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useDocsTrashbin';
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
2+
3+
import {
4+
APIError,
5+
APIList,
6+
InfiniteQueryConfig,
7+
errorCauses,
8+
fetchAPI,
9+
useAPIInfiniteQuery,
10+
} from '@/api';
11+
import { Doc, DocsResponse } from '@/docs/doc-management';
12+
13+
export type DocsTrashbinParams = {
14+
page: number;
15+
};
16+
17+
export type DocsTrashbinResponse = APIList<Doc>;
18+
export const getDocsTrashbin = async (
19+
params: DocsTrashbinParams,
20+
): Promise<DocsTrashbinResponse> => {
21+
const response = await fetchAPI(`documents/trashbin/?page=${params.page}`);
22+
23+
if (!response.ok) {
24+
throw new APIError('Failed to get the docs', await errorCauses(response));
25+
}
26+
27+
return response.json() as Promise<DocsTrashbinResponse>;
28+
};
29+
30+
export const KEY_LIST_DOC_TRASHBIN = 'docs_trashbin';
31+
32+
type UseDocsTrashbinOptions = UseQueryOptions<
33+
DocsResponse,
34+
APIError,
35+
DocsResponse
36+
>;
37+
type UseInfiniteDocsTrashbinOptions = InfiniteQueryConfig<DocsTrashbinResponse>;
38+
39+
export function useDocsTrashbin(
40+
params: DocsTrashbinParams,
41+
queryConfig?: UseDocsTrashbinOptions,
42+
) {
43+
return useQuery<DocsTrashbinResponse, APIError, DocsTrashbinResponse>({
44+
queryKey: [KEY_LIST_DOC_TRASHBIN, params],
45+
queryFn: () => getDocsTrashbin(params),
46+
...queryConfig,
47+
});
48+
}
49+
50+
export const useInfiniteDocsTrashbin = (
51+
params: DocsTrashbinParams,
52+
queryConfig?: UseInfiniteDocsTrashbinOptions,
53+
) => {
54+
return useAPIInfiniteQuery(
55+
KEY_LIST_DOC_TRASHBIN,
56+
getDocsTrashbin,
57+
params,
58+
queryConfig,
59+
);
60+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
2+
import { useRouter } from 'next/router';
3+
import { useTranslation } from 'react-i18next';
4+
import { css } from 'styled-components';
5+
6+
import { DropdownMenu, DropdownMenuOption, Icon } from '@/components';
7+
import { Doc, KEY_LIST_DOC, useRestoreDoc } from '@/docs/doc-management';
8+
9+
import { KEY_LIST_DOC_TRASHBIN } from '../api';
10+
11+
interface DocsGridTrashbinActionsProps {
12+
doc: Doc;
13+
}
14+
15+
export const DocsGridTrashbinActions = ({
16+
doc,
17+
}: DocsGridTrashbinActionsProps) => {
18+
const { t } = useTranslation();
19+
const { toast } = useToastProvider();
20+
const { push } = useRouter();
21+
const { mutate: restoreDoc, error } = useRestoreDoc({
22+
listInvalideQueries: [KEY_LIST_DOC, KEY_LIST_DOC_TRASHBIN],
23+
options: {
24+
onSuccess: (_data, variables) => {
25+
void push('/docs/' + variables.docId);
26+
27+
toast(t('The document has been restored.'), VariantType.SUCCESS, {
28+
duration: 4000,
29+
});
30+
},
31+
onError: () => {
32+
toast(
33+
t('An error occurred while restoring the document: {{error}}', {
34+
error: error?.message,
35+
}),
36+
VariantType.ERROR,
37+
{
38+
duration: 4000,
39+
},
40+
);
41+
},
42+
},
43+
});
44+
45+
const options: DropdownMenuOption[] = [
46+
{
47+
label: t('Restore'),
48+
icon: 'undo',
49+
callback: () => {
50+
restoreDoc({
51+
docId: doc.id,
52+
});
53+
},
54+
testId: `docs-grid-actions-restore-${doc.id}`,
55+
},
56+
];
57+
58+
const documentTitle = doc.title || t('Untitled document');
59+
const menuLabel = t('Open the menu of actions for the document: {{title}}', {
60+
title: documentTitle,
61+
});
62+
63+
return (
64+
<DropdownMenu
65+
options={options}
66+
label={menuLabel}
67+
aria-label={t('More options')}
68+
>
69+
<Icon
70+
data-testid={`docs-grid-actions-button-${doc.id}`}
71+
iconName="more_horiz"
72+
$theme="primary"
73+
$variation="600"
74+
$css={css`
75+
cursor: pointer;
76+
&:hover {
77+
opacity: 0.8;
78+
}
79+
`}
80+
aria-hidden="true"
81+
/>
82+
</DropdownMenu>
83+
);
84+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from './api';
12
export * from './components';

0 commit comments

Comments
 (0)