♻️(frontend) improve the ui error and message info ui

Improve the ui error and message info ui:
- Can use a icon in TextErrors component
- use mode the Alert component to display message info
This commit is contained in:
Anthony LC
2024-06-14 11:39:57 +02:00
committed by Anthony LC
parent 62098ec753
commit 5ba35dbc1d
12 changed files with 146 additions and 119 deletions

View File

@@ -29,6 +29,7 @@ and this project adheres to
- (frontend) pdf has title doc (#84)
- ⚡️(e2e) unique login between tests (#80)
- ⚡️(CI) improve e2e job (#86)
- ♻️(frontend) improve the error and message info ui (#93)
## Fixed

View File

@@ -1,3 +1,5 @@
import { Alert, VariantType } from '@openfun/cunningham-react';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Text, TextType } from '@/components';
@@ -5,40 +7,38 @@ import { Box, Text, TextType } from '@/components';
interface TextErrorsProps extends TextType {
causes?: string[];
defaultMessage?: string;
icon?: ReactNode;
}
export const TextErrors = ({
causes,
defaultMessage,
icon,
...textProps
}: TextErrorsProps) => {
const { t } = useTranslation();
return (
<Box>
{causes &&
causes.map((cause, i) => (
<Text
key={`causes-${i}`}
$margin={{ top: 'small' }}
$theme="danger"
$textAlign="center"
{...textProps}
>
{cause}
</Text>
))}
<Alert canClose={false} type={VariantType.ERROR} icon={icon}>
<Box $direction="column" $gap="0.2rem">
{causes &&
causes.map((cause, i) => (
<Text
key={`causes-${i}`}
$theme="danger"
$textAlign="center"
{...textProps}
>
{cause}
</Text>
))}
{!causes && (
<Text
$margin={{ top: 'small' }}
$theme="danger"
$textAlign="center"
{...textProps}
>
{defaultMessage || t('Something bad happens, please retry.')}
</Text>
)}
</Box>
{!causes && (
<Text $theme="danger" $textAlign="center" {...textProps}>
{defaultMessage || t('Something bad happens, please retry.')}
</Text>
)}
</Box>
</Alert>
);
};

View File

@@ -172,13 +172,9 @@ describe('ModalRole', () => {
{ wrapper: AppWrapper },
);
expect(
screen.getByText('You are the sole owner of this group.'),
).toBeInTheDocument();
expect(
screen.getByText(
'Make another member the group owner, before you can change your own role.',
'You are the sole owner of this group, make another member the group owner, before you can change your own role.',
),
).toBeInTheDocument();

View File

@@ -53,6 +53,7 @@ export const MemberAction = ({
}}
color="primary-text"
icon={<span className="material-icons">edit</span>}
size="small"
>
<Text $theme="primary">{t('Update role')}</Text>
</Button>
@@ -64,6 +65,7 @@ export const MemberAction = ({
}}
color="primary-text"
icon={<span className="material-icons">delete</span>}
size="small"
>
{t('Remove from group')}
</Button>

View File

@@ -1,12 +1,13 @@
import {
Alert,
Button,
Modal,
ModalSize,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { t } from 'i18next';
import { useRouter } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import IconUser from '@/assets/icons/icon-user.svg';
import { Box, Text, TextErrors } from '@/components';
@@ -28,6 +29,7 @@ export const ModalDelete = ({ access, onClose, doc }: ModalDeleteProps) => {
const { toast } = useToastProvider();
const { colorsTokens } = useCunninghamTheme();
const router = useRouter();
const { t } = useTranslation();
const { isMyself, isLastOwner, isOtherOwner } = useWhoAmI(access);
const isNotAllowed = isOtherOwner || isLastOwner;
@@ -89,33 +91,28 @@ export const ModalDelete = ({ access, onClose, doc }: ModalDeleteProps) => {
}
>
<Box aria-label={t('Radio buttons to update the roles')}>
<Text>
{t('Are you sure you want to remove this member from the document?')}
</Text>
{isErrorUpdate && (
<TextErrors
$margin={{ bottom: 'small' }}
causes={errorUpdate.cause}
/>
{!isLastOwner && !isOtherOwner && !isErrorUpdate && (
<Alert canClose={false} type={VariantType.INFO}>
<Text>
{t(
'Are you sure you want to remove this member from the document?',
)}
</Text>
</Alert>
)}
{(isLastOwner || isOtherOwner) && (
<Text
$theme="warning"
$direction="row"
$align="center"
$gap="0.5rem"
$margin="tiny"
$justify="center"
>
<span className="material-icons">warning</span>
{isLastOwner &&
t(
'You are the last owner, you cannot be removed from your document.',
)}
{isOtherOwner && t('You cannot remove other owner.')}
</Text>
{isErrorUpdate && <TextErrors causes={errorUpdate.cause} />}
{(isLastOwner || isOtherOwner) && !isErrorUpdate && (
<Alert canClose={false} type={VariantType.WARNING}>
<Text>
{isLastOwner &&
t(
'You are the last owner, you cannot be removed from your document.',
)}
{isOtherOwner && t('You cannot remove other owner.')}
</Text>
</Alert>
)}
<Text

View File

@@ -1,4 +1,5 @@
import {
Alert,
Button,
Modal,
ModalSize,
@@ -84,38 +85,36 @@ export const ModalRole = ({
title={t('Update the role')}
>
<Box aria-label={t('Radio buttons to update the roles')}>
{isErrorUpdate && (
<TextErrors
$margin={{ bottom: 'small' }}
causes={errorUpdate.cause}
/>
)}
{isErrorUpdate && <TextErrors causes={errorUpdate.cause} />}
{(isLastOwner || isOtherOwner) && (
<Text
$theme="warning"
<Box
$direction="row"
$align="center"
$gap="0.5rem"
$margin={{ bottom: 'tiny', top: 'none' }}
as="div"
>
<span className="material-icons">warning</span>
{isLastOwner && (
<Box $align="flex-start">
<Text $theme="warning">
{t('You are the sole owner of this group.')}
<Alert
canClose={false}
type={VariantType.WARNING}
icon={
<Text className="material-icons" $theme="warning">
warning
</Text>
<Text $theme="warning">
{t(
'Make another member the group owner, before you can change your own role.',
)}
</Text>
</Box>
)}
{isOtherOwner && t('You cannot update the role of other owner.')}
</Text>
}
>
{isLastOwner && (
<Box $direction="column" $gap="0.2rem">
<Text $theme="warning">
{t(
'You are the sole owner of this group, make another member the group owner, before you can change your own role.',
)}
</Text>
</Box>
)}
{isOtherOwner && t('You cannot update the role of other owner.')}
</Alert>
</Box>
)}
<ChooseRole

View File

@@ -1,4 +1,5 @@
import {
Alert,
Button,
Modal,
ModalSize,
@@ -83,16 +84,18 @@ export const ModalRemovePad = ({ onClose, pad }: ModalRemovePadProps) => {
$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} />
{!isError && (
<Alert canClose={false} type={VariantType.WARNING}>
<Text>
{t('Are you sure you want to delete the document "{{title}}"?', {
title: pad.title,
})}
</Text>
</Alert>
)}
{isError && <TextErrors causes={error.cause} />}
<Text
as="p"
$padding="small"

View File

@@ -1,4 +1,5 @@
import {
Alert,
Button,
Modal,
ModalSize,
@@ -6,8 +7,8 @@ import {
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { t } from 'i18next';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Text } from '@/components';
import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
@@ -29,6 +30,7 @@ export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
const [title, setTitle] = useState(pad.title);
const { toast } = useToastProvider();
const [padPublic, setPadPublic] = useState(pad.is_public);
const { t } = useTranslation();
const {
mutate: updatePad,
@@ -92,10 +94,11 @@ export const ModalUpdatePad = ({ onClose, pad }: ModalUpdatePadProps) => {
<Box
$margin={{ bottom: 'xl' }}
aria-label={t('Content modal to update the document')}
$gap="1rem"
>
<Text as="p" $margin={{ bottom: 'big' }}>
{t('Enter the new name of the selected document.')}
</Text>
<Alert canClose={false} type={VariantType.INFO}>
<Text>{t('Enter the new name of the selected document.')}</Text>
</Alert>
<Box $gap="1rem">
<InputPadName

View File

@@ -1,4 +1,5 @@
import {
Alert,
Button,
Loader,
Modal,
@@ -136,12 +137,15 @@ export const ModalPDF = ({ onClose, templateOptions, pad }: ModalPDFProps) => {
<Box
$margin={{ bottom: 'xl' }}
aria-label={t('Content modal to generate a PDF')}
$gap="1.5rem"
>
<Text as="p" $margin={{ bottom: 'big' }}>
{t(
'Generate a PDF from your document, it will be inserted in the selected template.',
)}
</Text>
<Alert canClose={false} type={VariantType.INFO}>
<Text>
{t(
'Generate a PDF from your document, it will be inserted in the selected template.',
)}
</Text>
</Alert>
<Select
clearable={false}

View File

@@ -120,9 +120,7 @@ describe('PanelPads', () => {
expect(screen.getByRole('status')).toBeInTheDocument();
expect(
await screen.findByText(
'Something bad happens, please refresh the page.',
),
await screen.findByText('Something bad happens, please retry.'),
).toBeInTheDocument();
});

View File

@@ -2,7 +2,8 @@ import { Loader } from '@openfun/cunningham-react';
import React, { useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Text } from '@/components';
import { APIError } from '@/api';
import { Box, Text, TextErrors } from '@/components';
import { InfiniteScroll } from '@/components/InfiniteScroll';
import { Pad, usePads } from '@/features/pads/pad-management';
@@ -12,23 +13,13 @@ import { PadItem } from './PadItem';
interface PanelTeamsStateProps {
isLoading: boolean;
isError: boolean;
error: APIError<unknown> | null;
pads?: Pad[];
}
const PadListState = ({ isLoading, isError, pads }: PanelTeamsStateProps) => {
const PadListState = ({ isLoading, error, pads }: PanelTeamsStateProps) => {
const { t } = useTranslation();
if (isError) {
return (
<Box $justify="center" $margin={{ bottom: 'big' }}>
<Text $theme="danger" $align="center" $textAlign="center">
{t('Something bad happens, please refresh the page.')}
</Text>
</Box>
);
}
if (isLoading) {
return (
<Box $align="center" $margin="large">
@@ -37,7 +28,7 @@ const PadListState = ({ isLoading, isError, pads }: PanelTeamsStateProps) => {
);
}
if (!pads?.length) {
if (!pads?.length && !error) {
return (
<Box $justify="center" $margin="small">
<Text
@@ -57,14 +48,35 @@ const PadListState = ({ isLoading, isError, pads }: PanelTeamsStateProps) => {
);
}
return pads.map((pad) => <PadItem pad={pad} key={pad.id} />);
return (
<>
{pads?.map((pad) => <PadItem pad={pad} key={pad.id} />)}
{error && (
<Box
$justify="center"
$margin={{ vertical: 'big', horizontal: 'auto' }}
>
<TextErrors
causes={error.cause}
icon={
error.status === 502 ? (
<Text className="material-icons" $theme="danger">
wifi_off
</Text>
) : undefined
}
/>
</Box>
)}
</>
);
};
export const PadList = () => {
const ordering = usePadPanelStore((state) => state.ordering);
const {
data,
isError,
error,
isLoading,
fetchNextPage,
hasNextPage,
@@ -93,7 +105,7 @@ export const PadList = () => {
$margin={{ top: 'none' }}
role="listbox"
>
<PadListState isLoading={isLoading} isError={isError} pads={pads} />
<PadListState isLoading={isLoading} error={error} pads={pads} />
</InfiniteScroll>
</Box>
);

View File

@@ -3,8 +3,7 @@ import { useRouter as useNavigate } from 'next/navigation';
import { useRouter } from 'next/router';
import { ReactElement } from 'react';
import { Box } from '@/components';
import { TextErrors } from '@/components/TextErrors';
import { Box, Text, TextErrors } from '@/components/';
import { PadEditor } from '@/features/pads/pad-editor';
import { usePad } from '@/features/pads/pad-management';
import { PadLayout } from '@/layouts';
@@ -36,7 +35,20 @@ const Pad = ({ id }: PadProps) => {
return null;
}
return <TextErrors causes={error.cause} />;
return (
<Box $margin="large">
<TextErrors
causes={error.cause}
icon={
error.status === 502 ? (
<Text className="material-icons" $theme="danger">
wifi_off
</Text>
) : undefined
}
/>
</Box>
);
}
if (isLoading || !pad) {