build ok,
This commit is contained in:
10
002_source/cms/src/app/auth/auth0/callback/route.ts
Normal file
10
002_source/cms/src/app/auth/auth0/callback/route.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import type { AppRouteHandlerFnContext } from '@auth0/nextjs-auth0';
|
||||
|
||||
import { auth0 } from '@/lib/auth/auth0/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(req: NextRequest, ctx: AppRouteHandlerFnContext): Promise<Response> {
|
||||
return auth0.handleCallback(req, ctx);
|
||||
}
|
10
002_source/cms/src/app/auth/auth0/profile/route.ts
Normal file
10
002_source/cms/src/app/auth/auth0/profile/route.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import type { AppRouteHandlerFnContext } from '@auth0/nextjs-auth0';
|
||||
|
||||
import { auth0 } from '@/lib/auth/auth0/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(req: NextRequest, ctx: AppRouteHandlerFnContext): Promise<Response> {
|
||||
return auth0.handleProfile(req, ctx, {});
|
||||
}
|
15
002_source/cms/src/app/auth/auth0/sign-in/route.ts
Normal file
15
002_source/cms/src/app/auth/auth0/sign-in/route.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import type { AppRouteHandlerFnContext } from '@auth0/nextjs-auth0';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { auth0 } from '@/lib/auth/auth0/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(req: NextRequest, ctx: AppRouteHandlerFnContext): Promise<Response> {
|
||||
return auth0.handleLogin(req, ctx, {
|
||||
authorizationParams: { redirect_uri: new URL(paths.auth.auth0.callback, config.auth0.baseUrl).toString() },
|
||||
returnTo: new URL(paths.dashboard.overview, config.auth0.baseUrl).toString(),
|
||||
});
|
||||
}
|
10
002_source/cms/src/app/auth/auth0/sign-out/route.ts
Normal file
10
002_source/cms/src/app/auth/auth0/sign-out/route.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import type { AppRouteHandlerFnContext } from '@auth0/nextjs-auth0';
|
||||
|
||||
import { auth0 } from '@/lib/auth/auth0/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(req: NextRequest, ctx: AppRouteHandlerFnContext): Promise<Response> {
|
||||
return auth0.handleLogout(req, ctx);
|
||||
}
|
13
002_source/cms/src/app/auth/auth0/sign-up/route.ts
Normal file
13
002_source/cms/src/app/auth/auth0/sign-up/route.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import type { AppRouteHandlerFnContext } from '@auth0/nextjs-auth0';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { auth0 } from '@/lib/auth/auth0/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(req: NextRequest, ctx: AppRouteHandlerFnContext): Promise<Response> {
|
||||
return auth0.handleLogin(req, ctx, {
|
||||
authorizationParams: { redirect_uri: `${config.auth0.baseUrl}/auth/auth0/callback`, login_hint: 'signup' },
|
||||
});
|
||||
}
|
15
002_source/cms/src/app/auth/cognito/layout.tsx
Normal file
15
002_source/cms/src/app/auth/cognito/layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { AuthStrategy } from '@/lib/auth/strategy';
|
||||
import { StrategyGuard } from '@/components/auth/strategy-guard';
|
||||
|
||||
// We are not adding the auth check because there might be individual pages that require the user to be authenticated.
|
||||
// Another reason is that layouts get cached and loaded only once for all children.
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Layout({ children }: LayoutProps): React.JSX.Element {
|
||||
return <StrategyGuard expected={AuthStrategy.COGNITO}>{children}</StrategyGuard>;
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { NewPasswordRequiredForm } from '@/components/auth/cognito/new-password-required-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `New password required | Cognito | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<NewPasswordRequiredForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/cognito/reset-password/page.tsx
Normal file
19
002_source/cms/src/app/auth/cognito/reset-password/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { ResetPasswordForm } from '@/components/auth/cognito/reset-password-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Reset password | Cognito | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<ResetPasswordForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/cognito/sign-in/page.tsx
Normal file
19
002_source/cms/src/app/auth/cognito/sign-in/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { SignInForm } from '@/components/auth/cognito/sign-in-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign in | Cognito | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignInForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
35
002_source/cms/src/app/auth/cognito/sign-up-confirm/page.tsx
Normal file
35
002_source/cms/src/app/auth/cognito/sign-up-confirm/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { SignUpConfirmForm } from '@/components/auth/cognito/sign-up-confirm-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign up confirm | Cognito | Auth | ${config.site.name}` };
|
||||
|
||||
interface PageProps {
|
||||
searchParams: { email?: string };
|
||||
}
|
||||
|
||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const { email } = searchParams;
|
||||
|
||||
if (!email) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Alert color="error">Email is required</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignUpConfirmForm email={email} />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/cognito/sign-up/page.tsx
Normal file
19
002_source/cms/src/app/auth/cognito/sign-up/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { SignUpForm } from '@/components/auth/cognito/sign-up-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign up | Cognito | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignUpForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
35
002_source/cms/src/app/auth/cognito/update-password/page.tsx
Normal file
35
002_source/cms/src/app/auth/cognito/update-password/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { UpdatePasswordForm } from '@/components/auth/cognito/update-password-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Update password | Cognito | Auth | ${config.site.name}` };
|
||||
|
||||
interface PageProps {
|
||||
searchParams: { email?: string };
|
||||
}
|
||||
|
||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const { email } = searchParams;
|
||||
|
||||
if (!email) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Alert color="error">Email is required</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<UpdatePasswordForm email={email} />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
15
002_source/cms/src/app/auth/custom/layout.tsx
Normal file
15
002_source/cms/src/app/auth/custom/layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { AuthStrategy } from '@/lib/auth/strategy';
|
||||
import { StrategyGuard } from '@/components/auth/strategy-guard';
|
||||
|
||||
// We are not adding the auth check because there might be individual pages that require the user to be authenticated.
|
||||
// Another reason is that layouts get cached and loaded only once for all children.
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Layout({ children }: LayoutProps): React.JSX.Element {
|
||||
return <StrategyGuard expected={AuthStrategy.CUSTOM}>{children}</StrategyGuard>;
|
||||
}
|
19
002_source/cms/src/app/auth/custom/reset-password/page.tsx
Normal file
19
002_source/cms/src/app/auth/custom/reset-password/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { ResetPasswordForm } from '@/components/auth/custom/reset-password-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Reset password | Custom | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<ResetPasswordForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/custom/sign-in/page.tsx
Normal file
19
002_source/cms/src/app/auth/custom/sign-in/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { SignInForm } from '@/components/auth/custom/sign-in-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign in | Custom | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignInForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/custom/sign-up/page.tsx
Normal file
19
002_source/cms/src/app/auth/custom/sign-up/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { SignUpForm } from '@/components/auth/custom/sign-up-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign up | Custom | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignUpForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
15
002_source/cms/src/app/auth/firebase/layout.tsx
Normal file
15
002_source/cms/src/app/auth/firebase/layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { AuthStrategy } from '@/lib/auth/strategy';
|
||||
import { StrategyGuard } from '@/components/auth/strategy-guard';
|
||||
|
||||
// We are not adding the auth check because there might be individual pages that require the user to be authenticated.
|
||||
// Another reason is that layouts get cached and loaded only once for all children.
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Layout({ children }: LayoutProps): React.JSX.Element {
|
||||
return <StrategyGuard expected={AuthStrategy.FIREBASE}>{children}</StrategyGuard>;
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Link from '@mui/material/Link';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { ResetPasswordButton } from '@/components/auth/firebase/reset-password-button';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Recovery link sent | Firebase | Auth | ${config.site.name}` };
|
||||
|
||||
interface PageProps {
|
||||
searchParams: { email?: string };
|
||||
}
|
||||
|
||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const { email } = searchParams;
|
||||
|
||||
if (!email) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Alert color="error">Email is required</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Typography variant="h5">Recovery link sent</Typography>
|
||||
<Stack spacing={1}>
|
||||
<Typography>
|
||||
If an account exists with email{' '}
|
||||
<Typography component="span" variant="subtitle1">
|
||||
"{email}"
|
||||
</Typography>
|
||||
, you will receive a recovery email.
|
||||
</Typography>
|
||||
<div>
|
||||
<Link component={RouterLink} href={paths.auth.firebase.resetPassword} variant="subtitle2">
|
||||
Use another email
|
||||
</Link>
|
||||
</div>
|
||||
</Stack>
|
||||
<ResetPasswordButton email={email}>Resend</ResetPasswordButton>
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/firebase/reset-password/page.tsx
Normal file
19
002_source/cms/src/app/auth/firebase/reset-password/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { ResetPasswordForm } from '@/components/auth/firebase/reset-password-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Reset password | Firebase | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<ResetPasswordForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/firebase/sign-in/page.tsx
Normal file
19
002_source/cms/src/app/auth/firebase/sign-in/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { SignInForm } from '@/components/auth/firebase/sign-in-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign in | Firebase | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignInForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/firebase/sign-up/page.tsx
Normal file
19
002_source/cms/src/app/auth/firebase/sign-up/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { SignUpForm } from '@/components/auth/firebase/sign-up-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign up | Firebase | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignUpForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { UpdatePasswordForm } from '@/components/auth/firebase/update-password-form';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
|
||||
export const metadata: Metadata = { title: `Update password | Firebase | Auth | ${config.site.name}` };
|
||||
|
||||
interface PageProps {
|
||||
searchParams: { oobCode?: string };
|
||||
}
|
||||
|
||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const { oobCode } = searchParams;
|
||||
|
||||
if (!oobCode) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Alert color="error">Code is required</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<UpdatePasswordForm oobCode={oobCode} />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { CenteredLayout } from '@/components/auth/centered-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Reset password | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<CenteredLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader title="Reset password" />
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Email address</InputLabel>
|
||||
<OutlinedInput name="email" type="email" />
|
||||
</FormControl>
|
||||
<Button type="submit" variant="contained">
|
||||
Send recovery link
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
);
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Reset password | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Typography variant="h5">Reset Password</Typography>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Email address</InputLabel>
|
||||
<OutlinedInput name="email" type="email" />
|
||||
</FormControl>
|
||||
<Button type="submit" variant="contained">
|
||||
Send recovery link
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
);
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { CenteredLayout } from '@/components/auth/centered-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign in | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<CenteredLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader
|
||||
subheader={
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
Don't have an account? <Link variant="subtitle2">Sign up</Link>
|
||||
</Typography>
|
||||
}
|
||||
title="Sign in"
|
||||
/>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Email address</InputLabel>
|
||||
<OutlinedInput name="email" type="email" />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel>Password</InputLabel>
|
||||
<OutlinedInput name="password" type="password" />
|
||||
</FormControl>
|
||||
<Button type="submit" variant="contained">
|
||||
Sign in
|
||||
</Button>
|
||||
</Stack>
|
||||
<div>
|
||||
<Link variant="subtitle2">Forgot password?</Link>
|
||||
</div>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
);
|
||||
}
|
69
002_source/cms/src/app/auth/samples/sign-in/split/page.tsx
Normal file
69
002_source/cms/src/app/auth/samples/sign-in/split/page.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign in | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="h5">Sign in</Typography>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
Don't have an account? <Link variant="subtitle2">Sign up</Link>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack spacing={2}>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Email address</InputLabel>
|
||||
<OutlinedInput name="email" type="email" />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel>Password</InputLabel>
|
||||
<OutlinedInput name="password" type="password" />
|
||||
</FormControl>
|
||||
<Button type="submit" variant="contained">
|
||||
Sign in
|
||||
</Button>
|
||||
</Stack>
|
||||
<div>
|
||||
<Link variant="subtitle2">Forgot password?</Link>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
);
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { CenteredLayout } from '@/components/auth/centered-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign up | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<CenteredLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader
|
||||
subheader={
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
Already have an account? <Link variant="subtitle2">Sign in</Link>
|
||||
</Typography>
|
||||
}
|
||||
title="Sign up"
|
||||
/>
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Name</InputLabel>
|
||||
<OutlinedInput name="name" />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel>Email address</InputLabel>
|
||||
<OutlinedInput name="email" type="email" />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel>Password</InputLabel>
|
||||
<OutlinedInput name="password" type="password" />
|
||||
</FormControl>
|
||||
<div>
|
||||
<FormControlLabel
|
||||
control={<Checkbox name="terms" />}
|
||||
label={
|
||||
<React.Fragment>
|
||||
I have read the <Link>terms and conditions</Link>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" variant="contained">
|
||||
Create account
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
);
|
||||
}
|
80
002_source/cms/src/app/auth/samples/sign-up/split/page.tsx
Normal file
80
002_source/cms/src/app/auth/samples/sign-up/split/page.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign up | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="h5">Sign up</Typography>
|
||||
<Typography color="text.secondary" variant="body2">
|
||||
Already have an account? <Link variant="subtitle2">Sign in</Link>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Name</InputLabel>
|
||||
<OutlinedInput name="name" />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel>Email address</InputLabel>
|
||||
<OutlinedInput name="email" type="email" />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel>Password</InputLabel>
|
||||
<OutlinedInput name="password" type="password" />
|
||||
</FormControl>
|
||||
<div>
|
||||
<FormControlLabel
|
||||
control={<Checkbox />}
|
||||
label={
|
||||
<React.Fragment>
|
||||
I have read the <Link>terms and conditions</Link>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" variant="contained">
|
||||
Create account
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
);
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { CenteredLayout } from '@/components/auth/centered-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Update password | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<CenteredLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader title="Update password" />
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Password</InputLabel>
|
||||
<OutlinedInput name="password" type="password" />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel>Confirm password</InputLabel>
|
||||
<OutlinedInput name="confirmPassword" type="password" />
|
||||
</FormControl>
|
||||
<Button type="submit" variant="contained">
|
||||
Update
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
);
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Update password | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Typography variant="h5">Update Password</Typography>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Password</InputLabel>
|
||||
<OutlinedInput name="password" type="password" />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel>Confirm password</InputLabel>
|
||||
<OutlinedInput name="confirmPassword" type="password" />
|
||||
</FormControl>
|
||||
<Button type="submit" variant="contained">
|
||||
Update
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
);
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { CenteredLayout } from '@/components/auth/centered-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Verify code | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<CenteredLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader title="Verify code" />
|
||||
<CardContent>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Code</InputLabel>
|
||||
<OutlinedInput name="code" />
|
||||
</FormControl>
|
||||
<Button type="submit" variant="contained">
|
||||
Verify
|
||||
</Button>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
);
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Verify code | Samples | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Link
|
||||
color="text.primary"
|
||||
component={RouterLink}
|
||||
href={paths.dashboard.overview}
|
||||
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
|
||||
variant="subtitle2"
|
||||
>
|
||||
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
|
||||
Dashboard
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Typography variant="h5">Verify code</Typography>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<InputLabel>Code</InputLabel>
|
||||
<OutlinedInput name="code" />
|
||||
</FormControl>
|
||||
<Button type="submit" variant="contained">
|
||||
Verify
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
);
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Alert from '@mui/material/Alert';
|
||||
|
||||
import { paths } from '@/paths';
|
||||
import { logger } from '@/lib/default-logger';
|
||||
import { createClient as createSupabaseClient } from '@/lib/supabase/client';
|
||||
import { toast } from '@/components/core/toaster';
|
||||
|
||||
// NOTE: This is a `Page` and not a `GET` route because
|
||||
// Supabase has endpoints that still use
|
||||
// Implicit Flow instead of PKCE Flow, such as `resend sign-up email` process.
|
||||
|
||||
export default function Page(): React.JSX.Element | null {
|
||||
const [supabaseClient] = React.useState(createSupabaseClient());
|
||||
const router = useRouter();
|
||||
const executedRef = React.useRef<boolean>(false);
|
||||
const [displayError, setDisplayError] = React.useState<string | null>(null);
|
||||
|
||||
const handle = React.useCallback(async (): Promise<void> => {
|
||||
// Prevent rerun on DEV mode
|
||||
if (executedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
executedRef.current = true;
|
||||
|
||||
// Callback `error` is received as a URL hash `#error=value`
|
||||
// Callback `access_token` is received as a URL hash `#access_token=value`
|
||||
|
||||
const hash = window.location.hash || '#';
|
||||
const hashParams = new URLSearchParams(hash.split('#')[1]);
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (hashParams.get('error')) {
|
||||
logger.debug(hashParams.get('error_description'));
|
||||
setDisplayError('Something went wrong');
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = hashParams.get('access_token');
|
||||
const refreshToken = hashParams.get('refresh_token');
|
||||
|
||||
if (!accessToken || !refreshToken) {
|
||||
setDisplayError('Access token or refresh token is missing');
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await supabaseClient.auth.setSession({ access_token: accessToken, refresh_token: refreshToken });
|
||||
|
||||
if (error) {
|
||||
logger.debug(error.message);
|
||||
toast.error('Something went wrong');
|
||||
router.push(paths.auth.supabase.signIn);
|
||||
return;
|
||||
}
|
||||
|
||||
const next = searchParams.get('next') || paths.dashboard.overview;
|
||||
|
||||
router.push(next);
|
||||
}, [supabaseClient, router]);
|
||||
|
||||
React.useEffect((): void => {
|
||||
handle().catch(logger.error);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- Expected
|
||||
}, []);
|
||||
|
||||
if (displayError) {
|
||||
return <Alert color="error">{displayError}</Alert>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
53
002_source/cms/src/app/auth/supabase/callback/pkce/route.tsx
Normal file
53
002_source/cms/src/app/auth/supabase/callback/pkce/route.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { cookies } from 'next/headers';
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { AuthApiError } from '@supabase/supabase-js';
|
||||
|
||||
import { paths } from '@/paths';
|
||||
import { logger } from '@/lib/default-logger';
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
// NOTE: If you have a proxy in front of this app
|
||||
// the request origin might be a local address.
|
||||
// Consider using `config.site.url` from `@/config` instead.
|
||||
|
||||
// NOTE: This is not a `Page` because we only redirect and it will never render React content.
|
||||
|
||||
export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||
const { searchParams, origin } = req.nextUrl;
|
||||
|
||||
if (searchParams.get('error')) {
|
||||
return NextResponse.json({ error: searchParams.get('error_description') || 'Something went wrong' });
|
||||
}
|
||||
|
||||
const code = searchParams.get('code');
|
||||
|
||||
if (!code) {
|
||||
return NextResponse.json({ error: 'Code is missing' });
|
||||
}
|
||||
|
||||
const cookieStore = cookies();
|
||||
const supabaseClient = createClient(cookieStore);
|
||||
|
||||
try {
|
||||
const { error } = await supabaseClient.auth.exchangeCodeForSession(code);
|
||||
|
||||
if (error) {
|
||||
return NextResponse.json({ error: error.message });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof AuthApiError && err.message.includes('code and code verifier should be non-empty')) {
|
||||
return NextResponse.json({ error: 'Please open the link in the same browser' });
|
||||
}
|
||||
|
||||
logger.error('Callback error', err);
|
||||
|
||||
return NextResponse.json({ error: 'Something went wrong' });
|
||||
}
|
||||
|
||||
const next = searchParams.get('next') || paths.home;
|
||||
|
||||
return NextResponse.redirect(new URL(next, origin));
|
||||
}
|
15
002_source/cms/src/app/auth/supabase/layout.tsx
Normal file
15
002_source/cms/src/app/auth/supabase/layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { AuthStrategy } from '@/lib/auth/strategy';
|
||||
import { StrategyGuard } from '@/components/auth/strategy-guard';
|
||||
|
||||
// We are not adding the auth check because there might be individual pages that require the user to be authenticated.
|
||||
// Another reason is that layouts get cached and loaded only once for all children.
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Layout({ children }: LayoutProps): React.JSX.Element {
|
||||
return <StrategyGuard expected={AuthStrategy.SUPABASE}>{children}</StrategyGuard>;
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Link from '@mui/material/Link';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { ResetPasswordButton } from '@/components/auth/supabase/reset-password-button';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Recovery link sent | Supabase | Auth | ${config.site.name}` };
|
||||
|
||||
interface PageProps {
|
||||
searchParams: { email?: string };
|
||||
}
|
||||
|
||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const { email } = searchParams;
|
||||
|
||||
if (!email) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Alert color="error">Email is required</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Typography variant="h5">Recovery link sent</Typography>
|
||||
<Stack spacing={1}>
|
||||
<Typography>
|
||||
If an account exists with email{' '}
|
||||
<Typography component="span" variant="subtitle1">
|
||||
"{email}"
|
||||
</Typography>
|
||||
, you will receive a recovery email.
|
||||
</Typography>
|
||||
<div>
|
||||
<Link component={RouterLink} href={paths.auth.supabase.resetPassword} variant="subtitle2">
|
||||
Use another email
|
||||
</Link>
|
||||
</div>
|
||||
</Stack>
|
||||
<ResetPasswordButton email={email}>Resend</ResetPasswordButton>
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
33
002_source/cms/src/app/auth/supabase/reset-password/page.tsx
Normal file
33
002_source/cms/src/app/auth/supabase/reset-password/page.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Box from '@mui/material/Box';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { ResetPasswordForm } from '@/components/auth/supabase/reset-password-form';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Reset password | Supabase | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Typography variant="h5">Reset password</Typography>
|
||||
<ResetPasswordForm />
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/supabase/sign-in/page.tsx
Normal file
19
002_source/cms/src/app/auth/supabase/sign-in/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { SignInForm } from '@/components/auth/supabase/sign-in-form';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign in | Supabase | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignInForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import RouterLink from 'next/link';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { paths } from '@/paths';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { SignUpResendButton } from '@/components/auth/supabase/sign-up-resend-button';
|
||||
import { DynamicLogo } from '@/components/core/logo';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign up confirm | Supabase | Auth | ${config.site.name}` };
|
||||
|
||||
interface PageProps {
|
||||
searchParams: { email?: string };
|
||||
}
|
||||
|
||||
export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
||||
const { email } = searchParams;
|
||||
|
||||
if (!email) {
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Alert color="error">Email is required</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<Stack spacing={4}>
|
||||
<div>
|
||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
||||
</Box>
|
||||
</div>
|
||||
<Typography variant="h5">Confirm your email</Typography>
|
||||
<Typography>
|
||||
We've sent a verification email to{' '}
|
||||
<Typography component="span" variant="subtitle1">
|
||||
"{email}"
|
||||
</Typography>
|
||||
.
|
||||
</Typography>
|
||||
<SignUpResendButton email={email}>Resend</SignUpResendButton>
|
||||
</Stack>
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
19
002_source/cms/src/app/auth/supabase/sign-up/page.tsx
Normal file
19
002_source/cms/src/app/auth/supabase/sign-up/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { GuestGuard } from '@/components/auth/guest-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { SignUpForm } from '@/components/auth/supabase/sign-up-form';
|
||||
|
||||
export const metadata: Metadata = { title: `Sign up | Supabase | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<GuestGuard>
|
||||
<SplitLayout>
|
||||
<SignUpForm />
|
||||
</SplitLayout>
|
||||
</GuestGuard>
|
||||
);
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import { config } from '@/config';
|
||||
import { AuthGuard } from '@/components/auth/auth-guard';
|
||||
import { SplitLayout } from '@/components/auth/split-layout';
|
||||
import { UpdatePasswordForm } from '@/components/auth/supabase/update-password-form';
|
||||
|
||||
export const metadata: Metadata = { title: `Update password | Supabase | Auth | ${config.site.name}` };
|
||||
|
||||
export default function Page(): React.JSX.Element {
|
||||
return (
|
||||
<AuthGuard>
|
||||
<SplitLayout>
|
||||
<UpdatePasswordForm />
|
||||
</SplitLayout>
|
||||
</AuthGuard>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user