cache user settings in cookie for faster modal loading

store granblueId and showCrewGamertag in cookie at login,
use as initial values so modal opens instantly
This commit is contained in:
Justin Edmund 2025-12-13 22:14:49 -08:00
parent 362db1238f
commit 30128107fb
3 changed files with 20 additions and 59 deletions

View file

@ -18,7 +18,9 @@ export function buildCookies(oauth: OAuthLoginResponse, info: UserInfoResponse)
element: info.avatar.element ?? '',
language: info.language ?? 'en',
gender: info.gender ?? 0,
theme: info.theme ?? 'system'
theme: info.theme ?? 'system',
granblueId: info.granblueId,
showCrewGamertag: info.showCrewGamertag
}
return { account, user, accessTokenExpiresAt, refresh: oauth.refresh_token }

View file

@ -27,6 +27,8 @@ export interface UserInfoResponse {
language: string | null
gender: number | null
theme: string | null
granblueId?: string
showCrewGamertag?: boolean
}
export async function passwordGrantLogin(

View file

@ -38,20 +38,19 @@
let theme = $state(user.theme)
let bahamut = $state(user.bahamut ?? false)
// Form state - fields from API (must wait for query to load)
// Initialize as empty - will be set by $effect when API data loads
let granblueId = $state('')
let showCrewGamertag = $state(false)
let apiDataLoaded = $state(false)
// Form state - fields that are also in cookie (use cookie as initial value)
let granblueId = $state(user.granblueId ?? '')
let showCrewGamertag = $state(user.showCrewGamertag ?? false)
let saving = $state(false)
let error = $state<string | null>(null)
// Fetch current user data from API (to get actual show_gamertag value from database)
// Fetch current user data from API (to sync with latest database values)
const currentUserQuery = createQuery(() => ({
queryKey: ['currentUser', 'settings'],
queryFn: () => userAdapter.getCurrentUser(),
enabled: open // Only fetch when modal is open
enabled: open, // Only fetch when modal is open
staleTime: 5 * 60 * 1000 // Cache for 5 minutes
}))
// Fetch current user's crew (for showing gamertag toggle)
@ -62,21 +61,12 @@
const isInCrew = $derived(!!myCrewQuery.data)
const crewGamertag = $derived(myCrewQuery.data?.gamertag)
const isLoadingApiData = $derived(currentUserQuery.isLoading || currentUserQuery.isPending)
// Update form state when API data loads (to get actual values from database)
// Sync form state when API returns fresher data than cookie
$effect(() => {
if (currentUserQuery.data) {
granblueId = currentUserQuery.data.granblueId ?? ''
showCrewGamertag = currentUserQuery.data.showCrewGamertag ?? false
apiDataLoaded = true
}
})
// Reset apiDataLoaded when modal closes so fresh data is fetched next time
$effect(() => {
if (!open) {
apiDataLoaded = false
}
})
@ -252,31 +242,20 @@
/>
<!-- Granblue ID -->
{#if isLoadingApiData}
<div class="loading-field">
<span class="loading-label">Granblue ID</span>
<span class="loading-text">Loading...</span>
</div>
{:else}
<Input
bind:value={granblueId}
label="Granblue ID"
placeholder="Enter your Granblue ID"
contained
fullWidth
/>
{/if}
<Input
bind:value={granblueId}
label="Granblue ID"
placeholder="Enter your Granblue ID"
contained
fullWidth
/>
<!-- Show Crew Gamertag (only if in a crew with a gamertag) -->
{#if isInCrew && crewGamertag}
<div class="inline-switch">
<label for="show-gamertag">
<span>Show crew tag on profile</span>
{#if isLoadingApiData}
<span class="loading-text">Loading...</span>
{:else}
<Switch bind:checked={showCrewGamertag} name="show-gamertag" element={element as 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light' | undefined} />
{/if}
<Switch bind:checked={showCrewGamertag} name="show-gamertag" element={element as 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light' | undefined} />
</label>
<p class="field-hint">Display "{crewGamertag}" next to your name</p>
</div>
@ -391,28 +370,6 @@
}
}
.loading-field {
display: flex;
flex-direction: column;
gap: spacing.$unit-half;
.loading-label {
font-size: typography.$font-small;
font-weight: typography.$medium;
color: var(--text-primary);
}
.loading-text {
font-size: typography.$font-regular;
color: var(--text-secondary);
}
}
.loading-text {
font-size: typography.$font-regular;
color: var(--text-secondary);
}
.picture-section {
display: flex;
gap: spacing.$unit-3x;