-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #23 from Design-System-Project/feature/tfr2-120-ot…
…p-page replace login with otp auth page
- Loading branch information
Showing
29 changed files
with
600 additions
and
202 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
'use server'; | ||
|
||
import { z } from 'zod'; | ||
import { config } from '@/config'; | ||
import { unprotectedAction } from '@/lib/safe-action'; | ||
|
||
export const authAction = unprotectedAction | ||
.metadata({ actionName: 'authAction' }) | ||
.schema( | ||
z.object({ | ||
email: z.string().email(), | ||
}) | ||
) | ||
.action(async ({ ctx, parsedInput: { email } }) => { | ||
const { error } = await ctx.authClient.auth.signInWithOtp({ | ||
email: email, | ||
options: { | ||
shouldCreateUser: true, | ||
emailRedirectTo: `${config.pageUrl}/auth/callback`, | ||
}, | ||
}); | ||
|
||
if (!error) { | ||
return { | ||
ok: true, | ||
}; | ||
} | ||
|
||
return { | ||
error: error.message, | ||
ok: false, | ||
}; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
'use server'; | ||
|
||
import { z } from 'zod'; | ||
import { unprotectedAction } from '@/lib/safe-action'; | ||
import { zfd } from 'zod-form-data'; | ||
|
||
export const verifyOtpAction = unprotectedAction | ||
.metadata({ actionName: 'verifyOtpAction' }) | ||
.schema( | ||
z.object({ | ||
email: zfd.text(z.string().email()), | ||
token: zfd.text( | ||
z.string().min(6, { | ||
message: 'Your one-time password must be 6 characters.', | ||
}) | ||
), | ||
}) | ||
) | ||
.action(async ({ ctx, parsedInput: { email, token } }) => { | ||
const { error } = await ctx.authClient.auth.verifyOtp({ | ||
email, | ||
token, | ||
type: 'email', | ||
}); | ||
|
||
if (!error) { | ||
return { | ||
ok: true, | ||
}; | ||
} | ||
|
||
return { | ||
error: error.message, | ||
ok: false, | ||
}; | ||
}); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardHeader, | ||
CardTitle, | ||
DSLogo, | ||
} from '@ds-project/components'; | ||
import Image from 'next/image'; | ||
import authBackground from '../_assets/auth-bg.svg'; | ||
import { AuthForm } from './auth-form'; | ||
|
||
export function AuthCard() { | ||
return ( | ||
<Card className="w-full max-w-sm"> | ||
<CardHeader className="flex flex-col items-center relative"> | ||
<Image | ||
src={authBackground} | ||
alt="auth background" | ||
className="absolute top-0" | ||
/> | ||
<DSLogo width={64} height={64} className="z-10" /> | ||
<CardTitle className="text-2xl" weight="normal"> | ||
<h1>Sign in to DS</h1> | ||
</CardTitle> | ||
<CardDescription weight="light"> | ||
<p>We'll email you a code for a password-free sign in.</p> | ||
</CardDescription> | ||
</CardHeader> | ||
|
||
<CardContent className="grid gap-4"> | ||
<AuthForm /> | ||
</CardContent> | ||
</Card> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
'use client'; | ||
|
||
import { | ||
Button, | ||
cn, | ||
Form, | ||
FormControl, | ||
FormDescription, | ||
FormField, | ||
FormItem, | ||
FormLabel, | ||
FormMessage, | ||
Icons, | ||
Input, | ||
InputOTP, | ||
InputOTPGroup, | ||
InputOTPSlot, | ||
} from '@ds-project/components'; | ||
import { authAction } from '../_actions/auth.action'; | ||
import { ErrorMessage } from './error-message'; | ||
import { verifyOtpAction } from '../_actions/verify-otp.action'; | ||
import { z } from 'zod'; | ||
import { zodResolver } from '@hookform/resolvers/zod'; | ||
import { useForm } from 'react-hook-form'; | ||
import { useState } from 'react'; | ||
import { useRouter } from 'next/navigation'; | ||
|
||
const FormSchema = z.object({ | ||
email: z.string().email({ | ||
message: 'Please enter a valid email address.', | ||
}), | ||
otpToken: z | ||
.string() | ||
.min(6, { | ||
message: 'Your one-time password must be 6 characters.', | ||
}) | ||
.optional(), | ||
}); | ||
|
||
export const AuthForm = () => { | ||
const router = useRouter(); | ||
const [loading, setLoading] = useState(false); | ||
const [stage, setStage] = useState<'signin' | 'verify'>('signin'); | ||
const [error, setError] = useState<string>(); | ||
const form = useForm<z.infer<typeof FormSchema>>({ | ||
resolver: zodResolver(FormSchema), | ||
defaultValues: { | ||
email: '', | ||
otpToken: undefined, | ||
}, | ||
}); | ||
|
||
async function onSubmit(data: z.infer<typeof FormSchema>) { | ||
setLoading(true); | ||
if (stage === 'verify' && data.otpToken) { | ||
const result = await verifyOtpAction({ | ||
email: data.email, | ||
token: data.otpToken, | ||
}); | ||
|
||
if (result?.data?.ok) { | ||
router.replace('/app'); | ||
} | ||
|
||
if (result?.data?.error) { | ||
setError(result.data.error); | ||
} | ||
} else { | ||
const result = await authAction({ | ||
email: data.email, | ||
}); | ||
|
||
if (result?.data?.ok) { | ||
setStage('verify'); | ||
} | ||
|
||
if (result?.data?.error) { | ||
setError(result.data.error); | ||
} | ||
} | ||
setLoading(false); | ||
} | ||
|
||
return ( | ||
<Form {...form}> | ||
<form | ||
onSubmit={form.handleSubmit(onSubmit)} | ||
className="flex flex-col gap-4" | ||
> | ||
<FormField | ||
control={form.control} | ||
name="email" | ||
render={({ field }) => ( | ||
<FormItem hidden={stage === 'verify'}> | ||
<FormLabel>Email</FormLabel> | ||
<FormControl> | ||
<Input | ||
autoComplete="email" | ||
placeholder="[email protected]" | ||
{...field} | ||
/> | ||
</FormControl> | ||
</FormItem> | ||
)} | ||
/> | ||
|
||
<FormField | ||
control={form.control} | ||
name="otpToken" | ||
render={({ field }) => ( | ||
<FormItem | ||
className={cn('flex flex-col items-center', { | ||
hidden: stage === 'signin', | ||
})} | ||
> | ||
<FormLabel>Code</FormLabel> | ||
<FormControl> | ||
<InputOTP maxLength={6} {...field}> | ||
<InputOTPGroup> | ||
<InputOTPSlot index={0} /> | ||
<InputOTPSlot index={1} /> | ||
<InputOTPSlot index={2} /> | ||
<InputOTPSlot index={3} /> | ||
<InputOTPSlot index={4} /> | ||
<InputOTPSlot index={5} /> | ||
</InputOTPGroup> | ||
</InputOTP> | ||
</FormControl> | ||
<FormDescription> | ||
Please enter the code sent to your email. | ||
</FormDescription> | ||
<FormMessage /> | ||
</FormItem> | ||
)} | ||
/> | ||
|
||
{/* <Message visible={signInFormState.data?.ok} /> */} | ||
<ErrorMessage | ||
error={ | ||
form.formState.errors.email?.message ?? | ||
form.formState.errors.otpToken?.message ?? | ||
error | ||
} | ||
/> | ||
|
||
<Button className="w-full" aria-disabled={loading} type="submit"> | ||
<> | ||
{loading ? ( | ||
<Icons.SymbolIcon className="mr-2 size-4 animate-spin" /> | ||
) : null} | ||
{loading | ||
? 'Sending...' | ||
: stage === 'signin' | ||
? 'Sign in with email' | ||
: 'Continue'} | ||
</> | ||
</Button> | ||
</form> | ||
</Form> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
41 changes: 0 additions & 41 deletions
41
apps/dashboard/src/app/auth/login/_actions/login-user.action.ts
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.