update auth guard and sign-in forms, add guidelines for custom auth to handle login and logout,

This commit is contained in:
louiscklaw
2025-05-11 07:54:23 +08:00
parent 25c1d3c917
commit 9a8fd1c073
4 changed files with 139 additions and 25 deletions

View File

@@ -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');

View 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

View File

@@ -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&apos;t have an account?{' '} Don&apos;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>
); );

View File

@@ -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&apos;t have an account?{' '} Don&apos;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>