Skip to content

Commit ce4a213

Browse files
committed
feat(auth): implement resend verification email functionality
- Add resendVerification to AuthContext using Supabase resend API - Add proper resend button with loading state in verification screen - Show success message when email is resent - Separate 'Resend email' and 'Use different email' actions - Clear form state when going back to signup form - Display errors in verification screen
1 parent afd8f0f commit ce4a213

2 files changed

Lines changed: 64 additions & 8 deletions

File tree

frontend/src/components/auth/SignupForm.tsx

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,33 @@ export function SignupForm() {
1717
const [loading, setLoading] = useState(false)
1818
const [oauthLoading, setOauthLoading] = useState<'github' | 'google' | null>(null)
1919
const [emailSent, setEmailSent] = useState(false)
20-
const { signUp, signInWithGitHub, signInWithGoogle } = useAuth()
20+
const [resendLoading, setResendLoading] = useState(false)
21+
const [resendSuccess, setResendSuccess] = useState(false)
22+
const { signUp, signInWithGitHub, signInWithGoogle, resendVerification } = useAuth()
2123
const navigate = useNavigate()
2224

25+
const handleResend = async () => {
26+
setResendLoading(true)
27+
setResendSuccess(false)
28+
try {
29+
await resendVerification(email)
30+
setResendSuccess(true)
31+
} catch (err: any) {
32+
setError(err.message || 'Failed to resend verification email')
33+
} finally {
34+
setResendLoading(false)
35+
}
36+
}
37+
38+
const handleGoBack = () => {
39+
setEmail('')
40+
setPassword('')
41+
setConfirmPassword('')
42+
setEmailSent(false)
43+
setResendSuccess(false)
44+
setError('')
45+
}
46+
2347
const handleSubmit = async (e: React.FormEvent) => {
2448
e.preventDefault()
2549
setError('')
@@ -130,6 +154,9 @@ export function SignupForm() {
130154
<Mail className="w-4 h-4 text-primary" />
131155
<span className="font-mono text-sm text-foreground">{email}</span>
132156
</div>
157+
{error && (
158+
<p className="text-sm text-destructive mt-3">{error}</p>
159+
)}
133160
</motion.div>
134161

135162
{/* Instructions card */}
@@ -143,19 +170,38 @@ export function SignupForm() {
143170
<div className="w-8 h-8 rounded-lg bg-green-500/10 border border-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
144171
<CheckCircle2 className="w-4 h-4 text-green-500" />
145172
</div>
146-
<div>
173+
<div className="flex-1">
147174
<p className="text-sm text-foreground font-medium mb-1">
148175
Click the link in the email to verify your account
149176
</p>
150-
<p className="text-sm text-muted-foreground">
151-
Didn't receive it? Check your spam folder or{' '}
177+
<p className="text-sm text-muted-foreground mb-3">
178+
Didn't receive it? Check your spam folder.
179+
</p>
180+
181+
{/* Resend success message */}
182+
{resendSuccess && (
183+
<p className="text-sm text-green-500 mb-3">
184+
✓ Verification email resent!
185+
</p>
186+
)}
187+
188+
{/* Action buttons */}
189+
<div className="flex items-center gap-3">
152190
<button
153-
onClick={() => setEmailSent(false)}
154-
className="text-primary hover:underline font-medium"
191+
onClick={handleResend}
192+
disabled={resendLoading}
193+
className="text-sm text-primary hover:underline font-medium disabled:opacity-50"
155194
>
156-
use a different email
195+
{resendLoading ? 'Sending...' : 'Resend email'}
157196
</button>
158-
</p>
197+
<span className="text-muted-foreground">·</span>
198+
<button
199+
onClick={handleGoBack}
200+
className="text-sm text-muted-foreground hover:text-foreground"
201+
>
202+
Use different email
203+
</button>
204+
</div>
159205
</div>
160206
</div>
161207
</motion.div>

frontend/src/contexts/AuthContext.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface AuthContextType {
1111
signInWithGitHub: () => Promise<void>;
1212
signInWithGoogle: () => Promise<void>;
1313
signOut: () => Promise<void>;
14+
resendVerification: (email: string) => Promise<void>;
1415
}
1516

1617
const AuthContext = createContext<AuthContextType | undefined>(undefined);
@@ -86,6 +87,14 @@ export function AuthProvider({ children }: { children: ReactNode }) {
8687
if (error) throw error;
8788
};
8889

90+
const resendVerification = async (email: string) => {
91+
const { error } = await supabase.auth.resend({
92+
type: 'signup',
93+
email,
94+
});
95+
if (error) throw error;
96+
};
97+
8998
const value = {
9099
user,
91100
session,
@@ -95,6 +104,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
95104
signInWithGitHub,
96105
signInWithGoogle,
97106
signOut,
107+
resendVerification,
98108
};
99109

100110
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;

0 commit comments

Comments
 (0)