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">
|
<script lang="ts">
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
|
import { createQuery } from "@tanstack/svelte-query";
|
||||||
|
import { api } from "$lib/api/client";
|
||||||
import { cn } from "$lib/utils.js";
|
import { cn } from "$lib/utils.js";
|
||||||
import AppHeader from "./app-header.svelte";
|
import AppHeader from "./app-header.svelte";
|
||||||
import AppSidebar from "./app-sidebar.svelte";
|
import AppSidebar from "./app-sidebar.svelte";
|
||||||
|
import EmailVerificationBanner from "./email-verification-banner.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -11,6 +14,11 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let { title, children, class: className }: Props = $props();
|
let { title, children, class: className }: Props = $props();
|
||||||
|
|
||||||
|
const userQuery = createQuery(() => ({
|
||||||
|
queryKey: ["me"],
|
||||||
|
queryFn: () => api.me.get(),
|
||||||
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-screen overflow-hidden bg-background">
|
<div class="flex h-screen overflow-hidden bg-background">
|
||||||
@@ -20,6 +28,9 @@ let { title, children, class: className }: Props = $props();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-1 flex-col overflow-hidden">
|
<div class="flex flex-1 flex-col overflow-hidden">
|
||||||
|
{#if userQuery.data && !userQuery.data.emailVerified}
|
||||||
|
<EmailVerificationBanner email={userQuery.data.email} />
|
||||||
|
{/if}
|
||||||
<AppHeader {title} />
|
<AppHeader {title} />
|
||||||
|
|
||||||
<main class="flex-1 overflow-auto p-4 lg:p-6">
|
<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 AppHeader } from "./app-header.svelte";
|
||||||
export { default as AppSidebar } from "./app-sidebar.svelte";
|
export { default as AppSidebar } from "./app-sidebar.svelte";
|
||||||
export { default as DashboardLayout } from "./dashboard-layout.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";
|
export { default as MobileNav } from "./mobile-nav.svelte";
|
||||||
|
|||||||
Reference in New Issue
Block a user