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:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View File

@@ -1,10 +1,9 @@
'use client';
import { useFormStatus } from 'react-dom';
import { zodResolver } from '@hookform/resolvers/zod';
import { useAction } from 'next-safe-action/hooks';
import { useForm, useWatch } from 'react-hook-form';
import { z } from 'zod';
import * as z from 'zod';
import { ErrorBoundary } from '@kit/monitoring/components';
import { VerifyOtpForm } from '@kit/otp/components';
@@ -100,12 +99,12 @@ function DeleteTeamContainer(props: {
<div className={'flex flex-col space-y-4'}>
<div className={'flex flex-col space-y-1'}>
<span className={'text-sm font-medium'}>
<Trans i18nKey={'teams:deleteTeam'} />
<Trans i18nKey={'teams.deleteTeam'} />
</span>
<p className={'text-muted-foreground text-sm'}>
<Trans
i18nKey={'teams:deleteTeamDescription'}
i18nKey={'teams.deleteTeamDescription'}
values={{
teamName: props.account.name,
}}
@@ -115,25 +114,27 @@ function DeleteTeamContainer(props: {
<div>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
data-test={'delete-team-trigger'}
type={'button'}
variant={'destructive'}
>
<Trans i18nKey={'teams:deleteTeam'} />
</Button>
</AlertDialogTrigger>
<AlertDialogTrigger
render={
<Button
data-test={'delete-team-trigger'}
type={'button'}
variant={'destructive'}
>
<Trans i18nKey={'teams.deleteTeam'} />
</Button>
}
/>
<AlertDialogContent onEscapeKeyDown={(e) => e.preventDefault()}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
<Trans i18nKey={'teams:deletingTeam'} />
<Trans i18nKey={'teams.deletingTeam'} />
</AlertDialogTitle>
<AlertDialogDescription>
<Trans
i18nKey={'teams:deletingTeamDescription'}
i18nKey={'teams.deletingTeamDescription'}
values={{
teamName: props.account.name,
}}
@@ -161,6 +162,8 @@ function DeleteTeamConfirmationForm({
}) {
const { data: user } = useUser();
const { execute, isPending } = useAction(deleteTeamAccountAction);
const form = useForm({
mode: 'onChange',
reValidateMode: 'onChange',
@@ -188,7 +191,7 @@ function DeleteTeamConfirmationForm({
onSuccess={(otp) => form.setValue('otp', otp, { shouldValidate: true })}
CancelButton={
<AlertDialogCancel className={'m-0'}>
<Trans i18nKey={'common:cancel'} />
<Trans i18nKey={'common.cancel'} />
</AlertDialogCancel>
}
/>
@@ -201,7 +204,10 @@ function DeleteTeamConfirmationForm({
<form
data-test={'delete-team-form'}
className={'flex flex-col space-y-4'}
action={deleteTeamAccountAction}
onSubmit={(e) => {
e.preventDefault();
execute({ accountId: id, otp });
}}
>
<div className={'flex flex-col space-y-2'}>
<div
@@ -211,7 +217,7 @@ function DeleteTeamConfirmationForm({
>
<div>
<Trans
i18nKey={'teams:deleteTeamDisclaimer'}
i18nKey={'teams.deleteTeamDisclaimer'}
values={{
teamName: name,
}}
@@ -219,20 +225,24 @@ function DeleteTeamConfirmationForm({
</div>
<div className={'text-sm'}>
<Trans i18nKey={'common:modalConfirmationQuestion'} />
<Trans i18nKey={'common.modalConfirmationQuestion'} />
</div>
</div>
<input type="hidden" value={id} name={'accountId'} />
<input type="hidden" value={otp} name={'otp'} />
</div>
<AlertDialogFooter>
<AlertDialogCancel>
<Trans i18nKey={'common:cancel'} />
<Trans i18nKey={'common.cancel'} />
</AlertDialogCancel>
<DeleteTeamSubmitButton />
<Button
type="submit"
data-test={'delete-team-form-confirm-button'}
disabled={isPending}
variant={'destructive'}
>
<Trans i18nKey={'teams.deleteTeam'} />
</Button>
</AlertDialogFooter>
</form>
</Form>
@@ -240,26 +250,14 @@ function DeleteTeamConfirmationForm({
);
}
function DeleteTeamSubmitButton() {
const { pending } = useFormStatus();
return (
<Button
data-test={'delete-team-form-confirm-button'}
disabled={pending}
variant={'destructive'}
>
<Trans i18nKey={'teams:deleteTeam'} />
</Button>
);
}
function LeaveTeamContainer(props: {
account: {
name: string;
id: string;
};
}) {
const { execute, isPending } = useAction(leaveTeamAccountAction);
const form = useForm({
resolver: zodResolver(
z.object({
@@ -278,7 +276,7 @@ function LeaveTeamContainer(props: {
<div className={'flex flex-col space-y-4'}>
<p className={'text-muted-foreground text-sm'}>
<Trans
i18nKey={'teams:leaveTeamDescription'}
i18nKey={'teams.leaveTeamDescription'}
values={{
teamName: props.account.name,
}}
@@ -286,26 +284,26 @@ function LeaveTeamContainer(props: {
</p>
<AlertDialog>
<AlertDialogTrigger asChild>
<div>
<AlertDialogTrigger
render={
<Button
data-test={'leave-team-button'}
type={'button'}
variant={'destructive'}
>
<Trans i18nKey={'teams:leaveTeam'} />
<Trans i18nKey={'teams.leaveTeam'} />
</Button>
</div>
</AlertDialogTrigger>
}
/>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
<Trans i18nKey={'teams:leavingTeamModalHeading'} />
<Trans i18nKey={'teams.leavingTeamModalHeading'} />
</AlertDialogTitle>
<AlertDialogDescription>
<Trans i18nKey={'teams:leavingTeamModalDescription'} />
<Trans i18nKey={'teams.leavingTeamModalDescription'} />
</AlertDialogDescription>
</AlertDialogHeader>
@@ -313,21 +311,20 @@ function LeaveTeamContainer(props: {
<Form {...form}>
<form
className={'flex flex-col space-y-4'}
action={leaveTeamAccountAction}
onSubmit={form.handleSubmit((data) => {
execute({
accountId: props.account.id,
confirmation: data.confirmation,
});
})}
>
<input
type={'hidden'}
value={props.account.id}
name={'accountId'}
/>
<FormField
name={'confirmation'}
render={({ field }) => {
return (
<FormItem>
<FormLabel>
<Trans i18nKey={'teams:leaveTeamInputLabel'} />
<Trans i18nKey={'teams.leaveTeamInputLabel'} />
</FormLabel>
<FormControl>
@@ -344,7 +341,7 @@ function LeaveTeamContainer(props: {
</FormControl>
<FormDescription>
<Trans i18nKey={'teams:leaveTeamInputDescription'} />
<Trans i18nKey={'teams.leaveTeamInputDescription'} />
</FormDescription>
<FormMessage />
@@ -355,10 +352,17 @@ function LeaveTeamContainer(props: {
<AlertDialogFooter>
<AlertDialogCancel>
<Trans i18nKey={'common:cancel'} />
<Trans i18nKey={'common.cancel'} />
</AlertDialogCancel>
<LeaveTeamSubmitButton />
<Button
type="submit"
data-test={'confirm-leave-organization-button'}
disabled={isPending}
variant={'destructive'}
>
<Trans i18nKey={'teams.leaveTeam'} />
</Button>
</AlertDialogFooter>
</form>
</Form>
@@ -369,36 +373,22 @@ function LeaveTeamContainer(props: {
);
}
function LeaveTeamSubmitButton() {
const { pending } = useFormStatus();
return (
<Button
data-test={'confirm-leave-organization-button'}
disabled={pending}
variant={'destructive'}
>
<Trans i18nKey={'teams:leaveTeam'} />
</Button>
);
}
function LeaveTeamErrorAlert() {
return (
<div className={'flex flex-col space-y-4'}>
<Alert variant={'destructive'}>
<AlertTitle>
<Trans i18nKey={'teams:leaveTeamErrorHeading'} />
<Trans i18nKey={'teams.leaveTeamErrorHeading'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'common:genericError'} />
<Trans i18nKey={'common.genericError'} />
</AlertDescription>
</Alert>
<AlertDialogFooter>
<AlertDialogCancel>
<Trans i18nKey={'common:cancel'} />
<Trans i18nKey={'common.cancel'} />
</AlertDialogCancel>
</AlertDialogFooter>
</div>
@@ -410,17 +400,17 @@ function DeleteTeamErrorAlert() {
<div className={'flex flex-col space-y-4'}>
<Alert variant={'destructive'}>
<AlertTitle>
<Trans i18nKey={'teams:deleteTeamErrorHeading'} />
<Trans i18nKey={'teams.deleteTeamErrorHeading'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'common:genericError'} />
<Trans i18nKey={'common.genericError'} />
</AlertDescription>
</Alert>
<AlertDialogFooter>
<AlertDialogCancel>
<Trans i18nKey={'common:cancel'} />
<Trans i18nKey={'common.cancel'} />
</AlertDialogCancel>
</AlertDialogFooter>
</div>
@@ -432,11 +422,11 @@ function DangerZoneCard({ children }: React.PropsWithChildren) {
<Card className={'border-destructive border'}>
<CardHeader>
<CardTitle>
<Trans i18nKey={'teams:settings.dangerZone'} />
<Trans i18nKey={'teams.settings.dangerZone'} />
</CardTitle>
<CardDescription>
<Trans i18nKey={'teams:settings.dangerZoneDescription'} />
<Trans i18nKey={'teams.settings.dangerZoneDescription'} />
</CardDescription>
</CardHeader>