Unify workspace dropdowns; Update layouts (#458)

Unified Account and Workspace drop-downs; Layout updates, now header lives within the PageBody component; Sidebars now use floating variant
This commit is contained in:
Giancarlo Buomprisco
2026-03-11 14:45:42 +08:00
committed by GitHub
parent ca585e09be
commit 4bc8448a1d
530 changed files with 14398 additions and 11198 deletions

View File

@@ -1,71 +0,0 @@
import Link from 'next/link';
import type { AuthError } from '@supabase/supabase-js';
import { ResendAuthLinkForm } from '@kit/auth/resend-email-link';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { Button } from '@kit/ui/button';
import { Trans } from '@kit/ui/trans';
import pathsConfig from '~/config/paths.config';
import { withI18n } from '~/lib/i18n/with-i18n';
interface AuthCallbackErrorPageProps {
searchParams: Promise<{
error: string;
callback?: string;
email?: string;
code?: AuthError['code'];
}>;
}
async function AuthCallbackErrorPage(props: AuthCallbackErrorPageProps) {
const { error, callback, code } = await props.searchParams;
const signInPath = pathsConfig.auth.signIn;
const redirectPath = callback ?? pathsConfig.auth.callback;
return (
<div className={'flex flex-col space-y-4 py-4'}>
<Alert variant={'warning'}>
<AlertTitle>
<Trans i18nKey={'auth:authenticationErrorAlertHeading'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={error ?? 'auth:authenticationErrorAlertBody'} />
</AlertDescription>
</Alert>
<AuthCallbackForm
code={code}
signInPath={signInPath}
redirectPath={redirectPath}
/>
</div>
);
}
function AuthCallbackForm(props: {
signInPath: string;
redirectPath?: string;
code?: AuthError['code'];
}) {
switch (props.code) {
case 'otp_expired':
return <ResendAuthLinkForm redirectPath={props.redirectPath} />;
default:
return <SignInButton signInPath={props.signInPath} />;
}
}
function SignInButton(props: { signInPath: string }) {
return (
<Button className={'w-full'} asChild>
<Link href={props.signInPath}>
<Trans i18nKey={'auth:signIn'} />
</Link>
</Button>
);
}
export default withI18n(AuthCallbackErrorPage);

View File

@@ -1,18 +0,0 @@
import { redirect } from 'next/navigation';
import type { NextRequest } from 'next/server';
import { createAuthCallbackService } from '@kit/supabase/auth';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import pathsConfig from '~/config/paths.config';
export async function GET(request: NextRequest) {
const service = createAuthCallbackService(getSupabaseServerClient());
const { nextPath } = await service.exchangeCodeForSession(request, {
joinTeamPath: pathsConfig.app.joinTeam,
redirectPath: pathsConfig.app.home,
});
return redirect(nextPath);
}

View File

@@ -1,17 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { createAuthCallbackService } from '@kit/supabase/auth';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import pathsConfig from '~/config/paths.config';
export async function GET(request: NextRequest) {
const service = createAuthCallbackService(getSupabaseServerClient());
const url = await service.verifyTokenHash(request, {
joinTeamPath: pathsConfig.app.joinTeam,
redirectPath: pathsConfig.app.home,
});
return NextResponse.redirect(url);
}

View File

@@ -1,9 +0,0 @@
import { AuthLayoutShell } from '@kit/auth/shared';
import { AppLogo } from '~/components/app-logo';
function AuthLayout({ children }: React.PropsWithChildren) {
return <AuthLayoutShell Logo={AppLogo}>{children}</AuthLayoutShell>;
}
export default AuthLayout;

View File

@@ -1,3 +0,0 @@
import { GlobalLoader } from '@kit/ui/global-loader';
export default GlobalLoader;

View File

@@ -1,51 +0,0 @@
import Link from 'next/link';
import { PasswordResetRequestContainer } from '@kit/auth/password-reset';
import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
export const generateMetadata = async () => {
const { t } = await createI18nServerInstance();
return {
title: t('auth:passwordResetLabel'),
};
};
const { callback, passwordUpdate, signIn } = pathsConfig.auth;
const redirectPath = `${callback}?next=${passwordUpdate}`;
function PasswordResetPage() {
return (
<>
<div className={'flex flex-col items-center gap-1'}>
<Heading level={4} className={'tracking-tight'}>
<Trans i18nKey={'auth:passwordResetLabel'} />
</Heading>
<p className={'text-muted-foreground text-sm'}>
<Trans i18nKey={'auth:passwordResetSubheading'} />
</p>
</div>
<div className={'flex flex-col space-y-4'}>
<PasswordResetRequestContainer redirectPath={redirectPath} />
<div className={'flex justify-center text-xs'}>
<Button asChild variant={'link'} size={'sm'}>
<Link href={signIn}>
<Trans i18nKey={'auth:passwordRecoveredQuestion'} />
</Link>
</Button>
</div>
</div>
</>
);
}
export default withI18n(PasswordResetPage);

View File

@@ -1,66 +0,0 @@
import Link from 'next/link';
import { SignInMethodsContainer } from '@kit/auth/sign-in';
import { getSafeRedirectPath } from '@kit/shared/utils';
import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
import authConfig from '~/config/auth.config';
import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
interface SignInPageProps {
searchParams: Promise<{
next?: string;
}>;
}
export const generateMetadata = async () => {
const i18n = await createI18nServerInstance();
return {
title: i18n.t('auth:signIn'),
};
};
async function SignInPage({ searchParams }: SignInPageProps) {
const { next } = await searchParams;
const paths = {
callback: pathsConfig.auth.callback,
returnPath: getSafeRedirectPath(next, pathsConfig.app.home),
joinTeam: pathsConfig.app.joinTeam,
};
return (
<>
<div className={'flex flex-col items-center gap-1'}>
<Heading level={4} className={'tracking-tight'}>
<Trans i18nKey={'auth:signInHeading'} />
</Heading>
<p className={'text-muted-foreground text-sm'}>
<Trans i18nKey={'auth:signInSubheading'} />
</p>
</div>
<SignInMethodsContainer
paths={paths}
providers={authConfig.providers}
captchaSiteKey={authConfig.captchaTokenSiteKey}
/>
<div className={'flex justify-center'}>
<Button asChild variant={'link'} size={'sm'}>
<Link href={pathsConfig.auth.signUp} prefetch={true}>
<Trans i18nKey={'auth:doNotHaveAccountYet'} />
</Link>
</Button>
</div>
</>
);
}
export default withI18n(SignInPage);

View File

@@ -1,57 +0,0 @@
import Link from 'next/link';
import { SignUpMethodsContainer } from '@kit/auth/sign-up';
import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
import authConfig from '~/config/auth.config';
import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
export const generateMetadata = async () => {
const i18n = await createI18nServerInstance();
return {
title: i18n.t('auth:signUp'),
};
};
const paths = {
callback: pathsConfig.auth.callback,
appHome: pathsConfig.app.home,
};
async function SignUpPage() {
return (
<>
<div className={'flex flex-col items-center gap-1'}>
<Heading level={4} className={'tracking-tight'}>
<Trans i18nKey={'auth:signUpHeading'} />
</Heading>
<p className={'text-muted-foreground text-sm'}>
<Trans i18nKey={'auth:signUpSubheading'} />
</p>
</div>
<SignUpMethodsContainer
providers={authConfig.providers}
displayTermsCheckbox={authConfig.displayTermsCheckbox}
paths={paths}
captchaSiteKey={authConfig.captchaTokenSiteKey}
/>
<div className={'flex justify-center'}>
<Button asChild variant={'link'} size={'sm'}>
<Link href={pathsConfig.auth.signIn} prefetch={true}>
<Trans i18nKey={'auth:alreadyHaveAnAccount'} />
</Link>
</Button>
</div>
</>
);
}
export default withI18n(SignUpPage);

View File

@@ -1,54 +0,0 @@
import { redirect } from 'next/navigation';
import { MultiFactorChallengeContainer } from '@kit/auth/mfa';
import { getSafeRedirectPath } from '@kit/shared/utils';
import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import pathsConfig from '~/config/paths.config';
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
interface Props {
searchParams: Promise<{
next?: string;
}>;
}
export const generateMetadata = async () => {
const i18n = await createI18nServerInstance();
return {
title: i18n.t('auth:signIn'),
};
};
async function VerifyPage(props: Props) {
const client = getSupabaseServerClient();
const { data } = await client.auth.getClaims();
if (!data?.claims) {
redirect(pathsConfig.auth.signIn);
}
const needsMfa = await checkRequiresMultiFactorAuthentication(client);
if (!needsMfa) {
redirect(pathsConfig.auth.signIn);
}
const nextPath = (await props.searchParams).next;
const redirectPath = getSafeRedirectPath(nextPath, pathsConfig.app.home);
return (
<MultiFactorChallengeContainer
userId={data.claims.sub}
paths={{
redirectPath,
}}
/>
);
}
export default withI18n(VerifyPage);