Add email verification banner for unverified users
Shows a warning banner at the top of dashboard pages when the user's email is not verified, with a button to resend the verification email. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import { createQuery } from "@tanstack/svelte-query";
|
||||
import { api } from "$lib/api/client";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import AppHeader from "./app-header.svelte";
|
||||
import AppSidebar from "./app-sidebar.svelte";
|
||||
import EmailVerificationBanner from "./email-verification-banner.svelte";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@@ -11,6 +14,11 @@ interface Props {
|
||||
}
|
||||
|
||||
let { title, children, class: className }: Props = $props();
|
||||
|
||||
const userQuery = createQuery(() => ({
|
||||
queryKey: ["me"],
|
||||
queryFn: () => api.me.get(),
|
||||
}));
|
||||
</script>
|
||||
|
||||
<div class="flex h-screen overflow-hidden bg-background">
|
||||
@@ -20,6 +28,9 @@ let { title, children, class: className }: Props = $props();
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
{#if userQuery.data && !userQuery.data.emailVerified}
|
||||
<EmailVerificationBanner email={userQuery.data.email} />
|
||||
{/if}
|
||||
<AppHeader {title} />
|
||||
|
||||
<main class="flex-1 overflow-auto p-4 lg:p-6">
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import { Loader2, Mail, RefreshCw } from "@lucide/svelte";
|
||||
import { api } from "$lib/api/client";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
interface Props {
|
||||
email: string;
|
||||
}
|
||||
|
||||
let { email }: Props = $props();
|
||||
|
||||
let resendCooldown = $state(0);
|
||||
let isResending = $state(false);
|
||||
|
||||
// Handle cooldown timer
|
||||
$effect(() => {
|
||||
if (resendCooldown > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
resendCooldown -= 1;
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleResend() {
|
||||
isResending = true;
|
||||
try {
|
||||
await api.auth.resendVerificationEmail();
|
||||
resendCooldown = 60;
|
||||
toast.success("Verification email sent!");
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : "Failed to send email");
|
||||
} finally {
|
||||
isResending = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between gap-4 border-b border-amber-500/30 bg-amber-500/10 px-4 py-3"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<Mail class="h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
||||
<p class="text-sm text-amber-700 dark:text-amber-300">
|
||||
Please verify your email address at
|
||||
<span class="font-medium">{email}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={resendCooldown > 0 || isResending}
|
||||
onclick={handleResend}
|
||||
class="shrink-0 border-amber-500/50 text-amber-700 hover:bg-amber-500/20 dark:text-amber-300"
|
||||
>
|
||||
{#if isResending}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
Sending...
|
||||
{:else if resendCooldown > 0}
|
||||
<RefreshCw class="h-4 w-4" />
|
||||
Resend ({resendCooldown}s)
|
||||
{:else}
|
||||
<RefreshCw class="h-4 w-4" />
|
||||
Resend verification email
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as AppHeader } from "./app-header.svelte";
|
||||
export { default as AppSidebar } from "./app-sidebar.svelte";
|
||||
export { default as DashboardLayout } from "./dashboard-layout.svelte";
|
||||
export { default as EmailVerificationBanner } from "./email-verification-banner.svelte";
|
||||
export { default as MobileNav } from "./mobile-nav.svelte";
|
||||
|
||||
Reference in New Issue
Block a user