MystBin
/56596703647e0db856 Created 2 days ago...
Raw
signin-form.tsx Hide Copy Raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
"use client"; import { getInputProps, useForm, type FieldMetadata } from "@conform-to/react"; import { getZodConstraint, parseWithZod } from "@conform-to/zod"; import { Label } from "@radix-ui/react-label"; import { signIn } from "~/actions/auth"; import { signInSchema as schema } from "~/schema/auth"; import Form from "next/form"; import { useActionState } from "react"; import { type z } from "zod"; import { CheckboxConform } from "./conform-inputs/checkbox"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; export function SignInForm() { const [lastResult, action, isSubmitting] = useActionState(signIn, undefined); const [form, fields] = useForm({ id: "signin", lastResult, constraint: getZodConstraint(schema), shouldValidate: "onBlur", onValidate({ formData }) { return parseWithZod(formData, { schema }); }, }); return ( <Form className="flex flex-col gap-2" id={form.id} action={action} onSubmit={form.onSubmit} noValidate > <Field label="Email" placeholder="[email protected]" type="email" meta={fields.email} /> <Field label="Password" type="password" meta={fields.password} /> <Label> <CheckboxConform className="mr-2 align-middle" meta={fields.rememberMe} /> <span className="align-middle">Remember me</span> </Label> <p className="text-sm text-red-500">{form.errors?.join(", ")}</p> <Button className="mt-2" type="submit" disabled={isSubmitting}> Login </Button> </Form> ); } function Field({ meta, label, placeholder, type, }: { meta: FieldMetadata< string, Omit<z.infer<typeof schema>, "rememberMe">, string[] >; label: string; placeholder?: string; type: "email" | "password"; }) { const { key: _key, ...inputProps } = getInputProps(meta, { type }); return ( <div> <Label htmlFor={meta.id}>{label}:</Label> <Input {...inputProps} placeholder={placeholder} /> <p className="text-sm text-red-500">{meta.errors?.join(", ")}</p> </div> ); }
actions/auth.ts Hide Copy Raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
"use server"; import { parseWithZod } from "@conform-to/zod"; import { signInSchema } from "~/schema/auth"; import { signInEmail } from "~/server/auth"; import { APIError } from "better-auth/api"; export async function signIn(prevState: unknown, formData: FormData) { const submission = parseWithZod(formData, { schema: signInSchema }); if (submission.status !== "success") { return submission.reply(); } try { await signInEmail({ body: { ...submission.value, callbackURL: "/", }, }); } catch (error) { if (error instanceof APIError) { if (error.status === "TOO_MANY_REQUESTS") { return submission.reply({ formErrors: [error.message], }); } if (error.status === "FORBIDDEN") { return submission.reply({ formErrors: ["Verify your email before siging in"], }); } if (error.status === "UNAUTHORIZED") { return submission.reply({ fieldErrors: { email: ["Invalid Credentials"], password: ["Invalid Credentials"], }, }); } throw error; } throw error; } }
auth-config.ts Hide Copy Raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import { env } from "~/env"; import { db } from "~/server/db"; import { account, rateLimit, session, user, verification, } from "~/server/db/schema"; import { api } from "~/trpc/server"; import { type BetterAuthOptions } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { nextCookies } from "better-auth/next-js"; export const authConfig = { appName: "kapil-jewels", baseURL: env.URL, advanced: { generateId: false, }, database: drizzleAdapter(db, { provider: "pg", schema: { user, account, session, verification, rateLimit, }, }), emailVerification: { expiresIn: 60 * 10, sendOnSignUp: true, async sendVerificationEmail({ url, user: { email } }) { await api.resend.verify({ email, url }); }, }, emailAndPassword: { enabled: true, autoSignIn: false, requireEmailVerification: true, resetPasswordTokenExpiresIn: 60 * 10, async sendResetPassword(data) { await api.resend.resetPassword({ email: data.user.email, url: data.url }); }, }, rateLimit: { enabled: true, storage: "database", window: 60, max: 2, }, session: { expiresIn: 30 * 24 * 60 * 60, updateAge: 15 * 24 * 60 * 60, cookieCache: { enabled: true, maxAge: 60 * 10, }, }, plugins: [nextCookies()], } satisfies BetterAuthOptions;