Files
resolutionflow/frontend/src/components/common/EmailVerificationWall.tsx
Michael Chihlas f1be3abcc5
Some checks failed
CI / e2e (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / backend (push) Has been cancelled
Mirror to GitHub / mirror (push) Has been cancelled
feat: self-serve signup Phase 2 (frontend cutover) (#162)
Co-authored-by: Michael Chihlas <michael@resolutionflow.com>
Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
2026-05-07 18:42:20 +00:00

91 lines
3.0 KiB
TypeScript

import { useState } from 'react'
import { Loader2, MailCheck } from 'lucide-react'
import { authApi } from '@/api/auth'
import { useAuthStore } from '@/store/authStore'
import { toast } from '@/lib/toast'
import { cn } from '@/lib/utils'
interface EmailVerificationWallProps {
className?: string
}
/**
* Hard wall shown after the email-verification grace period expires.
*
* Minimal v1 — Task 37 will refine copy, layout, and add the
* `/verify-email?token=...` route handling. Until then this gives
* Day 7+ unverified users a way to re-send the verification email
* or sign out.
*/
export function EmailVerificationWall({ className }: EmailVerificationWallProps) {
const user = useAuthStore((s) => s.user)
const logout = useAuthStore((s) => s.logout)
const [isSending, setIsSending] = useState(false)
const handleResend = async () => {
setIsSending(true)
try {
await authApi.sendVerificationEmail()
toast.success('Verification email sent')
} catch {
toast.error('Failed to send verification email')
} finally {
setIsSending(false)
}
}
const handleLogout = async () => {
try {
await logout()
} catch {
// logout swallows API errors internally
}
}
return (
<div
className={cn(
'flex min-h-[60vh] items-center justify-center px-4 py-12',
className,
)}
data-testid="email-verification-wall"
>
<div className="w-full max-w-md rounded-lg border border-default bg-card p-6 text-center">
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full border border-default bg-elevated text-muted-foreground">
<MailCheck className="h-5 w-5" aria-hidden="true" />
</div>
<h2 className="text-lg font-semibold text-heading">
Verify your email to continue
</h2>
<p className="mt-2 text-sm text-muted-foreground">
{user?.email
? `We sent a verification link to ${user.email}. Click it to unlock your account.`
: 'Check your inbox for the verification link we sent when you signed up.'}
</p>
<div className="mt-6 flex flex-col gap-2">
<button
type="button"
onClick={handleResend}
disabled={isSending}
data-testid="resend-button"
className="inline-flex items-center justify-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50"
>
{isSending && <Loader2 className="h-4 w-4 animate-spin" />}
Resend verification email
</button>
<button
type="button"
onClick={handleLogout}
data-testid="sign-out-button"
className="rounded-md border border-default bg-elevated px-4 py-2 text-sm font-medium text-primary transition-colors hover:bg-white/[0.06]"
>
Sign out
</button>
</div>
</div>
</div>
)
}
export default EmailVerificationWall