Files
publisher-dashboard/apps/publisher-dashboard/src/routes/invite/accept/+page.svelte
2026-01-09 18:18:31 +08:00

187 lines
5.1 KiB
Svelte

<script lang="ts">
import { CheckCircle2, Loader2, UserPlus, XCircle } from "@lucide/svelte";
import { toast } from "svelte-sonner";
import { goto } from "$app/navigation";
import { page } from "$app/state";
import { api } from "$lib/api/client";
import { Button } from "$lib/components/ui/button";
/**
* Org invite acceptance page
* Automatically accepts the invite token from URL on mount
* Redirects to dashboard on success
*/
let isAccepting = $state(true);
let error = $state("");
let success = $state(false);
const token = $derived(page.url.searchParams.get("token"));
/**
* Check if user is authenticated
*/
async function checkAuth(): Promise<boolean> {
try {
await api.me.get();
return true;
} catch {
return false;
}
}
/**
* Accept the invitation with the token from URL
*/
async function acceptInvite(): Promise<void> {
if (!token) {
error = "No invitation token provided";
isAccepting = false;
return;
}
// Check if user is logged in
const isAuthenticated = await checkAuth();
if (!isAuthenticated) {
// Redirect to login with return URL
const returnUrl = `/invite/accept?token=${encodeURIComponent(token)}`;
goto(`/auth/login?redirect=${encodeURIComponent(returnUrl)}`);
return;
}
try {
await api.orgs.invites.accept({ token });
success = true;
toast.success("You've joined the organization!");
// Redirect to dashboard after a short delay
setTimeout(() => {
goto("/dashboard");
}, 1500);
} catch (e) {
if (e instanceof Error) {
// Handle specific error cases
if (e.message.includes("expired") || e.message.includes("invalid")) {
error =
"This invitation has expired or is invalid. Please ask for a new invitation.";
} else if (
e.message.includes("already") ||
e.message.includes("member")
) {
error = "You're already a member of this organization.";
} else if (
e.message.includes("email") ||
e.message.includes("mismatch")
) {
error =
"This invitation was sent to a different email address. Please log in with the correct account.";
} else {
error = e.message;
}
} else {
error = "Failed to accept invitation";
}
} finally {
isAccepting = false;
}
}
// Auto-accept on mount
$effect(() => {
if (token) {
void acceptInvite();
} else {
error = "No invitation token provided";
isAccepting = false;
}
});
</script>
<svelte:head>
<title>Accept Invitation | Publisher Dashboard</title>
<meta name="description" content="Accept your organization invitation" />
</svelte:head>
<div class="flex min-h-screen items-center justify-center bg-background p-4">
<div class="w-full max-w-md space-y-6">
<!-- Header -->
<div class="space-y-4 text-center">
<div class="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-primary/10">
{#if isAccepting}
<UserPlus class="h-8 w-8 animate-pulse text-primary" />
{:else if error}
<XCircle class="h-8 w-8 text-destructive" />
{:else if success}
<CheckCircle2 class="h-8 w-8 text-green-600" />
{/if}
</div>
<div class="space-y-2">
<h1 class="text-2xl font-semibold tracking-tight">
{#if isAccepting}
Accepting invitation...
{:else if error}
Unable to join
{:else if success}
Welcome aboard!
{/if}
</h1>
<p class="text-sm text-muted-foreground">
{#if isAccepting}
Please wait while we process your invitation
{:else if error}
We couldn't process your invitation
{:else if success}
Redirecting to your dashboard...
{/if}
</p>
</div>
</div>
<!-- Loading indicator -->
{#if isAccepting}
<div class="flex items-center justify-center gap-2 text-sm text-muted-foreground">
<Loader2 class="h-4 w-4 animate-spin" />
<span>Processing...</span>
</div>
{/if}
<!-- Success message -->
{#if success}
<div class="flex items-center justify-center gap-2 text-sm text-green-600">
<CheckCircle2 class="h-4 w-4" />
<span>You've successfully joined the organization!</span>
</div>
{/if}
<!-- Error message -->
{#if error}
<div class="rounded-lg border border-destructive/50 bg-destructive/10 p-4">
<p class="text-sm text-destructive">{error}</p>
</div>
<!-- Actions -->
<div class="space-y-3">
{#if token}
<Button class="h-10 w-full" onclick={acceptInvite}>
Try again
</Button>
{/if}
<Button variant="outline" class="h-10 w-full" href="/dashboard">
Go to Dashboard
</Button>
<div class="text-center">
<a
href="/auth/login"
class="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
>
Sign in with a different account
</a>
</div>
</div>
{/if}
</div>
</div>