♻️(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:
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user