Skeleton: Initial support for user emails

This commit is contained in:
2025-03-14 22:19:40 +01:00
parent 12803a7b8f
commit c597fff15a

View File

@ -18,6 +18,7 @@
RacePickCard, RacePickCard,
RaceResultCard, RaceResultCard,
SeasonPickCard, SeasonPickCard,
EMailIcon,
} from "$lib/components"; } from "$lib/components";
import { get_avatar_preview_event_handler } from "$lib/image"; import { get_avatar_preview_event_handler } from "$lib/image";
import { import {
@ -37,11 +38,12 @@
type ModalComponent, type ModalComponent,
type ToastStore, type ToastStore,
getToastStore, getToastStore,
SlideToggle,
} from "@skeletonlabs/skeleton"; } from "@skeletonlabs/skeleton";
import { computePosition, autoUpdate, offset, shift, flip, arrow } from "@floating-ui/dom"; import { computePosition, autoUpdate, offset, shift, flip, arrow } from "@floating-ui/dom";
import { invalidate } from "$app/navigation"; import { invalidate } from "$app/navigation";
import { get_error_toast } from "$lib/toast"; import { get_error_toast, get_info_toast } from "$lib/toast";
import { pb, 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";
@ -138,11 +140,33 @@
// Reactive state // Reactive state
let username_value: string = $state(data.user?.username ?? ""); let username_value: string = $state(data.user?.username ?? "");
let firstname_value: string = $state(data.user?.firstname ?? ""); let firstname_value: string = $state(data.user?.firstname ?? "");
let email_value: string = $state(data.user?.email ?? "");
let password_value: string = $state(""); let password_value: string = $state("");
let avatar_value: FileList | undefined = $state(); let avatar_value: FileList | undefined = $state();
let registration_mode: boolean = $state(false);
// Add "Enter" event listeners for login/register text inputs
const enter_handler = (event: KeyboardEvent) => {
if (event.key === "Enter") {
// Cancel the default action, if needed
event.preventDefault();
registration_mode ? update_profile(true) : login();
}
};
// Database actions // Database actions
const login = async (): Promise<void> => { const login = async (): Promise<void> => {
if (!username_value || username_value.trim() === "") {
toastStore.trigger(get_error_toast("Please enter your username!"));
return;
}
if (!password_value || password_value.trim() === "") {
toastStore.trigger(get_error_toast("Please enter your password!"));
return;
}
try { try {
await pb.collection("users").authWithPassword(username_value, password_value); await pb.collection("users").authWithPassword(username_value, password_value);
} catch (error) { } catch (error) {
@ -150,6 +174,9 @@
} }
await invalidate("data:user"); await invalidate("data:user");
drawerStore.close(); drawerStore.close();
username_value = data.user?.username ?? "";
firstname_value = data.user?.firstname ?? "";
email_value = data.user?.email ?? "";
password_value = ""; password_value = "";
}; };
@ -159,6 +186,7 @@
drawerStore.close(); drawerStore.close();
username_value = ""; username_value = "";
firstname_value = ""; firstname_value = "";
email_value = "";
password_value = ""; password_value = "";
}; };
@ -201,6 +229,10 @@
toastStore.trigger(get_error_toast("Please enter your first name!")); toastStore.trigger(get_error_toast("Please enter your first name!"));
return; return;
} }
if (!email_value || email_value.trim() === "") {
toastStore.trigger(get_error_toast("Please enter your e-mail address!"));
return;
}
if (!password_value || password_value.trim() === "") { if (!password_value || password_value.trim() === "") {
toastStore.trigger(get_error_toast("Please enter a password!")); toastStore.trigger(get_error_toast("Please enter a password!"));
return; return;
@ -209,11 +241,17 @@
await pb.collection("users").create({ await pb.collection("users").create({
username: username_value.trim(), username: username_value.trim(),
firstname: firstname_value.trim(), firstname: firstname_value.trim(),
email: email_value.trim(),
password: password_value.trim(), password: password_value.trim(),
passwordConfirm: password_value.trim(), // lol passwordConfirm: password_value.trim(), // lol
admin: false, admin: false,
}); });
await pb.collection("users").requestVerification(email_value.trim());
toastStore.trigger(get_info_toast("Check your inbox!"));
pb.authStore.clear();
await login(); await login();
} else { } else {
if (!data.user?.id || data.user.id === "") { if (!data.user?.id || data.user.id === "") {
@ -221,12 +259,20 @@
return; return;
} }
if (email_value && email_value.trim() !== data.user.email) {
await pb.collection("users").requestEmailChange(email_value.trim());
toastStore.trigger(get_info_toast("Check your inbox!"));
}
await pb.collection("users").update(data.user.id, { await pb.collection("users").update(data.user.id, {
username: username_value.trim().length > 0 ? username_value.trim() : data.user.username, username: username_value.trim().length > 0 ? username_value.trim() : data.user.username,
firstname: firstname:
firstname_value.trim().length > 0 ? firstname_value.trim() : data.user.firstname, firstname_value.trim().length > 0 ? firstname_value.trim() : data.user.firstname,
avatar: avatar_avif, avatar: avatar_avif,
}); });
pb.authStore.clear();
toastStore.trigger(get_info_toast("Please login again (sry UwU)!"));
drawerStore.close(); drawerStore.close();
} }
} catch (error) { } catch (error) {
@ -322,7 +368,22 @@
<!-- Login Drawer --> <!-- Login Drawer -->
<!-- Login Drawer --> <!-- Login Drawer -->
<div class="flex flex-col gap-2 p-2 pt-3"> <div class="flex flex-col gap-2 p-2 pt-3">
<h4 class="h4 select-none">Enter Username and Password</h4> <div class="flex">
<h4 class="h4 select-none text-nowrap align-middle font-bold" style="line-height: 32px;">
Login or Register
</h4>
<div class="w-full"></div>
<div class="flex gap-2">
<span class="align-middle" style="line-height: 32px;">Login</span>
<SlideToggle
name="registrationmode"
background="bg-tertiary-500"
active="bg-tertiary-500"
bind:checked={registration_mode}
/>
<span class="align-middle" style="line-height: 32px;">Register</span>
</div>
</div>
<Input <Input
bind:value={username_value} bind:value={username_value}
placeholder="Username" placeholder="Username"
@ -330,28 +391,68 @@
minlength={3} minlength={3}
maxlength={10} maxlength={10}
required required
onkeypress={enter_handler}
> >
<UserIcon /> <UserIcon />
</Input> </Input>
<Input <div
bind:value={firstname_value} class="{registration_mode
placeholder="First Name (leave empty for login)" ? ''
autocomplete="off" : 'mt-[-8px] h-0'} overflow-hidden transition-all duration-150 ease-out"
> >
<NameIcon /> <Input
</Input> bind:value={firstname_value}
placeholder="First Name"
autocomplete="off"
tabindex={registration_mode ? 0 : -1}
onkeypress={enter_handler}
>
<NameIcon />
</Input>
</div>
<div
class="{registration_mode
? ''
: 'mt-[-8px] h-0'} overflow-hidden transition-all duration-150 ease-out"
>
<Input
id="login_email"
type="email"
bind:value={email_value}
placeholder="E-Mail"
autocomplete="email"
tabindex={registration_mode ? 0 : -1}
onkeypress={enter_handler}
>
<EMailIcon />
</Input>
</div>
<Input <Input
id="login_password"
bind:value={password_value} bind:value={password_value}
type="password" type="password"
placeholder="Password" placeholder="Password"
autocomplete="off" autocomplete="off"
required required
onkeypress={enter_handler}
> >
<PasswordIcon /> <PasswordIcon />
</Input> </Input>
<div class="flex justify-end gap-2"> <div
<Button onclick={login} color="tertiary" shadow>Login</Button> class="{!registration_mode
<Button onclick={update_profile(true)} color="tertiary" shadow>Register</Button> ? ''
: 'mt-[-8px] h-0'} w-full overflow-hidden transition-all duration-150 ease-out"
>
<Button onclick={login} color="tertiary" width="w-full" shadow>Login</Button>
</div>
<div
class="{registration_mode
? ''
: 'mt-[-8px] h-0'} w-full overflow-hidden transition-all duration-150 ease-out"
>
<Button onclick={update_profile(true)} color="tertiary" width="w-full" shadow>
Register
</Button>
</div> </div>
</div> </div>
{:else if $drawerStore.id === "profile_drawer" && data.user} {:else if $drawerStore.id === "profile_drawer" && data.user}
@ -359,7 +460,7 @@
<!-- Profile Drawer --> <!-- Profile Drawer -->
<!-- Profile Drawer --> <!-- Profile Drawer -->
<div class="flex flex-col gap-2 p-2 pt-3"> <div class="flex flex-col gap-2 p-2 pt-3">
<h4 class="h4 select-none">Edit Profile</h4> <h4 class="h4 select-none align-middle font-bold" style="line-height: 32px;">Edit Profile</h4>
<Input <Input
bind:value={username_value} bind:value={username_value}
maxlength={10} maxlength={10}
@ -371,6 +472,9 @@
<Input bind:value={firstname_value} placeholder="First Name" autocomplete="off"> <Input bind:value={firstname_value} placeholder="First Name" autocomplete="off">
<NameIcon /> <NameIcon />
</Input> </Input>
<Input bind:value={email_value} placeholder="E-Mail" autocomplete="email">
<EMailIcon />
</Input>
<FileDropzone <FileDropzone
name="avatar" name="avatar"
bind:files={avatar_value} bind:files={avatar_value}
@ -381,8 +485,10 @@
</svelte:fragment> </svelte:fragment>
</FileDropzone> </FileDropzone>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<Button onclick={update_profile()} color="secondary" shadow>Save Changes</Button> <Button onclick={update_profile()} color="secondary" width="w-full" shadow>
<Button onclick={logout} color="primary" shadow>Logout</Button> Save Changes
</Button>
<Button onclick={logout} color="primary" width="w-full" shadow>Logout</Button>
</div> </div>
</div> </div>
{/if} {/if}