update auth guard and sign-in forms, add guidelines for custom auth to handle login and logout,
This commit is contained in:
@@ -29,6 +29,7 @@ export function AuthGuard({ children }: AuthGuardProps): React.JSX.Element | nul
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: here state that if user = null, eject user to login page
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.debug('[AuthGuard]: User is not logged in, redirecting to sign in');
|
logger.debug('[AuthGuard]: User is not logged in, redirecting to sign in');
|
||||||
|
|
||||||
|
10
002_source/cms/src/components/auth/custom/_GUIDELINES.md
Normal file
10
002_source/cms/src/components/auth/custom/_GUIDELINES.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# GUIDELINES
|
||||||
|
|
||||||
|
This folder contains login pages
|
||||||
|
|
||||||
|
the `@` sign refer to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src`
|
||||||
|
|
||||||
|
## Assumption and Requirements
|
||||||
|
|
||||||
|
- assume `pb` is located in `@/lib/pb`
|
||||||
|
- no need to handle error in this function, i'll handle it in the caller
|
@@ -1,4 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
// RULES:
|
||||||
|
// refer to ticket REQ0016 for login flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
@@ -25,6 +27,7 @@ import { authClient } from '@/lib/auth/custom/client';
|
|||||||
import { useUser } from '@/hooks/use-user';
|
import { useUser } from '@/hooks/use-user';
|
||||||
import { DynamicLogo } from '@/components/core/logo';
|
import { DynamicLogo } from '@/components/core/logo';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
interface OAuthProvider {
|
interface OAuthProvider {
|
||||||
id: 'google' | 'discord';
|
id: 'google' | 'discord';
|
||||||
@@ -44,7 +47,7 @@ const schema = zod.object({
|
|||||||
|
|
||||||
type Values = zod.infer<typeof schema>;
|
type Values = zod.infer<typeof schema>;
|
||||||
|
|
||||||
const defaultValues = { email: '', password: '' } satisfies Values;
|
const defaultValues = { email: 'admin@123.com', password: 'admin@123.com' } satisfies Values;
|
||||||
|
|
||||||
export function SignInForm(): React.JSX.Element {
|
export function SignInForm(): React.JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -103,15 +106,31 @@ export function SignInForm(): React.JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<div>
|
<div>
|
||||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
<Box
|
||||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
component={RouterLink}
|
||||||
|
href={paths.home}
|
||||||
|
sx={{ display: 'inline-block', fontSize: 0 }}
|
||||||
|
>
|
||||||
|
<DynamicLogo
|
||||||
|
colorDark="light"
|
||||||
|
colorLight="dark"
|
||||||
|
height={32}
|
||||||
|
width={122}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Typography variant="h5">Sign in</Typography>
|
<Typography variant="h5">Sign in</Typography>
|
||||||
<Typography color="text.secondary" variant="body2">
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
variant="body2"
|
||||||
|
>
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<Link component={RouterLink} href={paths.auth.custom.signUp} variant="subtitle2">
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.auth.custom.signUp}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -123,7 +142,15 @@ export function SignInForm(): React.JSX.Element {
|
|||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
endIcon={<Box alt="" component="img" height={24} src={provider.logo} width={24} />}
|
endIcon={
|
||||||
|
<Box
|
||||||
|
alt=""
|
||||||
|
component="img"
|
||||||
|
height={24}
|
||||||
|
src={provider.logo}
|
||||||
|
width={24}
|
||||||
|
/>
|
||||||
|
}
|
||||||
key={provider.id}
|
key={provider.id}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
onAuth(provider.id).catch(() => {
|
onAuth(provider.id).catch(() => {
|
||||||
@@ -147,7 +174,10 @@ export function SignInForm(): React.JSX.Element {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl error={Boolean(errors.email)}>
|
<FormControl error={Boolean(errors.email)}>
|
||||||
<InputLabel>Email address</InputLabel>
|
<InputLabel>Email address</InputLabel>
|
||||||
<OutlinedInput {...field} type="email" />
|
<OutlinedInput
|
||||||
|
{...field}
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
{errors.email ? <FormHelperText>{errors.email.message}</FormHelperText> : null}
|
{errors.email ? <FormHelperText>{errors.email.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -187,27 +217,65 @@ export function SignInForm(): React.JSX.Element {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.root ? <Alert color="error">{errors.root.message}</Alert> : null}
|
{errors.root ? <Alert color="error">{errors.root.message}</Alert> : null}
|
||||||
<Button disabled={isPending} type="submit" variant="contained">
|
<Button
|
||||||
|
disabled={isPending}
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
<div>
|
<div>
|
||||||
<Link component={RouterLink} href={paths.auth.custom.resetPassword} variant="subtitle2">
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.auth.custom.resetPassword}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Alert color="warning">
|
<Alert color="warning">
|
||||||
Use{' '}
|
<Stack>
|
||||||
<Typography component="span" sx={{ fontWeight: 700 }} variant="inherit">
|
<Box>
|
||||||
sofia@devias.io
|
user:{' '}
|
||||||
</Typography>{' '}
|
<Typography
|
||||||
with password{' '}
|
component="span"
|
||||||
<Typography component="span" sx={{ fontWeight: 700 }} variant="inherit">
|
sx={{ fontWeight: 700 }}
|
||||||
Secret1
|
variant="inherit"
|
||||||
</Typography>
|
>
|
||||||
|
admin@123.com
|
||||||
|
</Typography>{' '}
|
||||||
|
password{' '}
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
sx={{ fontWeight: 700 }}
|
||||||
|
variant="inherit"
|
||||||
|
>
|
||||||
|
admin@123.com
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
user{' '}
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
sx={{ fontWeight: 700 }}
|
||||||
|
variant="inherit"
|
||||||
|
>
|
||||||
|
sofia@devias.io
|
||||||
|
</Typography>{' '}
|
||||||
|
password{' '}
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
sx={{ fontWeight: 700 }}
|
||||||
|
variant="inherit"
|
||||||
|
>
|
||||||
|
Secret1
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
</Alert>
|
</Alert>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
@@ -115,15 +115,31 @@ export function SignInForm(): React.JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<div>
|
<div>
|
||||||
<Box component={RouterLink} href={paths.home} sx={{ display: 'inline-block', fontSize: 0 }}>
|
<Box
|
||||||
<DynamicLogo colorDark="light" colorLight="dark" height={32} width={122} />
|
component={RouterLink}
|
||||||
|
href={paths.home}
|
||||||
|
sx={{ display: 'inline-block', fontSize: 0 }}
|
||||||
|
>
|
||||||
|
<DynamicLogo
|
||||||
|
colorDark="light"
|
||||||
|
colorLight="dark"
|
||||||
|
height={32}
|
||||||
|
width={122}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Typography variant="h5">Sign in</Typography>
|
<Typography variant="h5">Sign in</Typography>
|
||||||
<Typography color="text.secondary" variant="body2">
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
variant="body2"
|
||||||
|
>
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<Link component={RouterLink} href={paths.auth.supabase.signUp} variant="subtitle2">
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.auth.supabase.signUp}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -135,7 +151,15 @@ export function SignInForm(): React.JSX.Element {
|
|||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
endIcon={<Box alt="" component="img" height={24} src={provider.logo} width={24} />}
|
endIcon={
|
||||||
|
<Box
|
||||||
|
alt=""
|
||||||
|
component="img"
|
||||||
|
height={24}
|
||||||
|
src={provider.logo}
|
||||||
|
width={24}
|
||||||
|
/>
|
||||||
|
}
|
||||||
key={provider.id}
|
key={provider.id}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
onAuth(provider.id).catch(() => {
|
onAuth(provider.id).catch(() => {
|
||||||
@@ -159,7 +183,10 @@ export function SignInForm(): React.JSX.Element {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl error={Boolean(errors.email)}>
|
<FormControl error={Boolean(errors.email)}>
|
||||||
<InputLabel>Email address</InputLabel>
|
<InputLabel>Email address</InputLabel>
|
||||||
<OutlinedInput {...field} type="email" />
|
<OutlinedInput
|
||||||
|
{...field}
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
{errors.email ? <FormHelperText>{errors.email.message}</FormHelperText> : null}
|
{errors.email ? <FormHelperText>{errors.email.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -199,13 +226,21 @@ export function SignInForm(): React.JSX.Element {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.root ? <Alert color="error">{errors.root.message}</Alert> : null}
|
{errors.root ? <Alert color="error">{errors.root.message}</Alert> : null}
|
||||||
<Button disabled={isPending} type="submit" variant="contained">
|
<Button
|
||||||
|
disabled={isPending}
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
<div>
|
<div>
|
||||||
<Link component={RouterLink} href={paths.auth.supabase.resetPassword} variant="subtitle2">
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
href={paths.auth.supabase.resetPassword}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user