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

@@ -0,0 +1,35 @@
'use client';
import {
BorderedNavigationMenu,
BorderedNavigationMenuItem,
} from '@kit/ui/bordered-navigation-menu';
import pathsConfig from '~/config/paths.config';
export function SettingsSubNavigation(props: { account: string }) {
const settingsPath = pathsConfig.app.accountSettings.replace(
'[account]',
props.account,
);
const profilePath = pathsConfig.app.accountProfileSettings.replace(
'[account]',
props.account,
);
return (
<BorderedNavigationMenu>
<BorderedNavigationMenuItem
path={settingsPath}
label={'common.routes.settings'}
highlightMatch={`/home/${props.account}/settings$`}
/>
<BorderedNavigationMenuItem
path={profilePath}
label={'common.routes.profile'}
/>
</BorderedNavigationMenu>
);
}

View File

@@ -0,0 +1,39 @@
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
import featuresFlagConfig from '~/config/feature-flags.config';
import { TeamAccountLayoutPageHeader } from '../_components/team-account-layout-page-header';
import { SettingsSubNavigation } from './_components/settings-sub-navigation';
interface SettingsLayoutProps {
children: React.ReactNode;
params: Promise<{ account: string }>;
}
async function SettingsLayout({ children, params }: SettingsLayoutProps) {
const { account } = await params;
return (
<PageBody>
<div>
<TeamAccountLayoutPageHeader
account={account}
title={<Trans i18nKey={'teams.settings.pageTitle'} />}
description={<AppBreadcrumbs />}
/>
{featuresFlagConfig.enableTeamsOnly && (
<div className="mb-8">
<SettingsSubNavigation account={account} />
</div>
)}
</div>
{children}
</PageBody>
);
}
export default SettingsLayout;

View File

@@ -0,0 +1,55 @@
import { getTranslations } from 'next-intl/server';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { createTeamAccountsApi } from '@kit/team-accounts/api';
import { TeamAccountSettingsContainer } from '@kit/team-accounts/components';
import featuresFlagConfig from '~/config/feature-flags.config';
import pathsConfig from '~/config/paths.config';
export const generateMetadata = async () => {
const t = await getTranslations('teams');
const title = t('settings.pageTitle');
return {
title,
};
};
interface TeamAccountSettingsPageProps {
params: Promise<{ account: string }>;
}
const paths = {
teamAccountSettings: pathsConfig.app.accountSettings,
};
async function TeamAccountSettingsPage(props: TeamAccountSettingsPageProps) {
const api = createTeamAccountsApi(getSupabaseServerClient());
const slug = (await props.params).account;
const data = await api.getTeamAccount(slug);
const account = {
id: data.id,
name: data.name,
pictureUrl: data.picture_url,
slug: data.slug as string,
primaryOwnerUserId: data.primary_owner_user_id,
};
const features = {
enableTeamDeletion: featuresFlagConfig.enableTeamDeletion,
};
return (
<div className={'flex max-w-2xl flex-1 flex-col'}>
<TeamAccountSettingsContainer
account={account}
paths={paths}
features={features}
/>
</div>
);
}
export default TeamAccountSettingsPage;

View File

@@ -0,0 +1,66 @@
import { getTranslations } from 'next-intl/server';
import { PersonalAccountSettingsContainer } from '@kit/accounts/personal-account-settings';
import authConfig from '~/config/auth.config';
import featureFlagsConfig from '~/config/feature-flags.config';
import pathsConfig from '~/config/paths.config';
import { requireUserInServerComponent } from '~/lib/server/require-user-in-server-component';
const showEmailOption =
authConfig.providers.password ||
authConfig.providers.magicLink ||
authConfig.providers.otp;
const features = {
showLinkEmailOption: showEmailOption,
enablePasswordUpdate: authConfig.providers.password,
enableAccountDeletion: featureFlagsConfig.enableAccountDeletion,
enableAccountLinking: authConfig.enableIdentityLinking,
};
const providers = authConfig.providers.oAuth;
export const generateMetadata = async () => {
const t = await getTranslations('account');
const title = t('settingsTab');
return {
title,
};
};
interface TeamProfileSettingsPageProps {
params: Promise<{ account: string }>;
}
async function TeamProfileSettingsPage({
params,
}: TeamProfileSettingsPageProps) {
const [user, { account }] = await Promise.all([
requireUserInServerComponent(),
params,
]);
const profilePath = pathsConfig.app.accountProfileSettings.replace(
'[account]',
account,
);
const paths = {
callback: pathsConfig.auth.callback + `?next=${profilePath}`,
};
return (
<div className={'flex w-full flex-1 flex-col lg:max-w-2xl'}>
<PersonalAccountSettingsContainer
userId={user.id}
features={features}
paths={paths}
providers={providers}
/>
</div>
);
}
export default TeamProfileSettingsPage;