187 lines
5.1 KiB
Svelte
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>
|