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:
committed by
GitHub
parent
ca585e09be
commit
4bc8448a1d
74
apps/web/app/[locale]/auth/callback/error/page.tsx
Normal file
74
apps/web/app/[locale]/auth/callback/error/page.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
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';
|
||||
|
||||
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'}
|
||||
render={
|
||||
<Link href={props.signInPath}>
|
||||
<Trans i18nKey={'auth.signIn'} />
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default AuthCallbackErrorPage;
|
||||
18
apps/web/app/[locale]/auth/callback/route.ts
Normal file
18
apps/web/app/[locale]/auth/callback/route.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
17
apps/web/app/[locale]/auth/confirm/route.ts
Normal file
17
apps/web/app/[locale]/auth/confirm/route.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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);
|
||||
}
|
||||
9
apps/web/app/[locale]/auth/layout.tsx
Normal file
9
apps/web/app/[locale]/auth/layout.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
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;
|
||||
3
apps/web/app/[locale]/auth/loading.tsx
Normal file
3
apps/web/app/[locale]/auth/loading.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { GlobalLoader } from '@kit/ui/global-loader';
|
||||
|
||||
export default GlobalLoader;
|
||||
56
apps/web/app/[locale]/auth/password-reset/page.tsx
Normal file
56
apps/web/app/[locale]/auth/password-reset/page.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
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';
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
const t = await getTranslations('auth');
|
||||
|
||||
return {
|
||||
title: t('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
|
||||
nativeButton={false}
|
||||
variant={'link'}
|
||||
size={'sm'}
|
||||
render={
|
||||
<Link href={signIn}>
|
||||
<Trans i18nKey={'auth.passwordRecoveredQuestion'} />
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PasswordResetPage;
|
||||
71
apps/web/app/[locale]/auth/sign-in/page.tsx
Normal file
71
apps/web/app/[locale]/auth/sign-in/page.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
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';
|
||||
|
||||
interface SignInPageProps {
|
||||
searchParams: Promise<{
|
||||
next?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
const t = await getTranslations('auth');
|
||||
|
||||
return {
|
||||
title: t('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
|
||||
nativeButton={false}
|
||||
variant={'link'}
|
||||
size={'sm'}
|
||||
render={
|
||||
<Link href={pathsConfig.auth.signUp} prefetch={true}>
|
||||
<Trans i18nKey={'auth.doNotHaveAccountYet'} />
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignInPage;
|
||||
62
apps/web/app/[locale]/auth/sign-up/page.tsx
Normal file
62
apps/web/app/[locale]/auth/sign-up/page.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
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';
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
const t = await getTranslations('auth');
|
||||
|
||||
return {
|
||||
title: t('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
|
||||
render={
|
||||
<Link href={pathsConfig.auth.signIn} prefetch={true}>
|
||||
<Trans i18nKey={'auth.alreadyHaveAnAccount'} />
|
||||
</Link>
|
||||
}
|
||||
variant={'link'}
|
||||
size={'sm'}
|
||||
nativeButton={false}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignUpPage;
|
||||
54
apps/web/app/[locale]/auth/verify/page.tsx
Normal file
54
apps/web/app/[locale]/auth/verify/page.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
searchParams: Promise<{
|
||||
next?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const generateMetadata = async () => {
|
||||
const t = await getTranslations('auth');
|
||||
|
||||
return {
|
||||
title: t('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 VerifyPage;
|
||||
Reference in New Issue
Block a user