Skeleton: Remove user/admin from fetched data

This data was fetched before it was available, so the user object was
undefined
This commit is contained in:
2025-03-14 22:35:03 +01:00
parent 03fe027f8c
commit 614e2becc4
12 changed files with 51 additions and 45 deletions

View File

@ -14,7 +14,7 @@
import { team_dropdown_options } from "$lib/dropdown"; import { team_dropdown_options } from "$lib/dropdown";
import { get_driver_headshot_template } from "$lib/database"; import { get_driver_headshot_template } from "$lib/database";
import { get_error_toast } from "$lib/toast"; import { get_error_toast } from "$lib/toast";
import { pb } from "$lib/pocketbase"; import { pb, pbUser } from "$lib/pocketbase";
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import type { PageData } from "../../../routes/data/season/drivers/$types"; import type { PageData } from "../../../routes/data/season/drivers/$types";
@ -43,7 +43,7 @@
// Reactive state // Reactive state
let required: boolean = $derived(!driver); let required: boolean = $derived(!driver);
let disabled: boolean = $derived(!data.admin); let disabled: boolean = $derived(!pbUser?.admin);
let firstname_input_value: string = $state(driver?.firstname ?? ""); let firstname_input_value: string = $state(driver?.firstname ?? "");
let lastname_input_value: string = $state(driver?.lastname ?? ""); let lastname_input_value: string = $state(driver?.lastname ?? "");
let code_input_value: string = $state(driver?.code ?? ""); let code_input_value: string = $state(driver?.code ?? "");

View File

@ -13,7 +13,7 @@
import { get_race_pictogram_template } from "$lib/database"; import { get_race_pictogram_template } from "$lib/database";
import { format_date, isodatetimeformat } from "$lib/date"; import { format_date, isodatetimeformat } from "$lib/date";
import { get_error_toast } from "$lib/toast"; import { get_error_toast } from "$lib/toast";
import { pb } from "$lib/pocketbase"; import { pb, pbUser } from "$lib/pocketbase";
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import type { PageData } from "../../../routes/data/season/races/$types"; import type { PageData } from "../../../routes/data/season/races/$types";
@ -50,7 +50,7 @@
// Reactive state // Reactive state
let required: boolean = $derived(!race); let required: boolean = $derived(!race);
let disabled: boolean = $derived(!data.admin); let disabled: boolean = $derived(!pbUser?.admin);
let name_value: string = $state(race?.name ?? ""); let name_value: string = $state(race?.name ?? "");
let step_value: string = $state(race?.step.toString() ?? ""); let step_value: string = $state(race?.step.toString() ?? "");
let pxx_value: string = $state(race?.pxx.toString() ?? ""); let pxx_value: string = $state(race?.pxx.toString() ?? "");

View File

