Skip to content

Commit 671c378

Browse files
authored
Merge pull request #83 from techulus/main
Email OTP
2 parents 61dc76c + d779401 commit 671c378

File tree

11 files changed

+250
-10882
lines changed

11 files changed

+250
-10882
lines changed

app/(auth)/sign-in/page.tsx

Lines changed: 114 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
import { Button } from "@/components/ui/button";
44
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
55
import { Input } from "@/components/ui/input";
6+
import {
7+
InputOTP,
8+
InputOTPGroup,
9+
InputOTPSlot,
10+
} from "@/components/ui/input-otp";
611
import { Label } from "@/components/ui/label";
7-
import { signIn } from "@/lib/betterauth/auth-client";
12+
import { authClient, signIn } from "@/lib/betterauth/auth-client";
813
import { FingerprintIcon } from "lucide-react";
914
import Image from "next/image";
1015
import Link from "next/link";
@@ -15,8 +20,9 @@ import logo from "../../../public/images/logo.png";
1520

1621
export default function SignInForm() {
1722
const [email, setEmail] = useState("");
23+
const [otp, setOtp] = useState("");
24+
const [hasSentEmail, setHasSentEmail] = useState(false);
1825
const [processing, setProcessing] = useState(false);
19-
const [hasSendEmail, setHasSendEmail] = useState(false);
2026

2127
const router = useRouter();
2228

@@ -46,51 +52,50 @@ export default function SignInForm() {
4652
<CardTitle className="text-hero text-2xl">Get Started</CardTitle>
4753
</CardHeader>
4854

49-
<CardContent className="grid gap-4">
50-
<Label htmlFor="email">Email</Label>
55+
{hasSentEmail ? (
56+
<CardContent className="grid gap-4">
57+
<Label>Login Code</Label>
5158

52-
<Input
53-
id="email"
54-
type="email"
55-
placeholder="m@example.com"
56-
required
57-
onChange={(e) => {
58-
setEmail(e.target.value);
59-
}}
60-
value={email}
61-
/>
59+
<InputOTP
60+
maxLength={6}
61+
onChange={(value) => {
62+
setOtp(value);
63+
}}
64+
>
65+
<InputOTPGroup>
66+
<InputOTPSlot index={0} />
67+
<InputOTPSlot index={1} />
68+
<InputOTPSlot index={2} />
69+
<InputOTPSlot index={3} />
70+
<InputOTPSlot index={4} />
71+
<InputOTPSlot index={5} />
72+
</InputOTPGroup>
73+
</InputOTP>
6274

63-
{hasSendEmail ? (
64-
<p className="text-sm text-gray-500 dark:text-gray-400">
65-
An email has been sent to{" "}
66-
<span className="font-semibold">{email}</span> with a magic link
67-
to sign in.
68-
</p>
69-
) : (
7075
<Button
7176
className="gap-2"
7277
disabled={processing}
7378
onClick={async () => {
7479
try {
75-
if (!email) return;
80+
if (!otp || !email) return;
7681
setProcessing(true);
7782
toast.promise(
7883
signIn
79-
.magicLink({ email, callbackURL: "/start" })
84+
.emailOtp({ email, otp })
8085
.then((result) => {
8186
if (result?.error) {
8287
throw new Error(result.error?.message);
8388
}
8489

85-
setHasSendEmail(true);
90+
router.push("/start");
8691
})
8792
.finally(() => {
8893
setProcessing(false);
8994
}),
9095
{
91-
loading: "Sending magic link...",
92-
success: "Magic link sent!",
93-
error: "Failed to send magic link.",
96+
loading: "Verifying your login code...",
97+
success: "Login code verified!",
98+
error: "Failed to verify login code.",
9499
},
95100
);
96101
} catch (error) {
@@ -100,41 +105,91 @@ export default function SignInForm() {
100105
}
101106
}}
102107
>
103-
Sign-in with Magic Link
108+
Verify Login Code
104109
</Button>
105-
)}
110+
</CardContent>
111+
) : (
112+
<CardContent className="grid gap-4">
113+
<Label htmlFor="email">Email</Label>
106114

107-
<Button
108-
variant="secondary"
109-
className="gap-2"
110-
disabled={processing}
111-
onClick={async () => {
112-
setProcessing(true);
113-
toast.promise(
114-
signIn
115-
.passkey()
116-
.then((result) => {
117-
if (result?.error) {
118-
throw new Error(result.error?.message);
119-
}
115+
<Input
116+
id="email"
117+
type="email"
118+
placeholder="m@example.com"
119+
required
120+
onChange={(e) => {
121+
setEmail(e.target.value);
122+
}}
123+
value={email}
124+
/>
120125

121-
router.push("/start");
122-
})
123-
.finally(() => {
124-
setProcessing(false);
125-
}),
126-
{
127-
loading: "Waiting for passkey...",
128-
success: "Signed in with passkey!",
129-
error: "Failed to receive passkey.",
130-
},
131-
);
132-
}}
133-
>
134-
<FingerprintIcon size={16} />
135-
Sign-in with Passkey
136-
</Button>
137-
</CardContent>
126+
<Button
127+
className="gap-2"
128+
disabled={processing}
129+
onClick={async () => {
130+
try {
131+
if (!email) return;
132+
setProcessing(true);
133+
toast.promise(
134+
authClient.emailOtp
135+
.sendVerificationOtp({ email, type: "sign-in" })
136+
.then((result) => {
137+
if (result?.error) {
138+
throw new Error(result.error?.message);
139+
}
140+
setHasSentEmail(true);
141+
})
142+
.finally(() => {
143+
setProcessing(false);
144+
}),
145+
{
146+
loading: "Sending your login code...",
147+
success: "Login code sent!",
148+
error: "Failed to send login code.",
149+
},
150+
);
151+
} catch (error) {
152+
console.error(error);
153+
} finally {
154+
setProcessing(false);
155+
}
156+
}}
157+
>
158+
Sign-in with Email
159+
</Button>
160+
161+
<Button
162+
variant="secondary"
163+
className="gap-2"
164+
disabled={processing}
165+
onClick={async () => {
166+
setProcessing(true);
167+
toast.promise(
168+
signIn
169+
.passkey()
170+
.then((result) => {
171+
if (result?.error) {
172+
throw new Error(result.error?.message);
173+
}
174+
175+
router.push("/start");
176+
})
177+
.finally(() => {
178+
setProcessing(false);
179+
}),
180+
{
181+
loading: "Waiting for passkey...",
182+
success: "Signed in with passkey!",
183+
error: "Failed to receive passkey.",
184+
},
185+
);
186+
}}
187+
>
188+
<FingerprintIcon size={16} />
189+
Sign-in with Passkey
190+
</Button>
191+
</CardContent>
192+
)}
138193
</Card>
139194
</div>
140195
);

bun.lock

Lines changed: 19 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type * as React from "react";
21
import {
32
Body,
43
Container,
@@ -10,29 +9,30 @@ import {
109
Preview,
1110
Text,
1211
} from "@react-email/components";
12+
import type * as React from "react";
1313

14-
interface Props {
15-
url: string;
16-
}
17-
18-
export const MagicLinkEmail = ({ url }: Props) => (
14+
export const OtpEmail = ({ otp }: { otp: string }) => (
1915
<Html>
2016
<Head />
21-
<Preview>Log in with this magic link</Preview>
17+
<Preview>Your login code for Manage</Preview>
2218
<Body style={main}>
2319
<Container style={container}>
24-
<Heading style={h1}>Login to Manage</Heading>
25-
<Link
26-
href={url}
27-
target="_blank"
20+
<Heading style={h1}>Your Login Code</Heading>
21+
<Text
2822
style={{
29-
...link,
30-
display: "block",
31-
marginBottom: "16px",
23+
...text,
24+
fontSize: "32px",
25+
fontWeight: "bold",
26+
letterSpacing: "8px",
27+
margin: "32px 0",
3228
}}
3329
>
34-
Click here to log in with this magic link
35-
</Link>
30+
{otp}
31+
</Text>
32+
<Text style={text}>
33+
Enter this code to log in to your Manage account. This code will
34+
expire in 5 minutes.
35+
</Text>
3636
<Text
3737
style={{
3838
...text,
@@ -41,7 +41,7 @@ export const MagicLinkEmail = ({ url }: Props) => (
4141
marginBottom: "16px",
4242
}}
4343
>
44-
If you didn&apos;t try to login, you can safely ignore this email.
44+
If you didn't try to login, you can safely ignore this email.
4545
</Text>
4646
<Img
4747
src="https://managee.xyz/images/logo.png"

components/layout/page-title.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export default function PageTitle({
7070
</div>
7171
</div>
7272

73-
<div className="flex min-h-[240px] items-center justify-center border-b bg-gray-50 pb-4 pl-4 pr-6 pt-4 dark:bg-card dark:bg-gray-900 dark:text-white sm:pl-6 lg:pl-8 xl:border-t-0">
73+
<div className="flex min-h-[200px] sm:min-h-[240px] items-center justify-center border-b bg-gray-50 pb-4 pl-4 pr-6 pt-4 dark:bg-card dark:bg-gray-900 dark:text-white sm:pl-6 lg:pl-8 xl:border-t-0">
7474
<div className="flex w-full max-w-5xl items-center justify-between">
7575
<div className="relative flex w-full flex-col">
7676
<h1 className="text-hero flex-1 text-3xl tracking-tighter lg:text-4xl">

0 commit comments

Comments
 (0)