MFA: shuffled components to avoid re-rendering closing the setup dialog

This commit is contained in:
gbuomprisco
2024-10-13 22:19:37 +08:00
parent fe2148730a
commit d137df2675
2 changed files with 93 additions and 97 deletions

View File

@@ -46,17 +46,25 @@ import { Trans } from '@kit/ui/trans';
import { MultiFactorAuthSetupDialog } from './multi-factor-auth-setup-dialog'; import { MultiFactorAuthSetupDialog } from './multi-factor-auth-setup-dialog';
const MAX_FACTOR_COUNT = 10;
export function MultiFactorAuthFactorsList(props: { userId: string }) { export function MultiFactorAuthFactorsList(props: { userId: string }) {
return (
<div className={'flex flex-col space-y-4'}>
<FactorsTableContainer userId={props.userId} />
<div>
<MultiFactorAuthSetupDialog userId={props.userId} />
</div>
</div>
);
}
function FactorsTableContainer(props: { userId: string }) {
const { const {
data: factors, data: factors,
isLoading, isLoading,
isError, isError,
} = useFetchAuthFactors(props.userId); } = useFetchAuthFactors(props.userId);
const [unEnrolling, setUnenrolling] = useState<string>();
if (isLoading) { if (isLoading) {
return ( return (
<div className={'flex items-center space-x-4'}> <div className={'flex items-center space-x-4'}>
@@ -103,37 +111,11 @@ export function MultiFactorAuthFactorsList(props: { userId: string }) {
<Trans i18nKey={'account:multiFactorAuthDescription'} /> <Trans i18nKey={'account:multiFactorAuthDescription'} />
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<div>
<MultiFactorAuthSetupDialog userId={props.userId} />
</div>
</div> </div>
); );
} }
const canAddNewFactors = allFactors.length < MAX_FACTOR_COUNT; return <FactorsTable factors={allFactors} userId={props.userId} />;
return (
<div className={'flex flex-col space-y-4'}>
<FactorsTable factors={allFactors} setUnenrolling={setUnenrolling} />
<If condition={canAddNewFactors}>
<div>
<MultiFactorAuthSetupDialog userId={props.userId} />
</div>
</If>
<If condition={unEnrolling}>
{(factorId) => (
<ConfirmUnenrollFactorModal
userId={props.userId}
factorId={factorId}
setIsModalOpen={() => setUnenrolling(undefined)}
/>
)}
</If>
</div>
);
} }
function ConfirmUnenrollFactorModal( function ConfirmUnenrollFactorModal(
@@ -157,7 +139,7 @@ function ConfirmUnenrollFactorModal(
const errorCode = response.data; const errorCode = response.data;
throw t(`auth:errors.${errorCode}`, { throw t(`auth:errors.${errorCode}`, {
defaultValue: t(`account:unenrollFactorError`) defaultValue: t(`account:unenrollFactorError`),
}); });
} }
}); });
@@ -205,75 +187,89 @@ function ConfirmUnenrollFactorModal(
} }
function FactorsTable({ function FactorsTable({
setUnenrolling,
factors, factors,
userId,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
setUnenrolling: (factorId: string) => void;
factors: Factor[]; factors: Factor[];
userId: string;
}>) { }>) {
const [unEnrolling, setUnenrolling] = useState<string>();
return ( return (
<Table> <>
<TableHeader> <Table>
<TableRow> <TableHeader>
<TableHead> <TableRow>
<Trans i18nKey={'account:factorName'} /> <TableHead>
</TableHead> <Trans i18nKey={'account:factorName'} />
<TableHead> </TableHead>
<Trans i18nKey={'account:factorType'} /> <TableHead>
</TableHead> <Trans i18nKey={'account:factorType'} />
<TableHead> </TableHead>
<Trans i18nKey={'account:factorStatus'} /> <TableHead>
</TableHead> <Trans i18nKey={'account:factorStatus'} />
</TableHead>
<TableHead /> <TableHead />
</TableRow>
</TableHeader>
<TableBody>
{factors.map((factor) => (
<TableRow key={factor.id}>
<TableCell>
<span className={'block truncate'}>{factor.friendly_name}</span>
</TableCell>
<TableCell>
<Badge variant={'info'} className={'inline-flex uppercase'}>
{factor.factor_type}
</Badge>
</TableCell>
<td>
<Badge
className={'inline-flex capitalize'}
variant={factor.status === 'verified' ? 'success' : 'outline'}
>
{factor.status}
</Badge>
</td>
<td className={'flex justify-end'}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={'ghost'}
size={'icon'}
onClick={() => setUnenrolling(factor.id)}
>
<X className={'h-4'} />
</Button>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey={'account:unenrollTooltip'} />
</TooltipContent>
</Tooltip>
</TooltipProvider>
</td>
</TableRow> </TableRow>
))} </TableHeader>
</TableBody>
</Table> <TableBody>
{factors.map((factor) => (
<TableRow key={factor.id}>
<TableCell>
<span className={'block truncate'}>{factor.friendly_name}</span>
</TableCell>
<TableCell>
<Badge variant={'info'} className={'inline-flex uppercase'}>
{factor.factor_type}
</Badge>
</TableCell>
<td>
<Badge
className={'inline-flex capitalize'}
variant={factor.status === 'verified' ? 'success' : 'outline'}
>
{factor.status}
</Badge>
</td>
<td className={'flex justify-end'}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={'ghost'}
size={'icon'}
onClick={() => setUnenrolling(factor.id)}
>
<X className={'h-4'} />
</Button>
</TooltipTrigger>
<TooltipContent>
<Trans i18nKey={'account:unenrollTooltip'} />
</TooltipContent>
</Tooltip>
</TooltipProvider>
</td>
</TableRow>
))}
</TableBody>
</Table>
<If condition={unEnrolling}>
{(factorId) => (
<ConfirmUnenrollFactorModal
userId={userId}
factorId={factorId}
setIsModalOpen={() => setUnenrolling(undefined)}
/>
)}
</If>
</>
); );
} }
@@ -291,13 +287,13 @@ function useUnenrollFactor(userId: string) {
return { return {
success: false as const, success: false as const,
data: error.code as string, data: error.code as string,
} };
} }
return { return {
success: true as const, success: true as const,
data, data,
} };
}; };
return useMutation({ return useMutation({

View File

@@ -53,7 +53,7 @@ export function MultiFactorAuthSetupDialog(props: { userId: string }) {
const onEnrollSuccess = useCallback(() => { const onEnrollSuccess = useCallback(() => {
setIsOpen(false); setIsOpen(false);
return toast.success(t(`multiFactorSetupSuccess`)); return toast.success(t(`account:multiFactorSetupSuccess`));
}, [t]); }, [t]);
return ( return (