Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
@@ -1,12 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
import { useAction } from 'next-safe-action/hooks';
|
||||
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { Heading } from '@kit/ui/heading';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { Separator } from '@kit/ui/separator';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import { acceptInvitationAction } from '../../server/actions/team-invitations-server-actions';
|
||||
import { InvitationSubmitButton } from './invitation-submit-button';
|
||||
import { SignOutInvitationButton } from './sign-out-invitation-button';
|
||||
|
||||
export function AcceptInvitationContainer(props: {
|
||||
@@ -28,11 +32,13 @@ export function AcceptInvitationContainer(props: {
|
||||
nextPath: string;
|
||||
};
|
||||
}) {
|
||||
const { execute, isPending } = useAction(acceptInvitationAction);
|
||||
|
||||
return (
|
||||
<div className={'flex flex-col items-center space-y-4'}>
|
||||
<Heading className={'text-center'} level={4}>
|
||||
<Trans
|
||||
i18nKey={'teams:acceptInvitationHeading'}
|
||||
i18nKey={'teams.acceptInvitationHeading'}
|
||||
values={{
|
||||
accountName: props.invitation.account.name,
|
||||
}}
|
||||
@@ -53,7 +59,7 @@ export function AcceptInvitationContainer(props: {
|
||||
|
||||
<div className={'text-muted-foreground text-center text-sm'}>
|
||||
<Trans
|
||||
i18nKey={'teams:acceptInvitationDescription'}
|
||||
i18nKey={'teams.acceptInvitationDescription'}
|
||||
values={{
|
||||
accountName: props.invitation.account.name,
|
||||
}}
|
||||
@@ -64,20 +70,24 @@ export function AcceptInvitationContainer(props: {
|
||||
<form
|
||||
data-test={'join-team-form'}
|
||||
className={'w-full'}
|
||||
action={acceptInvitationAction}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
execute({
|
||||
inviteToken: props.inviteToken,
|
||||
nextPath: props.paths.nextPath,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name={'inviteToken'} value={props.inviteToken} />
|
||||
|
||||
<input
|
||||
type={'hidden'}
|
||||
name={'nextPath'}
|
||||
value={props.paths.nextPath}
|
||||
/>
|
||||
|
||||
<InvitationSubmitButton
|
||||
email={props.email}
|
||||
accountName={props.invitation.account.name}
|
||||
/>
|
||||
<Button type={'submit'} className={'w-full'} disabled={isPending}>
|
||||
<Trans
|
||||
i18nKey={isPending ? 'teams.joiningTeam' : 'teams.continueAs'}
|
||||
values={{
|
||||
accountName: props.invitation.account.name,
|
||||
email: props.email,
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<Separator />
|
||||
@@ -85,7 +95,7 @@ export function AcceptInvitationContainer(props: {
|
||||
<SignOutInvitationButton nextPath={props.paths.signOutNext} />
|
||||
|
||||
<span className={'text-muted-foreground text-center text-xs'}>
|
||||
<Trans i18nKey={'teams:signInWithDifferentAccountDescription'} />
|
||||
<Trans i18nKey={'teams.signInWithDifferentAccountDescription'} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useMemo, useState } from 'react';
|
||||
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { Ellipsis } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
import { Database } from '@kit/supabase/database';
|
||||
import { Badge } from '@kit/ui/badge';
|
||||
@@ -43,7 +43,7 @@ export function AccountInvitationsTable({
|
||||
invitations,
|
||||
permissions,
|
||||
}: AccountInvitationsTableProps) {
|
||||
const { t } = useTranslation('teams');
|
||||
const t = useTranslations('teams');
|
||||
const [search, setSearch] = useState('');
|
||||
const columns = useGetColumns(permissions);
|
||||
|
||||
@@ -82,7 +82,7 @@ function useGetColumns(permissions: {
|
||||
canRemoveInvitation: boolean;
|
||||
currentUserRoleHierarchy: number;
|
||||
}): ColumnDef<Invitations[0]>[] {
|
||||
const { t } = useTranslation('teams');
|
||||
const t = useTranslations('teams');
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
@@ -96,7 +96,7 @@ function useGetColumns(permissions: {
|
||||
return (
|
||||
<span
|
||||
data-test={'invitation-email'}
|
||||
className={'flex items-center space-x-4 text-left'}
|
||||
className={'flex items-center gap-x-2 text-left'}
|
||||
>
|
||||
<span>
|
||||
<ProfileAvatar text={email} />
|
||||
@@ -172,19 +172,21 @@ function ActionsDropdown({
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'ghost'} size={'icon'}>
|
||||
<Ellipsis className={'h-5 w-5'} />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button variant={'ghost'} size={'icon'}>
|
||||
<Ellipsis className={'h-5 w-5'} />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuContent className="min-w-52">
|
||||
<If condition={permissions.canUpdateInvitation}>
|
||||
<DropdownMenuItem
|
||||
data-test={'update-invitation-trigger'}
|
||||
onClick={() => setIsUpdatingRole(true)}
|
||||
>
|
||||
<Trans i18nKey={'teams:updateInvitation'} />
|
||||
<Trans i18nKey={'teams.updateInvitation'} />
|
||||
</DropdownMenuItem>
|
||||
|
||||
<If condition={getIsInviteExpired(invitation.expires_at)}>
|
||||
@@ -192,7 +194,7 @@ function ActionsDropdown({
|
||||
data-test={'renew-invitation-trigger'}
|
||||
onClick={() => setIsRenewingInvite(true)}
|
||||
>
|
||||
<Trans i18nKey={'teams:renewInvitation'} />
|
||||
<Trans i18nKey={'teams.renewInvitation'} />
|
||||
</DropdownMenuItem>
|
||||
</If>
|
||||
</If>
|
||||
@@ -200,9 +202,10 @@ function ActionsDropdown({
|
||||
<If condition={permissions.canRemoveInvitation}>
|
||||
<DropdownMenuItem
|
||||
data-test={'remove-invitation-trigger'}
|
||||
variant="destructive"
|
||||
onClick={() => setIsDeletingInvite(true)}
|
||||
>
|
||||
<Trans i18nKey={'teams:removeInvitation'} />
|
||||
<Trans i18nKey={'teams.removeInvitation'} />
|
||||
</DropdownMenuItem>
|
||||
</If>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useState, useTransition } from 'react';
|
||||
'use client';
|
||||
|
||||
import { useAction } from 'next-safe-action/hooks';
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
import {
|
||||
@@ -11,6 +13,7 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from '@kit/ui/alert-dialog';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { useAsyncDialog } from '@kit/ui/hooks/use-async-dialog';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
@@ -25,22 +28,35 @@ export function DeleteInvitationDialog({
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
invitationId: number;
|
||||
}) {
|
||||
const { dialogProps, isPending, setIsPending, setOpen } = useAsyncDialog({
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
});
|
||||
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<AlertDialog
|
||||
open={dialogProps.open}
|
||||
onOpenChange={dialogProps.onOpenChange}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
<Trans i18nKey="team:deleteInvitation" />
|
||||
<Trans i18nKey="teams.deleteInvitation" />
|
||||
</AlertDialogTitle>
|
||||
|
||||
<AlertDialogDescription>
|
||||
<Trans i18nKey="team:deleteInvitationDialogDescription" />
|
||||
<Trans i18nKey="teams.deleteInvitationDialogDescription" />
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<DeleteInvitationForm
|
||||
setIsOpen={setIsOpen}
|
||||
invitationId={invitationId}
|
||||
isPending={isPending}
|
||||
setIsPending={setIsPending}
|
||||
onSuccess={() => {
|
||||
setIsPending(false);
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
@@ -49,48 +65,45 @@ export function DeleteInvitationDialog({
|
||||
|
||||
function DeleteInvitationForm({
|
||||
invitationId,
|
||||
setIsOpen,
|
||||
isPending,
|
||||
setIsPending,
|
||||
onSuccess,
|
||||
}: {
|
||||
invitationId: number;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
isPending: boolean;
|
||||
setIsPending: (pending: boolean) => void;
|
||||
onSuccess: () => void;
|
||||
}) {
|
||||
const [isSubmitting, startTransition] = useTransition();
|
||||
const [error, setError] = useState<boolean>();
|
||||
|
||||
const onInvitationRemoved = () => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
await deleteInvitationAction({ invitationId });
|
||||
|
||||
setIsOpen(false);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
const { execute, hasErrored } = useAction(deleteInvitationAction, {
|
||||
onExecute: () => setIsPending(true),
|
||||
onSuccess: () => onSuccess(),
|
||||
onSettled: () => setIsPending(false),
|
||||
});
|
||||
|
||||
return (
|
||||
<form data-test={'delete-invitation-form'} action={onInvitationRemoved}>
|
||||
<form
|
||||
data-test={'delete-invitation-form'}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
execute({ invitationId });
|
||||
}}
|
||||
>
|
||||
<div className={'flex flex-col space-y-6'}>
|
||||
<p className={'text-muted-foreground text-sm'}>
|
||||
<Trans i18nKey={'common:modalConfirmationQuestion'} />
|
||||
<Trans i18nKey={'common.modalConfirmationQuestion'} />
|
||||
</p>
|
||||
|
||||
<If condition={error}>
|
||||
<If condition={hasErrored}>
|
||||
<RemoveInvitationErrorAlert />
|
||||
</If>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
<Trans i18nKey={'common:cancel'} />
|
||||
<AlertDialogCancel disabled={isPending}>
|
||||
<Trans i18nKey={'common.cancel'} />
|
||||
</AlertDialogCancel>
|
||||
|
||||
<Button
|
||||
type={'submit'}
|
||||
variant={'destructive'}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
<Trans i18nKey={'teams:deleteInvitation'} />
|
||||
<Button type={'submit'} variant={'destructive'} disabled={isPending}>
|
||||
<Trans i18nKey={'teams.deleteInvitation'} />
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</div>
|
||||
@@ -102,11 +115,11 @@ function RemoveInvitationErrorAlert() {
|
||||
return (
|
||||
<Alert variant={'destructive'}>
|
||||
<AlertTitle>
|
||||
<Trans i18nKey={'teams:deleteInvitationErrorTitle'} />
|
||||
<Trans i18nKey={'teams.deleteInvitationErrorTitle'} />
|
||||
</AlertTitle>
|
||||
|
||||
<AlertDescription>
|
||||
<Trans i18nKey={'teams:deleteInvitationErrorMessage'} />
|
||||
<Trans i18nKey={'teams.deleteInvitationErrorMessage'} />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ export function InvitationSubmitButton(props: {
|
||||
return (
|
||||
<Button type={'submit'} className={'w-full'} disabled={pending}>
|
||||
<Trans
|
||||
i18nKey={pending ? 'teams:joiningTeam' : 'teams:continueAs'}
|
||||
i18nKey={pending ? 'teams.joiningTeam' : 'teams.continueAs'}
|
||||
values={{
|
||||
accountName: props.accountName,
|
||||
email: props.email,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useState, useTransition } from 'react';
|
||||
'use client';
|
||||
|
||||
import { useAction } from 'next-safe-action/hooks';
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
import {
|
||||
@@ -11,6 +13,7 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from '@kit/ui/alert-dialog';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { useAsyncDialog } from '@kit/ui/hooks/use-async-dialog';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
@@ -27,25 +30,38 @@ export function RenewInvitationDialog({
|
||||
invitationId: number;
|
||||
email: string;
|
||||
}) {
|
||||
const { dialogProps, isPending, setIsPending, setOpen } = useAsyncDialog({
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
});
|
||||
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<AlertDialog
|
||||
open={dialogProps.open}
|
||||
onOpenChange={dialogProps.onOpenChange}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
<Trans i18nKey="team:renewInvitation" />
|
||||
<Trans i18nKey="team.renewInvitation" />
|
||||
</AlertDialogTitle>
|
||||
|
||||
<AlertDialogDescription>
|
||||
<Trans
|
||||
i18nKey="team:renewInvitationDialogDescription"
|
||||
i18nKey="team.renewInvitationDialogDescription"
|
||||
values={{ email }}
|
||||
/>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<RenewInvitationForm
|
||||
setIsOpen={setIsOpen}
|
||||
invitationId={invitationId}
|
||||
isPending={isPending}
|
||||
setIsPending={setIsPending}
|
||||
onSuccess={() => {
|
||||
setIsPending(false);
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
@@ -54,47 +70,48 @@ export function RenewInvitationDialog({
|
||||
|
||||
function RenewInvitationForm({
|
||||
invitationId,
|
||||
setIsOpen,
|
||||
isPending,
|
||||
setIsPending,
|
||||
onSuccess,
|
||||
}: {
|
||||
invitationId: number;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
isPending: boolean;
|
||||
setIsPending: (pending: boolean) => void;
|
||||
onSuccess: () => void;
|
||||
}) {
|
||||
const [isSubmitting, startTransition] = useTransition();
|
||||
const [error, setError] = useState<boolean>();
|
||||
|
||||
const inInvitationRenewed = () => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
await renewInvitationAction({ invitationId });
|
||||
|
||||
setIsOpen(false);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
const { execute, hasErrored } = useAction(renewInvitationAction, {
|
||||
onExecute: () => setIsPending(true),
|
||||
onSuccess: () => onSuccess(),
|
||||
onSettled: () => setIsPending(false),
|
||||
});
|
||||
|
||||
return (
|
||||
<form action={inInvitationRenewed}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
execute({ invitationId });
|
||||
}}
|
||||
>
|
||||
<div className={'flex flex-col space-y-6'}>
|
||||
<p className={'text-muted-foreground text-sm'}>
|
||||
<Trans i18nKey={'common:modalConfirmationQuestion'} />
|
||||
<Trans i18nKey={'common.modalConfirmationQuestion'} />
|
||||
</p>
|
||||
|
||||
<If condition={error}>
|
||||
<If condition={hasErrored}>
|
||||
<RenewInvitationErrorAlert />
|
||||
</If>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
<Trans i18nKey={'common:cancel'} />
|
||||
<AlertDialogCancel disabled={isPending}>
|
||||
<Trans i18nKey={'common.cancel'} />
|
||||
</AlertDialogCancel>
|
||||
|
||||
<Button
|
||||
type={'submit'}
|
||||
data-test={'confirm-renew-invitation'}
|
||||
disabled={isSubmitting}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Trans i18nKey={'teams:renewInvitation'} />
|
||||
<Trans i18nKey={'teams.renewInvitation'} />
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</div>
|
||||
@@ -106,11 +123,11 @@ function RenewInvitationErrorAlert() {
|
||||
return (
|
||||
<Alert variant={'destructive'}>
|
||||
<AlertTitle>
|
||||
<Trans i18nKey={'teams:renewInvitationErrorTitle'} />
|
||||
<Trans i18nKey={'teams.renewInvitationErrorTitle'} />
|
||||
</AlertTitle>
|
||||
|
||||
<AlertDescription>
|
||||
<Trans i18nKey={'teams:renewInvitationErrorDescription'} />
|
||||
<Trans i18nKey={'teams.renewInvitationErrorDescription'} />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ export function SignOutInvitationButton(
|
||||
window.location.assign(safePath);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey={'teams:signInWithDifferentAccount'} />
|
||||
<Trans i18nKey={'teams.signInWithDifferentAccount'} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useState, useTransition } from 'react';
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useAction } from 'next-safe-action/hooks';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
|
||||
import { Button } from '@kit/ui/button';
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@kit/ui/form';
|
||||
import { useAsyncDialog } from '@kit/ui/hooks/use-async-dialog';
|
||||
import { If } from '@kit/ui/if';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
@@ -45,16 +47,21 @@ export function UpdateInvitationDialog({
|
||||
userRole: Role;
|
||||
userRoleHierarchy: number;
|
||||
}) {
|
||||
const { dialogProps, isPending, setIsPending, setOpen } = useAsyncDialog({
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent>
|
||||
<Dialog {...dialogProps}>
|
||||
<DialogContent showCloseButton={!isPending}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans i18nKey={'teams:updateMemberRoleModalHeading'} />
|
||||
<Trans i18nKey={'teams.updateMemberRoleModalHeading'} />
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
<Trans i18nKey={'teams:updateMemberRoleModalDescription'} />
|
||||
<Trans i18nKey={'teams.updateMemberRoleModalDescription'} />
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -62,7 +69,12 @@ export function UpdateInvitationDialog({
|
||||
invitationId={invitationId}
|
||||
userRole={userRole}
|
||||
userRoleHierarchy={userRoleHierarchy}
|
||||
setIsOpen={setIsOpen}
|
||||
isPending={isPending}
|
||||
setIsPending={setIsPending}
|
||||
onSuccess={() => {
|
||||
setIsPending(false);
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -73,31 +85,24 @@ function UpdateInvitationForm({
|
||||
invitationId,
|
||||
userRole,
|
||||
userRoleHierarchy,
|
||||
setIsOpen,
|
||||
isPending,
|
||||
setIsPending,
|
||||
onSuccess,
|
||||
}: React.PropsWithChildren<{
|
||||
invitationId: number;
|
||||
userRole: Role;
|
||||
userRoleHierarchy: number;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
isPending: boolean;
|
||||
setIsPending: (pending: boolean) => void;
|
||||
onSuccess: () => void;
|
||||
}>) {
|
||||
const { t } = useTranslation('teams');
|
||||
const [pending, startTransition] = useTransition();
|
||||
const [error, setError] = useState<boolean>();
|
||||
const t = useTranslations('teams');
|
||||
|
||||
const onSubmit = ({ role }: { role: Role }) => {
|
||||
startTransition(async () => {
|
||||
try {
|
||||
await updateInvitationAction({
|
||||
invitationId,
|
||||
role,
|
||||
});
|
||||
|
||||
setIsOpen(false);
|
||||
} catch {
|
||||
setError(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
const { execute, hasErrored } = useAction(updateInvitationAction, {
|
||||
onExecute: () => setIsPending(true),
|
||||
onSuccess: () => onSuccess(),
|
||||
onSettled: () => setIsPending(false),
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(
|
||||
@@ -122,10 +127,12 @@ function UpdateInvitationForm({
|
||||
<Form {...form}>
|
||||
<form
|
||||
data-test={'update-invitation-form'}
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
onSubmit={form.handleSubmit(({ role }) => {
|
||||
execute({ invitationId, role });
|
||||
})}
|
||||
className={'flex flex-col space-y-6'}
|
||||
>
|
||||
<If condition={error}>
|
||||
<If condition={hasErrored}>
|
||||
<UpdateRoleErrorAlert />
|
||||
</If>
|
||||
|
||||
@@ -135,7 +142,7 @@ function UpdateInvitationForm({
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<Trans i18nKey={'teams:roleLabel'} />
|
||||
<Trans i18nKey={'teams.roleLabel'} />
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
@@ -145,16 +152,18 @@ function UpdateInvitationForm({
|
||||
roles={roles}
|
||||
currentUserRole={userRole}
|
||||
value={field.value}
|
||||
onChange={(newRole) =>
|
||||
form.setValue(field.name, newRole)
|
||||
}
|
||||
onChange={(newRole) => {
|
||||
if (newRole) {
|
||||
form.setValue(field.name, newRole);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</RolesDataProvider>
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
<Trans i18nKey={'teams:updateRoleDescription'} />
|
||||
<Trans i18nKey={'teams.updateRoleDescription'} />
|
||||
</FormDescription>
|
||||
|
||||
<FormMessage />
|
||||
@@ -163,8 +172,8 @@ function UpdateInvitationForm({
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button type={'submit'} disabled={pending}>
|
||||
<Trans i18nKey={'teams:updateRoleSubmitLabel'} />
|
||||
<Button type={'submit'} disabled={isPending}>
|
||||
<Trans i18nKey={'teams.updateRoleSubmitLabel'} />
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
@@ -175,11 +184,11 @@ function UpdateRoleErrorAlert() {
|
||||
return (
|
||||
<Alert variant={'destructive'}>
|
||||
<AlertTitle>
|
||||
<Trans i18nKey={'teams:updateRoleErrorHeading'} />
|
||||
<Trans i18nKey={'teams.updateRoleErrorHeading'} />
|
||||
</AlertTitle>
|
||||
|
||||
<AlertDescription>
|
||||
<Trans i18nKey={'teams:updateRoleErrorMessage'} />
|
||||
<Trans i18nKey={'teams.updateRoleErrorMessage'} />
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user