Skip to content

Commit 69c9646

Browse files
nits
1 parent 2277641 commit 69c9646

2 files changed

Lines changed: 160 additions & 130 deletions

File tree

Lines changed: 9 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,12 @@
1-
"use client"
2-
3-
import { InputOTPSeparator } from "@/components/ui/input-otp"
4-
import { InputOTPGroup } from "@/components/ui/input-otp"
5-
import { InputOTPSlot } from "@/components/ui/input-otp"
6-
import { InputOTP } from "@/components/ui/input-otp"
7-
import { Card, CardHeader, CardDescription, CardTitle, CardContent, CardFooter } from "@/components/ui/card"
8-
import { Button } from "@/components/ui/button"
9-
import { ArrowLeft } from "lucide-react"
10-
import { useSearchParams } from "next/navigation"
11-
import { useCallback, useState, Suspense } from "react"
12-
import { SourcebotLogo } from "@/app/components/sourcebotLogo"
13-
import useCaptureEvent from "@/hooks/useCaptureEvent"
14-
import { Footer } from "@/app/components/footer"
15-
import { SOURCEBOT_SUPPORT_EMAIL } from "@/lib/constants"
16-
import { Redirect } from "@/app/components/redirect"
17-
18-
function VerifyPageContent() {
19-
const [value, setValue] = useState("")
20-
const searchParams = useSearchParams()
21-
const email = searchParams.get("email")
22-
const captureEvent = useCaptureEvent();
23-
24-
const handleSubmit = useCallback(() => {
25-
if (email && value.length === 6) {
26-
const url = new URL("/api/auth/callback/nodemailer", window.location.origin)
27-
url.searchParams.set("token", value)
28-
url.searchParams.set("email", email)
29-
// Use a full-page navigation (not router.push) so the auth callback's
30-
// session cookie + 302 redirect are applied by the browser, and the
31-
// one-time token isn't consumed twice by a client-side RSC navigation.
32-
window.location.href = url.toString()
33-
}
34-
}, [value, email])
35-
36-
if (!email) {
37-
captureEvent("wa_login_verify_page_no_email", {})
38-
return <Redirect
39-
to="/login"
40-
/>
1+
import { auth } from "@/auth";
2+
import { redirect } from "next/navigation";
3+
import { VerifyForm } from "./verifyForm";
4+
5+
export default async function VerifyPage() {
6+
const session = await auth();
7+
if (session) {
8+
return redirect("/");
419
}
4210

43-
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
44-
if (e.key === 'Enter' && value.length === 6) {
45-
handleSubmit()
46-
}
47-
}
48-
49-
return (
50-
<div className="flex flex-col min-h-screen">
51-
<div className="flex-1 flex flex-col items-center p-4 sm:p-12 w-full bg-backgroundSecondary">
52-
<div className="w-full max-w-md">
53-
<div className="flex justify-center mb-6">
54-
<SourcebotLogo className="h-16" size="large" />
55-
</div>
56-
<Card className="w-full">
57-
<CardHeader className="space-y-1">
58-
<CardTitle className="text-2xl font-semibold text-center">Verify your email</CardTitle>
59-
<CardDescription className="text-center">
60-
Enter the 6-digit code we sent to <span className="font-semibold text-primary">{email}</span>
61-
</CardDescription>
62-
</CardHeader>
63-
64-
<CardContent>
65-
<form onSubmit={(e) => {
66-
e.preventDefault()
67-
handleSubmit()
68-
}} className="space-y-6">
69-
<div className="flex justify-center py-4">
70-
<InputOTP maxLength={6} value={value} onChange={setValue} onKeyDown={handleKeyDown} className="gap-2">
71-
<InputOTPGroup>
72-
<InputOTPSlot index={0} />
73-
<InputOTPSlot index={1} />
74-
<InputOTPSlot index={2} />
75-
</InputOTPGroup>
76-
<InputOTPSeparator />
77-
<InputOTPGroup>
78-
<InputOTPSlot index={3} />
79-
<InputOTPSlot index={4} />
80-
<InputOTPSlot index={5} />
81-
</InputOTPGroup>
82-
</InputOTP>
83-
</div>
84-
</form>
85-
</CardContent>
86-
87-
<CardFooter className="flex flex-col space-y-4 pt-0">
88-
<Button variant="ghost" className="w-full text-sm" size="sm" onClick={() => window.history.back()}>
89-
<ArrowLeft className="mr-2 h-4 w-4" />
90-
Back to login
91-
</Button>
92-
</CardFooter>
93-
</Card>
94-
<div className="mt-8 text-center text-sm text-muted-foreground">
95-
<p>
96-
Having trouble?{" "}
97-
<a href={`mailto:${SOURCEBOT_SUPPORT_EMAIL}`} className="text-primary hover:underline">
98-
Contact support
99-
</a>
100-
</p>
101-
</div>
102-
</div>
103-
</div>
104-
<Footer />
105-
</div>
106-
)
107-
}
108-
109-
function LoadingVerifyPage() {
110-
return (
111-
<div className="min-h-screen flex flex-col items-center justify-center p-4 bg-gradient-to-b from-background to-muted/30">
112-
<div className="w-full max-w-md">
113-
<div className="flex justify-center mb-6">
114-
<SourcebotLogo className="h-16" size="large" />
115-
</div>
116-
<Card className="w-full shadow-lg border-muted/40">
117-
<CardHeader className="space-y-1">
118-
<CardTitle className="text-2xl font-bold text-center">Loading...</CardTitle>
119-
</CardHeader>
120-
</Card>
121-
</div>
122-
</div>
123-
)
11+
return <VerifyForm />;
12412
}
125-
126-
export default function VerifyPage() {
127-
return (
128-
<Suspense fallback={<LoadingVerifyPage />}>
129-
<VerifyPageContent />
130-
</Suspense>
131-
)
132-
}
133-
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"use client"
2+
3+
import { InputOTPSeparator } from "@/components/ui/input-otp"
4+
import { InputOTPGroup } from "@/components/ui/input-otp"
5+
import { InputOTPSlot } from "@/components/ui/input-otp"
6+
import { InputOTP } from "@/components/ui/input-otp"
7+
import { Card, CardHeader, CardDescription, CardTitle, CardContent, CardFooter } from "@/components/ui/card"
8+
import { Button } from "@/components/ui/button"
9+
import { ArrowLeft, Loader2 } from "lucide-react"
10+
import { useSearchParams } from "next/navigation"
11+
import { useCallback, useState, Suspense } from "react"
12+
import { SourcebotLogo } from "@/app/components/sourcebotLogo"
13+
import useCaptureEvent from "@/hooks/useCaptureEvent"
14+
import { Footer } from "@/app/components/footer"
15+
import { SOURCEBOT_SUPPORT_EMAIL } from "@/lib/constants"
16+
import { Redirect } from "@/app/components/redirect"
17+
18+
function VerifyPageContent() {
19+
const [value, setValue] = useState("")
20+
const [isVerifying, setIsVerifying] = useState(false)
21+
const searchParams = useSearchParams()
22+
const email = searchParams.get("email")
23+
const captureEvent = useCaptureEvent();
24+
25+
const handleSubmit = useCallback((code: string) => {
26+
if (isVerifying || !email || code.length !== 6) {
27+
return
28+
}
29+
30+
setIsVerifying(true)
31+
const url = new URL("/api/auth/callback/nodemailer", window.location.origin)
32+
url.searchParams.set("token", code)
33+
url.searchParams.set("email", email)
34+
// Use a full-page navigation (not router.push) so the auth callback's
35+
// session cookie + 302 redirect are applied by the browser, and the
36+
// one-time token isn't consumed twice by a client-side RSC navigation.
37+
window.location.href = url.toString()
38+
}, [email, isVerifying])
39+
40+
// Auto-submit once the full 6-digit code is entered. Pass the new value
41+
// directly rather than reading `value`, which hasn't been committed yet.
42+
const handleValueChange = (newValue: string) => {
43+
setValue(newValue)
44+
if (newValue.length === 6) {
45+
handleSubmit(newValue)
46+
}
47+
}
48+
49+
if (!email) {
50+
captureEvent("wa_login_verify_page_no_email", {})
51+
return <Redirect
52+
to="/login"
53+
/>
54+
}
55+
56+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
57+
if (e.key === 'Enter') {
58+
handleSubmit(value)
59+
}
60+
}
61+
62+
return (
63+
<div className="flex flex-col min-h-screen">
64+
<div className="flex-1 flex flex-col items-center p-4 sm:p-12 w-full bg-backgroundSecondary">
65+
<div className="w-full max-w-md">
66+
<div className="flex justify-center mb-6">
67+
<SourcebotLogo className="h-16" size="large" />
68+
</div>
69+
<Card className="w-full">
70+
<CardHeader className="space-y-1">
71+
<CardTitle className="text-2xl font-semibold text-center">Verify your email</CardTitle>
72+
<CardDescription className="text-center">
73+
Enter the 6-digit code we sent to <span className="font-semibold text-primary">{email}</span>
74+
</CardDescription>
75+
</CardHeader>
76+
77+
<CardContent>
78+
<form onSubmit={(e) => {
79+
e.preventDefault()
80+
handleSubmit(value)
81+
}} className="space-y-6">
82+
<div className="flex justify-center py-4">
83+
<InputOTP maxLength={6} value={value} onChange={handleValueChange} onKeyDown={handleKeyDown} disabled={isVerifying} className="gap-2">
84+
<InputOTPGroup>
85+
<InputOTPSlot index={0} />
86+
<InputOTPSlot index={1} />
87+
<InputOTPSlot index={2} />
88+
</InputOTPGroup>
89+
<InputOTPSeparator />
90+
<InputOTPGroup>
91+
<InputOTPSlot index={3} />
92+
<InputOTPSlot index={4} />
93+
<InputOTPSlot index={5} />
94+
</InputOTPGroup>
95+
</InputOTP>
96+
</div>
97+
{isVerifying && (
98+
<div className="flex items-center justify-center gap-2 text-sm text-muted-foreground">
99+
<Loader2 className="h-4 w-4 animate-spin" />
100+
Verifying...
101+
</div>
102+
)}
103+
</form>
104+
</CardContent>
105+
106+
<CardFooter className="flex flex-col space-y-4 pt-0">
107+
<Button variant="ghost" className="w-full text-sm" size="sm" onClick={() => window.history.back()}>
108+
<ArrowLeft className="mr-2 h-4 w-4" />
109+
Back to login
110+
</Button>
111+
</CardFooter>
112+
</Card>
113+
<div className="mt-8 text-center text-sm text-muted-foreground">
114+
<p>
115+
Having trouble?{" "}
116+
<a href={`mailto:${SOURCEBOT_SUPPORT_EMAIL}`} className="text-primary hover:underline">
117+
Contact support
118+
</a>
119+
</p>
120+
</div>
121+
</div>
122+
</div>
123+
<Footer />
124+
</div>
125+
)
126+
}
127+
128+
function LoadingVerifyPage() {
129+
return (
130+
<div className="min-h-screen flex flex-col items-center justify-center p-4 bg-gradient-to-b from-background to-muted/30">
131+
<div className="w-full max-w-md">
132+
<div className="flex justify-center mb-6">
133+
<SourcebotLogo className="h-16" size="large" />
134+
</div>
135+
<Card className="w-full shadow-lg border-muted/40">
136+
<CardHeader className="space-y-1">
137+
<CardTitle className="text-2xl font-bold text-center">Loading...</CardTitle>
138+
</CardHeader>
139+
</Card>
140+
</div>
141+
</div>
142+
)
143+
}
144+
145+
export function VerifyForm() {
146+
return (
147+
<Suspense fallback={<LoadingVerifyPage />}>
148+
<VerifyPageContent />
149+
</Suspense>
150+
)
151+
}

0 commit comments

Comments
 (0)