@ -11,7 +11,7 @@
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config"; import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
import { driver_dropdown_options } from "$lib/dropdown"; import { driver_dropdown_options } from "$lib/dropdown";
import { get_error_toast } from "$lib/toast"; import { get_error_toast } from "$lib/toast";
import { pb } from "$lib/pocketbase"; import { pb, pbUser } from "$lib/pocketbase";
import type { PageData } from "../../../routes/racepicks/$types"; import type { PageData } from "../../../routes/racepicks/$types";
interface RacePickCardProps { interface RacePickCardProps {
@ -96,7 +96,7 @@
// Database actions // Database actions
const update_racepick = (create?: boolean): (() => Promise<void>) => { const update_racepick = (create?: boolean): (() => Promise<void>) => {
const handler = async (): Promise<void> => { const handler = async (): Promise<void> => {
if (!data.user?.id || data.user.id === "") { if (!pbUser?.id || pbUser.id === "") {
toastStore.trigger(get_error_toast("Invalid user id!")); toastStore.trigger(get_error_toast("Invalid user id!"));
return; return;
} }
@ -114,7 +114,7 @@
} }
const racepick_data = { const racepick_data = {
user: data.user.id, user: pbUser.id,
race: data.currentrace.id, race: data.currentrace.id,
pxx: pxx_select_value, pxx: pxx_select_value,
dnf: dnf_select_value, dnf: dnf_select_value,

View File

@ -12,7 +12,7 @@
import type { Driver, Race, RaceResult, Substitution } from "$lib/schema"; import type { Driver, Race, RaceResult, Substitution } from "$lib/schema";
import { get_by_value } from "$lib/database"; import { get_by_value } from "$lib/database";
import { race_dropdown_options } from "$lib/dropdown"; import { race_dropdown_options } from "$lib/dropdown";
import { pb } from "$lib/pocketbase"; import { pb, pbUser } from "$lib/pocketbase";
import { get_error_toast } from "$lib/toast"; import { get_error_toast } from "$lib/toast";
import type { PageData } from "../../../routes/data/raceresults/$types"; import type { PageData } from "../../../routes/data/raceresults/$types";
@ -51,7 +51,7 @@
// Reactive state // Reactive state
let required: boolean = $derived(!result); let required: boolean = $derived(!result);
let disabled: boolean = $derived(!data.admin); // TODO: Datelock (prevent entering future result) let disabled: boolean = $derived(!pbUser?.admin); // TODO: Datelock (prevent entering future result)
let race_select_value: string = $state(result?.race ?? ""); let race_select_value: string = $state(result?.race ?? "");
let currentrace: Race | undefined = $derived( let currentrace: Race | undefined = $derived(

View File

@ -14,7 +14,7 @@
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config"; import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
import { driver_dropdown_options, team_dropdown_options } from "$lib/dropdown"; import { driver_dropdown_options, team_dropdown_options } from "$lib/dropdown";
import { get_error_toast } from "$lib/toast"; import { get_error_toast } from "$lib/toast";
import { pb } from "$lib/pocketbase"; import { pb, pbUser } from "$lib/pocketbase";
import type { PageData } from "../../../routes/seasonpicks/$types"; import type { PageData } from "../../../routes/seasonpicks/$types";
interface SeasonPickCardProps { interface SeasonPickCardProps {
@ -184,7 +184,7 @@
// Database actions // Database actions
const update_seasonpick = (create?: boolean): (() => Promise<void>) => { const update_seasonpick = (create?: boolean): (() => Promise<void>) => {
const handler = async (): Promise<void> => { const handler = async (): Promise<void> => {
if (!data.user?.id || data.user.id === "") { if (!pbUser?.id || pbUser.id === "") {
toastStore.trigger(get_error_toast("Invalid user id!")); toastStore.trigger(get_error_toast("Invalid user id!"));
return; return;
} }
@ -229,7 +229,7 @@
} }
const seasonpick_data = { const seasonpick_data = {
user: data.user.id, user: pbUser.id,
hottake: hottake_value, hottake: hottake_value,
wdcwinner: wdc_value, wdcwinner: wdc_value,
wccwinner: wcc_value, wccwinner: wcc_value,

View File

@ -11,7 +11,7 @@
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config"; import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
import { driver_dropdown_options, race_dropdown_options } from "$lib/dropdown"; import { driver_dropdown_options, race_dropdown_options } from "$lib/dropdown";
import { get_error_toast } from "$lib/toast"; import { get_error_toast } from "$lib/toast";
import { pb } from "$lib/pocketbase"; import { pb, pbUser } from "$lib/pocketbase";
import type { PageData } from "../../../routes/data/season/substitutions/$types"; import type { PageData } from "../../../routes/data/season/substitutions/$types";
interface SubstitutionCardProps { interface SubstitutionCardProps {
@ -43,7 +43,7 @@
// Reactive state // Reactive state
let required: boolean = $derived(!substitution); let required: boolean = $derived(!substitution);
let disabled: boolean = $derived(!data.admin); let disabled: boolean = $derived(!pbUser?.admin);
let active_drivers: Driver[] = $derived((drivers ?? []).filter((d: Driver) => d.active)); let active_drivers: Driver[] = $derived((drivers ?? []).filter((d: Driver) => d.active));
let inactive_drivers: Driver[] = $derived((drivers ?? []).filter((d: Driver) => !d.active)); let inactive_drivers: Driver[] = $derived((drivers ?? []).filter((d: Driver) => !d.active));
let substitute_value: string = $state(substitution?.substitute ?? ""); let substitute_value: string = $state(substitution?.substitute ?? "");

View File

@ -17,7 +17,7 @@
} from "$lib/config"; } from "$lib/config";
import { get_team_banner_template, get_team_logo_template } from "$lib/database"; import { get_team_banner_template, get_team_logo_template } from "$lib/database";
import { get_error_toast } from "$lib/toast"; import { get_error_toast } from "$lib/toast";
import { pb } from "$lib/pocketbase"; import { pb, pbUser } from "$lib/pocketbase";
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import type { PageData } from "../../../routes/data/season/teams/$types"; import type { PageData } from "../../../routes/data/season/teams/$types";
@ -46,7 +46,7 @@
// Reactive state // Reactive state
let required: boolean = $derived(!team); let required: boolean = $derived(!team);
let disabled: boolean = $derived(!data.admin); let disabled: boolean = $derived(!pbUser?.admin);
let name_value: string = $state(team?.name ?? ""); let name_value: string = $state(team?.name ?? "");
let color_value: string = $state(team?.color ?? ""); let color_value: string = $state(team?.color ?? "");
let banner_value: FileList | undefined = $state(); let banner_value: FileList | undefined = $state();

View File

@ -1,3 +1,4 @@
import { get } from "svelte/store";
import { pb, pbUser } from "./pocketbase"; import { pb, pbUser } from "./pocketbase";
import type { import type {
CurrentPickedUser, CurrentPickedUser,

View File

@ -46,6 +46,7 @@
import { pb, pbUser, subscribe, unsubscribe } from "$lib/pocketbase"; import { pb, pbUser, subscribe, unsubscribe } from "$lib/pocketbase";
import { AVATAR_HEIGHT, AVATAR_WIDTH } from "$lib/config"; import { AVATAR_HEIGHT, AVATAR_WIDTH } from "$lib/config";
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
import { get } from "svelte/store";
let { data, children }: { data: LayoutData; children: Snippet } = $props(); let { data, children }: { data: LayoutData; children: Snippet } = $props();
@ -138,9 +139,9 @@
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow }); storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
// Reactive state // Reactive state
let username_value: string = $state(data.user?.username ?? ""); let username_value: string = $state(pbUser?.username ?? "");
let firstname_value: string = $state(data.user?.firstname ?? ""); let firstname_value: string = $state(pbUser?.firstname ?? "");
let email_value: string = $state(data.user?.email ?? ""); let email_value: string = $state(pbUser?.email ?? "");
let password_value: string = $state(""); let password_value: string = $state("");
let avatar_value: FileList | undefined = $state(); let avatar_value: FileList | undefined = $state();
@ -172,11 +173,12 @@
} catch (error) { } catch (error) {
toastStore.trigger(get_error_toast("" + error)); toastStore.trigger(get_error_toast("" + error));
} }
await invalidate("data:user"); await invalidate("data:user");
drawerStore.close(); drawerStore.close();
username_value = data.user?.username ?? ""; username_value = pbUser?.username ?? "";
firstname_value = data.user?.firstname ?? ""; firstname_value = pbUser?.firstname ?? "";
email_value = data.user?.email ?? ""; email_value = pbUser?.email ?? "";
password_value = ""; password_value = "";
}; };
@ -254,20 +256,20 @@
await login(); await login();
} else { } else {
if (!data.user?.id || data.user.id === "") { if (!pbUser?.id || pbUser.id === "") {
toastStore.trigger(get_error_toast("Invalid user id!")); toastStore.trigger(get_error_toast("Invalid user id!"));
return; return;
} }
if (email_value && email_value.trim() !== data.user.email) { if (email_value && email_value.trim() !== pbUser.email) {
await pb.collection("users").requestEmailChange(email_value.trim()); await pb.collection("users").requestEmailChange(email_value.trim());
toastStore.trigger(get_info_toast("Check your inbox!")); toastStore.trigger(get_info_toast("Check your inbox!"));
} }
await pb.collection("users").update(data.user.id, { await pb.collection("users").update(pbUser.id, {
username: username_value.trim().length > 0 ? username_value.trim() : data.user.username, username: username_value.trim().length > 0 ? username_value.trim() : pbUser.username,
firstname: firstname:
firstname_value.trim().length > 0 ? firstname_value.trim() : data.user.firstname, firstname_value.trim().length > 0 ? firstname_value.trim() : pbUser.firstname,
avatar: avatar_avif, avatar: avatar_avif,
}); });
@ -455,7 +457,7 @@
</Button> </Button>
</div> </div>
</div> </div>
{:else if $drawerStore.id === "profile_drawer" && data.user} {:else if $drawerStore.id === "profile_drawer" && pbUser}
<!-- Profile Drawer --> <!-- Profile Drawer -->
<!-- Profile Drawer --> <!-- Profile Drawer -->
<!-- Profile Drawer --> <!-- Profile Drawer -->
@ -537,14 +539,14 @@
activate={$page.url.pathname.startsWith("/data")}>Data</Button activate={$page.url.pathname.startsWith("/data")}>Data</Button
> >
{#if !data.user} {#if !pbUser}
<!-- Login drawer --> <!-- Login drawer -->
<Button color="primary" onclick={login_drawer}>Login</Button> <Button color="primary" onclick={login_drawer}>Login</Button>
{:else} {:else}
<!-- Profile drawer --> <!-- Profile drawer -->
<Avatar <Avatar
id="user_avatar_preview" id="user_avatar_preview"
src={data.user.avatar_url} src={pbUser?.avatar_url}
rounded="rounded-full" rounded="rounded-full"
width="w-10" width="w-10"
background="bg-primary-50" background="bg-primary-50"

View File

@ -1,6 +1,8 @@
import { fetch_graphics } from "$lib/fetch"; import { fetch_graphics } from "$lib/fetch";
import { pbUser } from "$lib/pocketbase"; import { pbUser } from "$lib/pocketbase";
import { get } from "svelte/store";
import type { LayoutLoad } from "./$types"; import type { LayoutLoad } from "./$types";
import type { User } from "$lib/schema";
// This makes the page client-side rendered // This makes the page client-side rendered
export const ssr = false; export const ssr = false;
@ -11,12 +13,14 @@ export const ssr = false;
// It will populate the "user" attribute of each page's "data" object, // It will populate the "user" attribute of each page's "data" object,
// so each page has access to the current user (or knows if no one is signed in). // so each page has access to the current user (or knows if no one is signed in).
export const load: LayoutLoad = async ({ fetch, depends }) => { export const load: LayoutLoad = async ({ fetch, depends }) => {
depends("data:graphics", "data:user"); depends("data:graphics");
return { return {
// NOTE: Don't do this! The user object will be updated after this, so it will be undefined!
//
// User information (synchronous) // User information (synchronous)
user: pbUser, // user: get(pbUser),
admin: pbUser?.admin ?? false, // admin: get(pbUser)?.admin ?? false,
// Return static data // Return static data
graphics: await fetch_graphics(fetch), graphics: await fetch_graphics(fetch),

View File

@ -22,6 +22,7 @@
import { format_date, shortdatetimeformat } from "$lib/date"; import { format_date, shortdatetimeformat } from "$lib/date";
import type { CurrentPickedUser, RacePick } from "$lib/schema"; import type { CurrentPickedUser, RacePick } from "$lib/schema";
import { get_by_value, get_driver_headshot_template } from "$lib/database"; import { get_by_value, get_driver_headshot_template } from "$lib/database";
import { pbUser } from "$lib/pocketbase";
let { data }: { data: PageData } = $props(); let { data }: { data: PageData } = $props();
@ -76,9 +77,8 @@
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<div <div
class="grid grid-cols-2 gap-2 lg:mx-auto lg:w-fit {data.user class="grid grid-cols-2 gap-2 lg:mx-auto lg:w-fit
? 'lg:grid-cols-6' {pbUser ? 'lg:grid-cols-6' : 'lg:grid-cols-4'}"
: 'lg:grid-cols-4'}"
> >
<!-- Show information about the next race --> <!-- Show information about the next race -->
<div class="card flex w-full min-w-40 flex-col p-2 shadow lg:max-w-40"> <div class="card flex w-full min-w-40 flex-col p-2 shadow lg:max-w-40">
@ -124,7 +124,7 @@
</div> </div>
<!-- Only show the userguess if signed in --> <!-- Only show the userguess if signed in -->
{#if data.user} {#if pbUser}
<!-- PXX pick --> <!-- PXX pick -->
<div class="card w-full min-w-40 p-2 pb-0 shadow lg:max-w-40"> <div class="card w-full min-w-40 p-2 pb-0 shadow lg:max-w-40">
<h1 class="mb-2 text-nowrap font-bold">Your P{data.currentrace.pxx} Pick:</h1> <h1 class="mb-2 text-nowrap font-bold">Your P{data.currentrace.pxx} Pick:</h1>
@ -309,10 +309,8 @@
{@const picks = racepicks.filter((pick: RacePick) => pick.user === user.id)} {@const picks = racepicks.filter((pick: RacePick) => pick.user === user.id)}
<div <div
class="card ml-1 mt-2 w-full min-w-12 overflow-hidden py-2 shadow lg:ml-2 lg:min-w-40 {data.user && class="card ml-1 mt-2 w-full min-w-12 overflow-hidden py-2 shadow lg:ml-2 lg:min-w-40
data.user.username === user.username {pbUser && pbUser.username === user.username ? 'bg-primary-300' : ''}"
? 'bg-primary-300'
: ''}"
> >
<!-- Avatar + name display at the top --> <!-- Avatar + name display at the top -->
<div class="mx-auto flex h-10 w-fit"> <div class="mx-auto flex h-10 w-fit">

View File

@ -23,6 +23,7 @@
TEAM_BANNER_WIDTH, TEAM_BANNER_WIDTH,
} from "$lib/config"; } from "$lib/config";
import Countdown from "$lib/components/Countdown.svelte"; import Countdown from "$lib/components/Countdown.svelte";
import { pbUser } from "$lib/pocketbase";
let { data }: { data: PageData } = $props(); let { data }: { data: PageData } = $props();
@ -69,12 +70,12 @@
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<div <div
class="grid grid-cols-2 gap-2 lg:mx-auto lg:w-fit {data.user class="grid grid-cols-2 gap-2 lg:mx-auto lg:w-fit {pbUser
? 'lg:grid-cols-5 2xl:grid-cols-10' ? 'lg:grid-cols-5 2xl:grid-cols-10'
: 'lg:grid-cols-2 2xl:grid-cols-2'}" : 'lg:grid-cols-2 2xl:grid-cols-2'}"
> >
<!-- Only show the stuff if signed in --> <!-- Only show the stuff if signed in -->
{#if data.user} {#if pbUser}
{@const teamwinners = data.seasonpick {@const teamwinners = data.seasonpick
? data.seasonpick.teamwinners ? data.seasonpick.teamwinners
.map((id: string) => get_by_value(drivers, "id", id) as Driver) .map((id: string) => get_by_value(drivers, "id", id) as Driver)
@ -345,8 +346,8 @@
: [undefined]} : [undefined]}
<div <div
class="card ml-1 mt-2 w-full min-w-[9.5rem] overflow-hidden py-2 shadow lg:ml-2 {data.user && class="card ml-1 mt-2 w-full min-w-[9.5rem] overflow-hidden py-2 shadow lg:ml-2 {pbUser &&
data.user.username === user.username pbUser.username === user.username
? 'bg-primary-300' ? 'bg-primary-300'
: ''}" : ''}"
> >