diff --git a/src/lib/supabase/types.ts b/src/lib/supabase/types.ts index a9b011c..664726b 100644 --- a/src/lib/supabase/types.ts +++ b/src/lib/supabase/types.ts @@ -34,7 +34,8 @@ export interface Database { id: string; org_id: string; user_id: string; - role: 'owner' | 'admin' | 'editor' | 'viewer'; + role: string; + role_id: string | null; invited_at: string; joined_at: string | null; }; @@ -42,7 +43,8 @@ export interface Database { id?: string; org_id: string; user_id: string; - role: 'owner' | 'admin' | 'editor' | 'viewer'; + role?: string; + role_id?: string | null; invited_at?: string; joined_at?: string | null; }; @@ -50,7 +52,8 @@ export interface Database { id?: string; org_id?: string; user_id?: string; - role?: 'owner' | 'admin' | 'editor' | 'viewer'; + role?: string; + role_id?: string | null; invited_at?: string; joined_at?: string | null; }; @@ -240,6 +243,111 @@ export interface Database { status?: 'pending' | 'accepted' | 'declined'; }; }; + org_roles: { + Row: { + id: string; + org_id: string; + name: string; + color: string; + permissions: string[]; + is_default: boolean; + is_system: boolean; + position: number; + created_at: string; + updated_at: string; + }; + Insert: { + id?: string; + org_id: string; + name: string; + color?: string; + permissions?: string[]; + is_default?: boolean; + is_system?: boolean; + position?: number; + created_at?: string; + updated_at?: string; + }; + Update: { + id?: string; + org_id?: string; + name?: string; + color?: string; + permissions?: string[]; + is_default?: boolean; + is_system?: boolean; + position?: number; + created_at?: string; + updated_at?: string; + }; + }; + org_invites: { + Row: { + id: string; + org_id: string; + email: string; + role_id: string | null; + role: string; + invited_by: string; + token: string; + expires_at: string; + accepted_at: string | null; + created_at: string; + }; + Insert: { + id?: string; + org_id: string; + email: string; + role_id?: string | null; + role?: string; + invited_by: string; + token?: string; + expires_at?: string; + accepted_at?: string | null; + created_at?: string; + }; + Update: { + id?: string; + org_id?: string; + email?: string; + role_id?: string | null; + role?: string; + invited_by?: string; + token?: string; + expires_at?: string; + accepted_at?: string | null; + created_at?: string; + }; + }; + org_google_calendars: { + Row: { + id: string; + org_id: string; + calendar_id: string; + calendar_name: string | null; + connected_by: string; + created_at: string; + updated_at: string; + }; + Insert: { + id?: string; + org_id: string; + calendar_id: string; + calendar_name?: string | null; + connected_by: string; + created_at?: string; + updated_at?: string; + }; + Update: { + id?: string; + org_id?: string; + calendar_id?: string; + calendar_name?: string | null; + connected_by?: string; + created_at?: string; + updated_at?: string; + }; + }; profiles: { Row: { id: string; diff --git a/src/routes/auth/callback/+server.ts b/src/routes/auth/callback/+server.ts index bee598f..90084fa 100644 --- a/src/routes/auth/callback/+server.ts +++ b/src/routes/auth/callback/+server.ts @@ -3,7 +3,7 @@ import type { RequestHandler } from './$types'; export const GET: RequestHandler = async ({ url, locals }) => { const code = url.searchParams.get('code'); - const next = url.searchParams.get('next') ?? '/'; + const next = url.searchParams.get('next') ?? url.searchParams.get('redirect') ?? '/'; if (code) { const { error } = await locals.supabase.auth.exchangeCodeForSession(code); diff --git a/src/routes/invite/[token]/+page.server.ts b/src/routes/invite/[token]/+page.server.ts new file mode 100644 index 0000000..da5f259 --- /dev/null +++ b/src/routes/invite/[token]/+page.server.ts @@ -0,0 +1,47 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ params, locals }) => { + const { token } = params; + + // Get invite details + const { data: invite, error } = await locals.supabase + .from('org_invites') + .select(` + *, + organizations (id, name, slug) + `) + .eq('token', token) + .is('accepted_at', null) + .single(); + + if (error || !invite) { + return { + error: 'Invalid or expired invite link', + token + }; + } + + const inv = invite as any; + + // Check if invite is expired + if (new Date(inv.expires_at) < new Date()) { + return { + error: 'This invite has expired', + token + }; + } + + // Get current user + const { data: { user } } = await locals.supabase.auth.getUser(); + + return { + invite: { + id: inv.id, + email: inv.email, + role: inv.role, + org: inv.organizations + }, + user, + token + }; +}; diff --git a/src/routes/invite/[token]/+page.svelte b/src/routes/invite/[token]/+page.svelte new file mode 100644 index 0000000..ea4c1a8 --- /dev/null +++ b/src/routes/invite/[token]/+page.svelte @@ -0,0 +1,188 @@ + + +
{data.error}
+ + {:else if data.invite} + +You've been invited to join
++ {data.invite.org.name} +
+as {data.invite.role}
+ + {#if error} ++ Signed in as {data.user.email} +
++ Wrong account? Sign out +
+ {:else} + ++ Sign in or create an account to accept this invite. +
+