diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 8a9ce27..f261645 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,5 +1,4 @@ -import { dev } from '$app/env'; -import { version } from '$app/environment'; +import { dev, version } from '$app/environment'; import { env } from '$env/dynamic/public'; import type { HandleClientError } from '@sveltejs/kit'; import posthog from 'posthog-js'; diff --git a/src/lib/features/auth/state/user-state.svelte.ts b/src/lib/features/auth/state/user-state.svelte.ts index 908e737..da9fae9 100644 --- a/src/lib/features/auth/state/user-state.svelte.ts +++ b/src/lib/features/auth/state/user-state.svelte.ts @@ -13,10 +13,20 @@ export class UserState { #userCreditLogs = $state(undefined); #userCreditBalance = $state(undefined); + #client: SupabaseClient | undefined; #unsub: any; constructor(supabaseClient: SupabaseClient | undefined) { - if (!supabaseClient?.auth) return; + this.setClient(supabaseClient); + } + + // Bind to a Supabase client. Keeps the loaded user data, so a client swap + // (for example a token refresh) does not reset the state to empty. + setClient(supabaseClient: SupabaseClient | undefined) { + if (!supabaseClient?.auth || supabaseClient === this.#client) return; + + this.#unsub?.subscription.unsubscribe(); + this.#client = supabaseClient; const { data } = supabaseClient.auth.onAuthStateChange((event, session) => { // console.log('Supabase auth state changed:', event, session); @@ -85,11 +95,7 @@ export class UserState { } get isComplete() { - return ( - this.#userState && - this.#userPublicProfile && - this.#userPreferences - ); // Neither null nor undefined + return this.#userState && this.#userPublicProfile && this.#userPreferences; // Neither null nor undefined } stopListening() { @@ -103,7 +109,9 @@ export class UserState { this.#userPublicProfile = (await getUserPublicProfile(this.#userState.id)).profile; // Refresh user preferences - this.#userPreferences = (await getUserPreferences(supabase.client, this.#userState.id)).preferences; + this.#userPreferences = ( + await getUserPreferences(supabase.client, this.#userState.id) + ).preferences; // Refresh user credit logs this.#userCreditLogs = (await getUserCreditLogs(this.#userState.id)).logs; @@ -115,8 +123,13 @@ export class UserState { } } -const currentUserState = $derived(new UserState(supabase.client)); +let currentUserState: UserState | undefined; export function getUserState() { + if (!currentUserState) { + currentUserState = new UserState(supabase.client); + } else { + currentUserState.setClient(supabase.client); + } return currentUserState; } diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 2a48d56..7faef68 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -52,6 +52,13 @@ }; let openChat = $state(false); + + // Latch once the first load finishes. Later refreshes may drop `isComplete` + // for a moment; without this latch the whole app would flash the splash. + let hasLoadedOnce = $state(false); + $effect(() => { + if (userState.isComplete) hasLoadedOnce = true; + }); @@ -59,7 +66,7 @@ -{#if !userState.isComplete} +{#if !hasLoadedOnce} {:else}