(frontend) remove document

We can now remove a document from a modal.
This commit is contained in:
Anthony LC
2024-05-23 17:22:09 +02:00
committed by Anthony LC
parent 2813b2ca27
commit 29577c0419
8 changed files with 255 additions and 1 deletions

View File

@@ -8,6 +8,16 @@ and this project adheres to
## [Unreleased]
## Added
- Update document (#68)
- Remove document (#68)
## Changed
- Generate PDF from a modal (#68)
- Remove trigger workflow on push tags on CI (#68)
## [0.1.0] - 2024-05-24
## Added

View File

@@ -102,4 +102,37 @@ test.describe('Pad Tools', () => {
page.getByRole('checkbox', { name: 'Is it public ?' }),
).not.toBeChecked();
});
test('it deletes the pad', async ({ page, browserName }) => {
const [randomPad] = await createPad(page, 'pad-delete', browserName, 1);
await expect(page.locator('h2').getByText(randomPad)).toBeVisible();
await page.getByLabel('Open the document options').click();
await page
.getByRole('button', {
name: 'Delete document',
})
.click();
await expect(
page.locator('h2').getByText(`Deleting the document "${randomPad}"`),
).toBeVisible();
await page
.getByRole('button', {
name: 'Confirm deletion',
})
.click();
await expect(
page.getByText('The document has been deleted.'),
).toBeVisible();
await expect(
page.getByRole('button', { name: 'Create a new pad' }),
).toBeVisible();
const panel = page.getByLabel('Pads panel').first();
await expect(panel.locator('li').getByText(randomPad)).toBeHidden();
});
});

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Box, DropButton, IconOptions, Text } from '@/components';
import { Pad } from '@/features/pads/pad';
import { ModalUpdatePad } from '@/features/pads/pads-create';
import { ModalRemovePad, ModalUpdatePad } from '@/features/pads/pads-create';
import { TemplatesOrdering, useTemplates } from '../api/useTemplates';
@@ -20,6 +20,7 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
ordering: TemplatesOrdering.BY_CREATED_ON_DESC,
});
const [isModalUpdateOpen, setIsModalUpdateOpen] = useState(false);
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
const [isDropOpen, setIsDropOpen] = useState(false);
@@ -63,6 +64,16 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
>
<Text $theme="primary">{t('Update document')}</Text>
</Button>
<Button
onClick={() => {
setIsModalRemoveOpen(true);
setIsDropOpen(false);
}}
color="primary-text"
icon={<span className="material-icons">delete</span>}
>
<Text $theme="primary">{t('Delete document')}</Text>
</Button>
<Button
onClick={() => {
setIsModalPDFOpen(true);
@@ -85,6 +96,9 @@ export const PadToolBox = ({ pad }: PadToolBoxProps) => {
{isModalUpdateOpen && (
<ModalUpdatePad onClose={() => setIsModalUpdateOpen(false)} pad={pad} />
)}
{isModalRemoveOpen && (
<ModalRemovePad onClose={() => setIsModalRemoveOpen(false)} pad={pad} />
)}
</Box>
);
};

View File

@@ -0,0 +1,46 @@
import {
UseMutationOptions,
useMutation,
useQueryClient,
} from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { KEY_LIST_PAD } from '../../pads-panel';
interface RemovePadProps {
padId: string;
}
export const removePad = async ({ padId }: RemovePadProps): Promise<void> => {
const response = await fetchAPI(`documents/${padId}/`, {
method: 'DELETE',
});
if (!response.ok) {
throw new APIError('Failed to delete the pad', await errorCauses(response));
}
};
type UseRemovePadOptions = UseMutationOptions<void, APIError, RemovePadProps>;
export const useRemovePad = (options?: UseRemovePadOptions) => {
const queryClient = useQueryClient();
return useMutation<void, APIError, RemovePadProps>({
mutationFn: removePad,
...options,
onSuccess: (data, variables, context) => {
void queryClient.invalidateQueries({
queryKey: [KEY_LIST_PAD],
});
if (options?.onSuccess) {
options.onSuccess(data, variables, context);
}
},
onError: (error, variables, context) => {
if (options?.onError) {
options.onError(error, variables, context);
}
},
});
};

View File

@@ -0,0 +1,20 @@
<svg
class="svg-icon"
style="vertical-align: middle; overflow: hidden"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M883.2 889.6c0 57.6-44.8 102.4-102.4 102.4h-550.4c-57.6 0-102.4-44.8-102.4-102.4v-665.6c0-57.6 44.8-102.4 102.4-102.4h550.4c57.6 0 102.4 44.8 102.4 102.4v665.6z m-57.6-665.6c0-25.6-19.2-44.8-44.8-44.8h-550.4c-25.6 0-44.8 19.2-44.8 44.8v665.6c0 25.6 19.2 44.8 44.8 44.8h550.4c25.6 0 44.8-19.2 44.8-44.8v-665.6z"
fill="currentColor"
/>
<path
d="M723.2 883.2h-236.8c-6.4 0-12.8-6.4-12.8-12.8s6.4-12.8 12.8-12.8h236.8c25.6 0 38.4-25.6 38.4-70.4v-44.8c0-6.4 6.4-12.8 12.8-12.8s12.8 6.4 12.8 12.8v44.8c0 57.6-25.6 96-64 96zM774.4 684.8c-6.4 0-6.4 0-12.8-6.4 0 0-6.4-6.4-6.4-12.8s0-6.4 6.4-12.8 12.8-6.4 19.2 0c0 0 6.4 6.4 6.4 12.8s0 6.4-6.4 12.8c0 6.4 0 6.4-6.4 6.4zM281.6 281.6c-12.8 0-32-12.8-32-25.6v-185.6c0-12.8 12.8-25.6 32-25.6 12.8 0 25.6 12.8 25.6 25.6v185.6c6.4 12.8-12.8 25.6-25.6 25.6zM441.6 281.6c-12.8 0-32-12.8-32-25.6v-185.6c0-12.8 12.8-25.6 32-25.6 12.8 0 25.6 12.8 25.6 25.6v185.6c0 12.8-12.8 25.6-25.6 25.6zM595.2 281.6c-12.8 0-32-12.8-32-25.6v-185.6c0-12.8 12.8-25.6 32-25.6 12.8 0 25.6 12.8 25.6 25.6v185.6c6.4 12.8-6.4 25.6-25.6 25.6zM755.2 281.6c-12.8 0-32-12.8-32-25.6v-185.6c0-12.8 12.8-25.6 32-25.6 12.8 0 25.6 12.8 25.6 25.6v185.6c6.4 12.8-6.4 25.6-25.6 25.6z"
fill="currentColor"
/>
<path
d="M857.6 396.8h-691.2c-6.4 0-12.8-6.4-12.8-12.8s6.4-12.8 12.8-12.8h691.2c6.4 0 12.8 6.4 12.8 12.8s0 12.8-12.8 12.8z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,6 @@
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 38C12 40.2 13.8 42 16 42H32C34.2 42 36 40.2 36 38V14H12V38ZM18.34 25.18C17.56 24.4 17.56 23.14 18.34 22.36C19.12 21.58 20.38 21.58 21.16 22.36L24 25.18L26.82 22.36C27.6 21.58 28.86 21.58 29.64 22.36C30.42 23.14 30.42 24.4 29.64 25.18L26.82 28L29.64 30.82C30.42 31.6 30.42 32.86 29.64 33.64C28.86 34.42 27.6 34.42 26.82 33.64L24 30.82L21.18 33.64C20.4 34.42 19.14 34.42 18.36 33.64C17.58 32.86 17.58 31.6 18.36 30.82L21.18 28L18.34 25.18ZM36 8H31L29.58 6.58C29.22 6.22 28.7 6 28.18 6H19.82C19.3 6 18.78 6.22 18.42 6.58L17 8H12C10.9 8 10 8.9 10 10C10 11.1 10.9 12 12 12H36C37.1 12 38 11.1 38 10C38 8.9 37.1 8 36 8Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 742 B

View File

@@ -0,0 +1,124 @@
import {
Button,
Modal,
ModalSize,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { t } from 'i18next';
import { useRouter } from 'next/navigation';
import { Box, Text, TextErrors } from '@/components';
import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
import { Pad } from '../../pad/types';
import { useRemovePad } from '../api/useRemovePad';
import IconPad from '../assets/icon-pad.svg';
import IconRemove from '../assets/icon-trash.svg';
interface ModalRemovePadProps {
onClose: () => void;
pad: Pad;
}
export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
const { colorsTokens } = useCunninghamTheme();
const { toast } = useToastProvider();
const router = useRouter();
const {
mutate: removePad,
isError,
error,
} = useRemovePad({
onSuccess: () => {
toast(t('The document has been deleted.'), VariantType.SUCCESS, {
duration: 4000,
});
router.push('/');
},
});
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('Confirm deletion')}
color="primary"
fullWidth
onClick={() =>
removePad({
padId: pad.id,
})
}
>
{t('Confirm deletion')}
</Button>
}
size={ModalSize.MEDIUM}
title={
<Box $align="center" $gap="1rem">
<IconRemove width={48} color={colorsTokens()['primary-text']} />
<Text as="h2" $size="h3" $margin="none">
{t('Deleting the document "{{title}}"', { title: pad.title })}
</Text>
</Box>
}
>
<Box
$margin={{ bottom: 'xl' }}
aria-label={t('Content modal to delete document')}
>
<Text as="p" $margin={{ bottom: 'big' }}>
{t('Are you sure you want to delete the document "{{title}}"?', {
title: pad.title,
})}
</Text>
{isError && (
<TextErrors $margin={{ bottom: 'small' }} causes={error.cause} />
)}
<Text
as="p"
$padding="small"
$direction="row"
$gap="0.5rem"
$background={colorsTokens()['primary-150']}
$theme="primary"
$align="center"
$radius="2px"
>
<IconPad
className="p-t"
aria-label={t(`Document icon`)}
color={colorsTokens()['primary-500']}
width={58}
style={{
borderRadius: '8px',
backgroundColor: '#ffffff',
border: `1px solid ${colorsTokens()['primary-300']}`,
}}
/>
<Text $theme="primary" $weight="bold" $size="l">
{pad.title}
</Text>
</Text>
</Box>
</Modal>
);
};

View File

@@ -1,2 +1,3 @@
export * from './CardCreatePad';
export * from './ModalRemovePad';
export * from './ModalUpdatePad';