Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"react-select": "5.8.0",
"remeda": "2.5.0",
"sharp": "0.33.4",
"sonner": "1.5.0",
"superjson": "2.2.1",
"trpc-openapi": "1.2.0",
"ts-pattern": "5.2.0",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/app/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { FC } from 'react';
import { CacheProvider } from '@chakra-ui/next-js';
import { ChakraProvider, createLocalStorageManager } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { Toaster } from 'sonner';

import '@/lib/dayjs/config';
import '@/lib/i18n/client';
Expand All @@ -28,6 +29,7 @@ export const Providers: FC<React.PropsWithChildren<unknown>> = ({
}}
>
{children}
<Toaster position="top-right" offset={16} />
</ChakraProvider>
</CacheProvider>
);
Expand Down
113 changes: 113 additions & 0 deletions src/components/Toast/docs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';

import { Box, Button, Flex } from '@chakra-ui/react';
import { Meta } from '@storybook/react';
import { toast } from 'sonner';

import { toastCustom } from '@/components/Toast';

export default {
title: 'Components/Toast',
decorators: [
(Story) => (
<Box h="10rem">
<Story />
</Box>
),
],
} satisfies Meta;

export const Default = () => {
const handleOpenToast = (props: { status: 'success' | 'error' | 'info' }) => {
toastCustom({
status: props.status,
title: 'This is a toast',
});
};

return (
<Flex gap={4}>
<Button size="md" onClick={() => handleOpenToast({ status: 'success' })}>
Success toast
</Button>
<Button size="md" onClick={() => handleOpenToast({ status: 'error' })}>
Error toast
</Button>
<Button size="md" onClick={() => handleOpenToast({ status: 'info' })}>
Info toast
</Button>
</Flex>
);
};

export const WithDescription = () => {
const handleOpenToast = (props: { status: 'success' | 'error' | 'info' }) => {
toastCustom({
status: props.status,
title: 'This is a toast',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis id porta lacus. Nunc tellus ipsum, blandit commodo neque at, eleifend facilisis arcu. Phasellus nec pretium sapien.',
});
};
return (
<Flex gap={4}>
<Button size="md" onClick={() => handleOpenToast({ status: 'success' })}>
Success toast with descrition
</Button>
<Button size="md" onClick={() => handleOpenToast({ status: 'error' })}>
Error toast with descrition
</Button>
<Button size="md" onClick={() => handleOpenToast({ status: 'info' })}>
Info toast with descrition
</Button>
</Flex>
);
};

export const WithActions = () => {
const handleOpenToast = (props: { status: 'success' | 'error' | 'info' }) => {
toastCustom({
status: props.status,
title: 'This is a toast',
actions: (
<Button onClick={() => toast.dismiss()}>Fermer les toasts</Button>
),
});
};
return (
<Flex gap={4}>
<Button size="md" onClick={() => handleOpenToast({ status: 'success' })}>
Success toast with actions
</Button>
<Button size="md" onClick={() => handleOpenToast({ status: 'error' })}>
Error toast with actions
</Button>
<Button size="md" onClick={() => handleOpenToast({ status: 'info' })}>
Info toast with with actions
</Button>
</Flex>
);
};

export const HideIcon = () => {
const handleOpenToast = (props: { status: 'success' | 'error' | 'info' }) => {
toastCustom({
status: props.status,
title: 'This is a toast',
hideIcon: true,
});
};
return (
<Flex gap={4}>
<Button size="md" onClick={() => handleOpenToast({ status: 'success' })}>
Success toast without icon
</Button>
<Button size="md" onClick={() => handleOpenToast({ status: 'error' })}>
Error toast with without icon
</Button>
<Button size="md" onClick={() => handleOpenToast({ status: 'info' })}>
Info toast with without icon
</Button>
</Flex>
);
};
33 changes: 0 additions & 33 deletions src/components/Toast/index.ts

This file was deleted.

101 changes: 101 additions & 0 deletions src/components/Toast/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ReactNode } from 'react';

import {
Box,
ButtonGroup,
Card,
CardBody,
Flex,
Heading,
IconButton,
} from '@chakra-ui/react';
import { LuCheckCircle2, LuInfo, LuX, LuXCircle } from 'react-icons/lu';
import { ExternalToast, toast } from 'sonner';
import { match } from 'ts-pattern';

import { Icon } from '@/components/Icons';

