echos-ocr/src/components/layout/email-verification-banner.tsx
Randall Stillwell a975043670 Add onboarding wizard, email verification, integration architecture, and settings restructure
- Auto-sign-in after registration instead of redirect to login
- Email verification system with token generation, send/confirm API routes, and persistent banner
- 7-step onboarding wizard (org, location, services, upload source, AI, integrations, complete)
- Middleware redirects owners with incomplete onboarding to /onboarding
- Integration provider plugin architecture with registry and 6 providers (Planning Center, Monday.com, Airtable, Google Sheets, Webhook, CSV Export)
- Full integration CRUD API with test, sync, fields, and OAuth authorize/callback routes
- Refactored fireIntegrationEvent to use Integration model with legacy AppSettings fallback
- Migration script for existing Monday.com/webhook config to Integration rows
- Settings page restructured from monolithic 1290-line file into focused sub-routes with section navigation
- Integration hub UI with provider tiles, connect flow, and individual config pages
- Post-onboarding contextual guidance cards on dashboard with dismissible hints
- Schema: Integration model, onboardingComplete/onboardingStep on Organization, dismissedHints on OrgMember

Made-with: Cursor
2026-04-15 01:29:13 -05:00

60 lines
1.9 KiB
TypeScript

"use client";
import { useState } from "react";
import { useSession } from "next-auth/react";
import { Mail, X, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
export function EmailVerificationBanner() {
const { data: session } = useSession();
const [dismissed, setDismissed] = useState(false);
const [sending, setSending] = useState(false);
const [sent, setSent] = useState(false);
if (!session?.user || session.user.isEmailVerified || dismissed) {
return null;
}
async function handleResend() {
setSending(true);
try {
const res = await fetch("/api/auth/verify-email/send", { method: "POST" });
if (res.ok) setSent(true);
} catch {
// silent fail — user can try again
} finally {
setSending(false);
}
}
return (
<div className="relative flex items-center gap-3 rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-200">
<Mail className="size-4 shrink-0 text-amber-400" />
<p className="flex-1">
{sent ? (
"Verification email sent — check your inbox."
) : (
<>
Please verify your email address.{" "}
<button
onClick={handleResend}
disabled={sending}
className="inline-flex items-center gap-1 font-medium text-amber-400 underline underline-offset-2 hover:text-amber-300 disabled:opacity-60"
>
{sending && <Loader2 className="size-3 animate-spin" />}
Resend verification email
</button>
</>
)}
</p>
<Button
variant="ghost"
size="icon"
className="size-6 shrink-0 text-amber-400 hover:bg-amber-500/20 hover:text-amber-300"
onClick={() => setDismissed(true)}
>
<X className="size-3.5" />
</Button>
</div>
);
}