Compare commits
4 Commits
a3805f76a1
...
b207aa5e29
| Author | SHA1 | Date | |
|---|---|---|---|
| b207aa5e29 | |||
| a552865b2f | |||
| 999cf5bf16 | |||
| aad969fc46 |
@ -139,6 +139,11 @@
|
||||
help = "Serve Formula 11 (Prod)";
|
||||
command = "npm run build && npm run preview -- --host --port 5173";
|
||||
}
|
||||
{
|
||||
name = "check";
|
||||
help = "Continuously monitor for SvelteKit issues";
|
||||
command = "svelte-check --watch";
|
||||
}
|
||||
];
|
||||
};
|
||||
});
|
||||
|
||||
@ -1,77 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import { LazyImage } from "$lib/components";
|
||||
import { lazyload } from "$lib/lazyload";
|
||||
|
||||
interface LazyCardProps {
|
||||
children: Snippet;
|
||||
|
||||
/** The URL for a possible header image. Leave undefined for no header image. Set to empty string for an image not yet loaded. */
|
||||
imgsrc?: string | undefined;
|
||||
|
||||
/** The id of the header image element for JS access. */
|
||||
imgid?: string | undefined;
|
||||
|
||||
/** The aspect ratio width used to reserve image space (while its loading) */
|
||||
imgwidth: number;
|
||||
|
||||
/** The aspect ratio height used to reserve image space (while its loading) */
|
||||
imgheight: number;
|
||||
|
||||
/** Hide the header image element. It can be shown by removing the "hidden" property using JS and the imgid. */
|
||||
imghidden?: boolean;
|
||||
|
||||
/** The aspect ratio width used to reserve card space (while its loading) */
|
||||
cardwidth: number;
|
||||
|
||||
/** The aspect ratio height used to reserve card space (while its loading) */
|
||||
cardheight: number;
|
||||
}
|
||||
|
||||
let {
|
||||
children,
|
||||
imgsrc = undefined,
|
||||
imgid = undefined,
|
||||
imgwidth,
|
||||
imgheight,
|
||||
imghidden = false,
|
||||
cardwidth,
|
||||
cardheight,
|
||||
...restProps
|
||||
}: LazyCardProps = $props();
|
||||
|
||||
let load: boolean = $state(false);
|
||||
|
||||
const lazy_visible_handler = () => {
|
||||
load = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
use:lazyload
|
||||
onLazyVisible={lazy_visible_handler}
|
||||
style="aspect-ratio: {cardwidth} / {cardheight};"
|
||||
>
|
||||
<div class="card w-full overflow-hidden bg-white shadow">
|
||||
<!-- Allow empty strings for images that only appear after user action -->
|
||||
{#if imgsrc !== undefined}
|
||||
<LazyImage
|
||||
id={imgid}
|
||||
src={imgsrc}
|
||||
{imgwidth}
|
||||
{imgheight}
|
||||
alt="Card header"
|
||||
draggable="false"
|
||||
class="select-none shadow"
|
||||
hidden={imghidden}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Only lazyload children, as the image is already lazy (also the image fade would break) -->
|
||||
{#if load}
|
||||
<div class="p-2" {...restProps}>
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@ -9,7 +9,6 @@ import Search from "./form/Search.svelte";
|
||||
|
||||
import Card from "./cards/Card.svelte";
|
||||
import DriverCard from "./cards/DriverCard.svelte";
|
||||
import LazyCard from "./cards/LazyCard.svelte";
|
||||
import RaceCard from "./cards/RaceCard.svelte";
|
||||
import SubstitutionCard from "./cards/SubstitutionCard.svelte";
|
||||
import TeamCard from "./cards/TeamCard.svelte";
|
||||
@ -18,6 +17,7 @@ import type { DropdownOption } from "./form/Dropdown";
|
||||
import type { TableColumn } from "./Table";
|
||||
|
||||
import MenuDrawerIcon from "./svg/MenuDrawerIcon.svelte";
|
||||
import NameIcon from "./svg/NameIcon.svelte";
|
||||
import PasswordIcon from "./svg/PasswordIcon.svelte";
|
||||
import UserIcon from "./svg/UserIcon.svelte";
|
||||
|
||||
@ -36,7 +36,6 @@ export {
|
||||
// Cards
|
||||
Card,
|
||||
DriverCard,
|
||||
LazyCard,
|
||||
RaceCard,
|
||||
SubstitutionCard,
|
||||
TeamCard,
|
||||
@ -46,6 +45,7 @@ export {
|
||||
type TableColumn,
|
||||
|
||||
// SVG
|
||||
NameIcon,
|
||||
MenuDrawerIcon,
|
||||
PasswordIcon,
|
||||
UserIcon,
|
||||
|
||||
10
src/lib/components/svg/NameIcon.svelte
Normal file
10
src/lib/components/svg/NameIcon.svelte
Normal file
@ -0,0 +1,10 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 411 511.71"
|
||||
fill="currentColor"
|
||||
class="h-4 w-4 opacity-70"
|
||||
>
|
||||
<path
|
||||
d="M69.04 126.32h70.44L76.02 0h40.4l63.25 126.32h49.54L292.22.01h40.16l-62.87 126.31h72.46c37.9 0 69.03 31.13 69.03 69.03v247.33c0 37.9-31.13 69.03-69.03 69.03H69.04C31.07 511.71 0 480.64 0 442.68V195.35c0-37.96 31.08-69.03 69.04-69.03zm36.57 231.81L89.13 334.2c-.58-.79-.94-2.51-1.08-5.17h-.43v29.1H66.06v-67.37h20.27l16.48 23.94c.58.78.94 2.5 1.08 5.17h.43v-29.11h21.57v67.37h-20.28zm49.53 0H132.4l17.46-67.37h33.31l17.46 67.37h-22.75l-2.48-10.67h-17.77l-2.49 10.67zm9.53-46.68-4.42 18.87h12.42l-4.31-18.87h-3.69zm62.31 46.68h-22.53l4.1-67.37h28.13l8.41 34.28h.75l8.41-34.28h28.13l4.1 67.37h-22.52l-1.31-32.66h-.75l-8.19 32.66h-16.49l-8.3-32.66h-.64l-1.3 32.66zm113.11-25.44h-21.55v10.71h26.41v14.73h-47.97v-67.37h47.42l-2.68 14.74h-23.18v11.57h21.55v15.62zM154.5 437.39h102v17.27h-102v-17.27zm-53.72-44.49h209.43v17.27H100.78V392.9zm104.07-217.74 12.62-25.3H69.04c-25.03 0-45.5 20.47-45.5 45.49v247.33c0 24.96 20.53 45.49 45.5 45.49h272.93c24.97 0 45.49-20.53 45.49-45.49V195.35c0-25.02-20.47-45.49-45.49-45.49h-84.2l-20.65 41.39c5.41 7 8.62 15.77 8.62 25.29 0 22.86-18.53 41.39-41.39 41.39-22.85 0-41.39-18.53-41.39-41.39s18.54-41.39 41.39-41.39l.5.01z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -7,6 +7,7 @@ export interface Graphic {
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
firstname: string;
|
||||
avatar: string;
|
||||
avatar_url?: string;
|
||||
admin: boolean;
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
TeamCard,
|
||||
RaceCard,
|
||||
SubstitutionCard,
|
||||
NameIcon,
|
||||
} from "$lib/components";
|
||||
import { get_avatar_preview_event_handler } from "$lib/image";
|
||||
|
||||
@ -151,13 +152,15 @@
|
||||
<!-- Data Drawer -->
|
||||
<!-- Data Drawer -->
|
||||
<div class="flex flex-col gap-2 p-2 pt-3">
|
||||
<Button href="/data/raceresult" onclick={close_drawer} color="surface" width="w-full"
|
||||
>Race Results
|
||||
<Button href="/data/raceresults" onclick={close_drawer} color="surface" width="w-full">
|
||||
Race Results
|
||||
</Button>
|
||||
<Button href="/data/season/teams" onclick={close_drawer} color="surface" width="w-full">
|
||||
Season
|
||||
</Button>
|
||||
<Button href="/data/users" onclick={close_drawer} color="surface" width="w-full">
|
||||
Users
|
||||
</Button>
|
||||
<Button href="/data/season/teams" onclick={close_drawer} color="surface" width="w-full"
|
||||
>Season</Button
|
||||
>
|
||||
<Button href="/data/user" onclick={close_drawer} color="surface" width="w-full">Users</Button>
|
||||
</div>
|
||||
{:else if $drawerStore.id === "login_drawer"}
|
||||
<!-- Login Drawer -->
|
||||
@ -168,22 +171,26 @@
|
||||
<form method="POST" class="contents">
|
||||
<!-- Supply the pathname so the form can redirect to the current page. -->
|
||||
<input type="hidden" name="redirect_url" value={$page.url.pathname} />
|
||||
<Input name="username" placeholder="Username" autocomplete="username" required
|
||||
><UserIcon />
|
||||
<Input name="username" placeholder="Username" autocomplete="username" required>
|
||||
<UserIcon />
|
||||
</Input>
|
||||
<Input name="password" type="password" placeholder="Password" autocomplete="off" required
|
||||
><PasswordIcon />
|
||||
<Input name="firstname" placeholder="First Name (leave empty for login)" autocomplete="off">
|
||||
<NameIcon />
|
||||
</Input>
|
||||
<Input name="password" type="password" placeholder="Password" autocomplete="off" required>
|
||||
<PasswordIcon />
|
||||
</Input>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button formaction="/profile?/login" onclick={close_drawer} color="tertiary" submit
|
||||
>Login
|
||||
<Button formaction="/profile?/login" onclick={close_drawer} color="tertiary" submit>
|
||||
Login
|
||||
</Button>
|
||||
<Button
|
||||
formaction="/profile?/create_profile"
|
||||
onclick={close_drawer}
|
||||
color="tertiary"
|
||||
submit
|
||||
>Register
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@ -206,6 +213,14 @@
|
||||
>
|
||||
<UserIcon />
|
||||
</Input>
|
||||
<Input
|
||||
name="firstname"
|
||||
value={data.user.firstname}
|
||||
placeholder="First Name"
|
||||
autocomplete="off"
|
||||
>
|
||||
<NameIcon />
|
||||
</Input>
|
||||
<FileDropzone
|
||||
name="avatar"
|
||||
onchange={get_avatar_preview_event_handler("user_avatar_preview")}
|
||||
|
||||
@ -7,11 +7,12 @@ import { AVATAR_HEIGHT, AVATAR_WIDTH } from "$lib/config";
|
||||
export const actions = {
|
||||
create_profile: async ({ request, locals }): Promise<void> => {
|
||||
const data: FormData = form_data_clean(await request.formData());
|
||||
form_data_ensure_keys(data, ["username", "password", "redirect_url"]);
|
||||
form_data_ensure_keys(data, ["username", "firstname", "password", "redirect_url"]);
|
||||
|
||||
// Confirm password lol
|
||||
await locals.pb.collection("users").create({
|
||||
username: data.get("username")?.toString(),
|
||||
firstname: data.get("firstname")?.toString(),
|
||||
password: data.get("password")?.toString(),
|
||||
passwordConfirm: data.get("password")?.toString(),
|
||||
admin: false,
|
||||
@ -22,6 +23,7 @@ export const actions = {
|
||||
.collection("users")
|
||||
.authWithPassword(data.get("username")?.toString(), data.get("password")?.toString());
|
||||
|
||||
// The current page is sent with the form, redirect to that page
|
||||
redirect(303, data.get("redirect_url")?.toString() ?? "/");
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user