export const toastCustom = (params: {
status?: 'info' | 'success' | 'error';
hideIcon?: boolean;
title: ReactNode;
description?: ReactNode;
actions?: ReactNode;
}) => {
const status = params.status ?? 'info';
const icon = match(status)
.with('info', () => LuInfo)
.with('success', () => LuCheckCircle2)
.with('error', () => LuXCircle)
.exhaustive();

const options: ExternalToast = {
duration: status === 'error' ? Infinity : 3000,
};

toast.custom(
(t) => (
<Flex>
<IconButton
zIndex={1}
size="xs"
aria-label="Fermer le message"
icon={<LuX />}
onClick={() => toast.dismiss(t)}
position="absolute"
top={-2.5}
right={-2.5}
borderRadius="full"
/>
<Card
w="356px"
position="relative"
overflow="hidden"
boxShadow="layout"
>
<Box
position="absolute"
top={0}
left={0}
bottom={0}
w="3px"
bg={`${status}.600`}
/>
<CardBody
display="flex"
flexDirection="column"
gap={1.5}
p={4}
color="gray.800"
_dark={{
color: 'white',
}}
>
<Flex alignItems="center" gap={2}>
<Heading size="xs" flex={1}>
{!params.hideIcon && (
<Icon
icon={icon}
mr={2}
fontSize="1.2em"
color={`${status}.500`}
/>
)}
{params.title}
</Heading>
{!!params.actions && (
<ButtonGroup size="xs">{params.actions}</ButtonGroup>
)}
</Flex>
{!!params.description && (
<Flex direction="column" fontSize="xs" color="text-dimmed">
{params.description}
</Flex>
)}
</CardBody>
</Card>
</Flex>
),
options
);
};
6 changes: 3 additions & 3 deletions src/features/account/AccountDeleteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { LuTrash2 } from 'react-icons/lu';

import { ConfirmModal } from '@/components/ConfirmModal';
import { useToastError } from '@/components/Toast';
import { toastCustom } from '@/components/Toast';
import {
AccountDeleteVerificationCodeModale,
SEARCH_PARAM_VERIFY_EMAIL,
Expand All @@ -25,7 +25,6 @@ export const AccountDeleteButton = () => {
);
const account = trpc.account.get.useQuery();

const toastError = useToastError();
const deleteAccountValidate = searchParams[SEARCH_PARAM_VERIFY_EMAIL];

const deleteAccount = trpc.account.deleteRequest.useMutation({
Expand All @@ -37,7 +36,8 @@ export const AccountDeleteButton = () => {
});
},
onError: () => {
toastError({
toastCustom({
status: 'error',
title: t('account:deleteAccount.feedbacks.updateError.title'),
});
},
Expand Down
7 changes: 3 additions & 4 deletions src/features/account/AccountEmailForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
FormFieldLabel,
} from '@/components/Form';
import { LoaderFull } from '@/components/LoaderFull';
import { useToastError } from '@/components/Toast';
import { toastCustom } from '@/components/Toast';
import { EmailVerificationCodeModale } from '@/features/account/EmailVerificationCodeModal';
import {
FormFieldsAccountEmail,
Expand All @@ -33,8 +33,6 @@ export const AccountEmailForm = () => {
staleTime: Infinity,
});

const toastError = useToastError();

const updateEmail = trpc.account.updateEmail.useMutation({
onSuccess: async ({ token }, { email }) => {
setSearchParams({
Expand All @@ -43,7 +41,8 @@ export const AccountEmailForm = () => {
});
},
onError: () => {
toastError({
toastCustom({
status: 'error',
title: t('account:email.feedbacks.updateError.title'),
});
},
Expand Down
11 changes: 5 additions & 6 deletions src/features/account/AccountProfileForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
FormFieldLabel,
} from '@/components/Form';
import { LoaderFull } from '@/components/LoaderFull';
import { useToastError, useToastSuccess } from '@/components/Toast';
import { toastCustom } from '@/components/Toast';
import {
FormFieldsAccountProfile,
zFormFieldsAccountProfile,
Expand All @@ -31,18 +31,17 @@ export const AccountProfileForm = () => {
staleTime: Infinity,
});

const toastSuccess = useToastSuccess();
const toastError = useToastError();

const updateAccount = trpc.account.update.useMutation({
onSuccess: async () => {
await trpcUtils.account.invalidate();
toastSuccess({
toastCustom({
status: 'success',
title: t('account:profile.feedbacks.updateSuccess.title'),
});
},
onError: () => {
toastError({
toastCustom({
status: 'error',
title: t('account:profile.feedbacks.updateError.title'),
});
},
Expand Down
Loading
Loading