Updating dependencies, upgrade latest svelte 5, and updating to use svelte 5 in most places.

This commit is contained in:
Bradley Shellnut 2024-07-06 23:12:36 -07:00
parent 8c47357605
commit 63ac7dfd76
22 changed files with 1000 additions and 1145 deletions

View file

@ -23,48 +23,48 @@
"devDependencies": {
"@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.81.0",
"@playwright/test": "^1.44.1",
"@playwright/test": "^1.45.1",
"@resvg/resvg-js": "^2.6.2",
"@sveltejs/adapter-auto": "^3.2.2",
"@sveltejs/enhanced-img": "^0.2.1",
"@sveltejs/kit": "^2.5.14",
"@sveltejs/kit": "^2.5.18",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@types/cookie": "^0.6.0",
"@types/node": "^20.14.2",
"@types/node": "^20.14.10",
"@types/pg": "^8.11.6",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"@typescript-eslint/parser": "^7.12.0",
"@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0",
"autoprefixer": "^10.4.19",
"drizzle-kit": "^0.22.7",
"drizzle-kit": "^0.22.8",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.39.3",
"eslint-plugin-svelte": "^2.41.0",
"just-clone": "^6.2.0",
"just-debounce-it": "^3.2.0",
"postcss": "^8.4.38",
"postcss": "^8.4.39",
"postcss-import": "^16.1.0",
"postcss-load-config": "^5.1.0",
"postcss-preset-env": "^9.5.14",
"postcss-preset-env": "^9.5.16",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.4",
"sass": "^1.77.5",
"prettier-plugin-svelte": "^3.2.5",
"sass": "^1.77.6",
"satori": "^0.10.13",
"satori-html": "^0.3.2",
"svelte": "^4.2.18",
"svelte-check": "^3.8.0",
"svelte": "5.0.0-next.175",
"svelte-check": "^3.8.4",
"svelte-headless-table": "^0.18.2",
"svelte-meta-tags": "^3.1.2",
"svelte-preprocess": "^5.1.4",
"svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-flash-message": "^2.4.4",
"sveltekit-rate-limiter": "^0.5.1",
"sveltekit-superforms": "^2.15.1",
"sveltekit-superforms": "^2.15.2",
"tailwindcss": "^3.4.4",
"ts-node": "^10.9.2",
"tslib": "^2.6.3",
"tsx": "^4.15.4",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"tsx": "^4.16.2",
"typescript": "^5.5.3",
"vite": "^5.3.3",
"vitest": "^1.6.0",
"zod": "^3.23.8"
},
@ -79,12 +79,12 @@
"@iconify-icons/mdi": "^1.2.48",
"@lucia-auth/adapter-drizzle": "^1.0.7",
"@lukeed/uuid": "^2.0.1",
"@neondatabase/serverless": "^0.9.3",
"@neondatabase/serverless": "^0.9.4",
"@paralleldrive/cuid2": "^2.2.2",
"@sveltejs/adapter-vercel": "^5.3.2",
"@sveltejs/adapter-vercel": "^5.4.1",
"@types/feather-icons": "^4.29.4",
"@vercel/og": "^0.5.20",
"bits-ui": "^0.21.10",
"bits-ui": "^0.21.11",
"boardgamegeekclient": "^1.9.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
@ -93,7 +93,7 @@
"dotenv-expand": "^11.0.6",
"drizzle-orm": "^0.31.2",
"feather-icons": "^4.29.2",
"formsnap": "^1.0.0",
"formsnap": "^1.0.1",
"html-entities": "^2.5.2",
"iconify-icon": "^2.1.0",
"just-capitalize": "^3.2.0",
@ -101,8 +101,8 @@
"loader": "^2.1.1",
"lucia": "3.2.0",
"lucide-svelte": "^0.390.0",
"open-props": "^1.7.4",
"oslo": "^1.2.0",
"open-props": "^1.7.5",
"oslo": "^1.2.1",
"pg": "^8.12.0",
"postgres": "^3.4.4",
"qrcode": "^1.5.3",
@ -112,6 +112,6 @@
"tailwind-merge": "^2.3.0",
"tailwind-variants": "^0.2.1",
"tailwindcss-animate": "^1.0.7",
"zod-to-json-schema": "^3.23.0"
"zod-to-json-schema": "^3.23.1"
}
}

File diff suppressed because it is too large Load diff

View file

@ -41,6 +41,16 @@ export default async function seed(db: db) {
console.log('Admin user created.', adminUser);
await db
.insert(schema.collections)
.values({ user_id: adminUser[0].id })
.onConflictDoNothing();
await db
.insert(schema.wishlists)
.values({ user_id: adminUser[0].id })
.onConflictDoNothing();
await db
.insert(schema.userRoles)
.values({

View file

@ -1,4 +1,6 @@
<script lang="ts">
const { children } = $props();
function portal(node: HTMLElement) {
let target;
@ -25,5 +27,5 @@
</script>
<div use:portal hidden>
<slot />
{@render children()}
</div>

View file

@ -1,112 +1,112 @@
<script>
/**
* @event {boolean} check
*/
/**
* @event {boolean} check
*/
/**
* Specify the value of the checkbox
* @type {any}
*/
export let value = '';
/**
* Specify the value of the checkbox
* @type {any}
*/
export let value = '';
/** Specify whether the checkbox is checked */
export let checked = false;
/** Specify whether the checkbox is checked */
export let checked = false;
/**
* Specify the bound group
* @type {any[]}
*/
export let group = undefined;
/**
* Specify the bound group
* @type {any[]}
*/
export let group = undefined;
/** Specify whether the checkbox is indeterminate */
export let indeterminate = false;
/** Specify whether the checkbox is indeterminate */
export let indeterminate = false;
/** Set to `true` to display the skeleton state */
export let skeleton = false;
/** Set to `true` to display the skeleton state */
export let skeleton = false;
/** Set to `true` to mark the field as required */
export let required = false;
/** Set to `true` to mark the field as required */
export let required = false;
/** Set to `true` for the checkbox to be read-only */
export let readonly = false;
/** Set to `true` for the checkbox to be read-only */
export let readonly = false;
/** Set to `true` to disable the checkbox */
export let disabled = false;
/** Set to `true` to disable the checkbox */
export let disabled = false;
/** Specify the label text */
export let labelText = '';
/** Specify the label text */
export let labelText = '';
/** Set to `true` to visually hide the label text */
export let hideLabel = false;
/** Set to `true` to visually hide the label text */
export let hideLabel = false;
/** Set a name for the input element */
export let name = '';
/** Set a name for the input element */
export let name = '';
/**
* Specify the title attribute for the label element
* @type {string}
*/
export let title = undefined;
/**
* Specify the title attribute for the label element
* @type {string}
*/
export let title = undefined;
/** Set an id for the input label */
export let id = 'ccs-' + Math.random().toString(36);
/** Set an id for the input label */
export let id = 'ccs-' + Math.random().toString(36);
/** Obtain a reference to the input HTML element */
export let ref = null;
/** Obtain a reference to the input HTML element */
export let ref = null;
import { createEventDispatcher } from 'svelte';
import CheckboxSkeleton from './CheckboxSkeleton.svelte';
import { createEventDispatcher } from 'svelte';
import CheckboxSkeleton from './CheckboxSkeleton.svelte';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
$: useGroup = Array.isArray(group);
$: checked = useGroup ? group.includes(value) : checked;
$: dispatch('check', checked);
$: useGroup = Array.isArray(group);
$: checked = useGroup ? group.includes(value) : checked;
$: dispatch('check', checked);
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
{#if skeleton}
<CheckboxSkeleton {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave />
<CheckboxSkeleton {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave />
{:else}
<div
class:bx--form-item={true}
class:bx--checkbox-wrapper={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave
>
<input
bind:this={ref}
type="checkbox"
{value}
{checked}
{disabled}
{id}
{indeterminate}
{name}
{required}
{readonly}
class:bx--checkbox={true}
on:change={() => {
if (useGroup) {
group = group.includes(value)
? group.filter((_value) => _value !== value)
: [...group, value];
} else {
checked = !checked;
}
}}
on:change
on:blur
/>
<label for={id} {title} class:bx--checkbox-label={true}>
<span class:bx--checkbox-label-text={true} class:bx--visually-hidden={hideLabel}>
<slot name="labelText">
{labelText}
</slot>
</span>
</label>
</div>
<div
class:bx--form-item={true}
class:bx--checkbox-wrapper={true}
{...$$restProps}
on:click
on:mouseover
on:mouseenter
on:mouseleave
>
<input
bind:this={ref}
type="checkbox"
{value}
{checked}
{disabled}
{id}
{indeterminate}
{name}
{required}
{readonly}
class:bx--checkbox={true}
on:change={() => {
if (useGroup) {
group = group.includes(value)
? group.filter((_value) => _value !== value)
: [...group, value];
} else {
checked = !checked;
}
}}
on:change
on:blur
/>
<label for={id} {title} class:bx--checkbox-label={true}>
<span class:bx--checkbox-label-text={true} class:bx--visually-hidden={hideLabel}>
<slot name="labelText">
{labelText}
</slot>
</span>
</label>
</div>
{/if}

View file

@ -8,15 +8,13 @@
import Logo from '$components/logo.svelte';
import type { Users } from '$db/schema';
export let user: Users | null = null;
type HeaderProps = {
user: Users | null;
};
console.log('header user', user);
let { user = null }: HeaderProps = $props();
let avatar: string;
$: if (user) {
avatar = user.username?.slice(0, 1).toUpperCase() || ':)';
}
let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)');
</script>
<header>

View file

@ -1,7 +1,5 @@
<script lang="ts">
export let url: string;
export let ariaLabel = `Link to ${url}`;
export let external = false;
const { url, ariaLabel = `Link to ${url}`, external = false, children }: { url: string; ariaLabel?: string; external?: boolean; children: any } = $props();
</script>
<a
@ -10,7 +8,7 @@
rel="noreferrer"
aria-label={`Board Game Atlas Link for ${ariaLabel}`}
>
<slot />
{@render children()}
</a>
<style>

View file

@ -1,11 +1,5 @@
<script lang="ts">
import { fade } from 'svelte/transition';
// import {
// Dialog,
// DialogDescription,
// DialogOverlay,
// DialogTitle
// } from '@rgossiaux/svelte-headlessui';
import { boredState } from '$lib/stores/boredState';
import { collectionStore } from '$lib/stores/collectionStore';
import { browser } from '$app/environment';

View file

@ -27,10 +27,24 @@ export function userNotFullyAuthenticated(user: User | null, session: Session |
return user && session && session.isTwoFactorAuthEnabled && !session.isTwoFactorAuthenticated;
}
/**
* Checks if the user is not fully authenticated.
*
* @param {User | null} user - The user object.
* @param {Session | null} session - The session object.
* @returns {boolean} True if the user is not fully authenticated, otherwise false.
*/
export function userNotAuthenticated(user: User | null, session: Session | null) {
return !user || !session || userNotFullyAuthenticated(user, session);
}
/**
* Checks if the user is fully authenticated.
*
* @param {User | null} user - The user object.
* @param {Session | null} session - The session object.
* @returns {boolean} True if the user is fully authenticated, otherwise false.
*/
export function userFullyAuthenticated(user: User | null, session: Session | null) {
return !userNotAuthenticated(user, session);
}

View file

@ -5,8 +5,8 @@
import { theme } from '$state/theme';
import toast, { Toaster } from 'svelte-french-toast';
export let data;
$: ({ user } = data);
const { data } = $props();
const { user } = data;
const flash = getFlash(page, {
clearOnNavigate: true,
@ -14,26 +14,27 @@
clearArray: true
});
onMount(() => {
$effect(() => {
// set the theme to the user's active theme
$theme = user?.theme || 'system';
document.querySelector('html')?.setAttribute('data-theme', $theme);
});
$effect(() => {
if ($flash) {
if ($flash.type === 'success') {
toast.success($flash.message);
} else {
toast.error($flash.message, {
duration: 5000
});
}
$: if ($flash) {
if ($flash.type === 'success') {
toast.success($flash.message);
} else {
toast.error($flash.message, {
duration: 5000
});
// Clearing the flash message could sometimes
// be required here to avoid double-toasting.
flash.set(undefined);
}
// Clearing the flash message could sometimes
// be required here to avoid double-toasting.
flash.set(undefined);
}
});
</script>
<h1>Do the admin stuff</h1>

View file

@ -1,6 +1,6 @@
<script lang="ts">
import DataTable from './user-table.svelte';
export let data;
const { data } = $props();
</script>
<h1>Users</h1>

View file

@ -4,7 +4,7 @@
import { Button } from '$lib/components/ui/button';
// import AddRolesForm from './add-roles-form.svelte';
export let data;
const { data } = $props();
const { user, availableRoles } = data;
const { user_roles }: { user_roles: { role: { name: string, cuid: string } }[] } = user;

View file

@ -1,5 +1,5 @@
<script lang="ts">
export let data;
const { data } = $props();
let collections = data?.collections || [];
</script>

View file

@ -56,10 +56,8 @@ export const actions: Actions = {
return fail(401);
}
const user = event.locals.user;
const dbUser = await db.query.users.findFirst({
where: eq(users.id, user.id),
where: eq(users.id, user!.id),
});
if (!dbUser?.hashed_password) {

View file

@ -3,7 +3,7 @@
import Header from '$components/Header.svelte';
import Footer from '$components/Footer.svelte';
export let data;
const { data, children } = $props();
console.log('layout data user', data.user);
</script>
@ -11,7 +11,7 @@
<Header user={data.user} />
<main>
<slot />
{@render children()}
</main>
<Footer />

View file

@ -2,7 +2,7 @@ import type { MetaTagsProps } from 'svelte-meta-tags';
import { eq } from 'drizzle-orm';
import type { PageServerLoad } from './$types';
import db from '../../db';
import { collections, wishlists } from '$db/schema';
import { collections, users, wishlists } from '$db/schema';
import { userFullyAuthenticated } from '$lib/server/auth-utils';
export const load: PageServerLoad = async (event) => {
@ -42,6 +42,10 @@ export const load: PageServerLoad = async (event) => {
});
if (userFullyAuthenticated(user, session)) {
const dbUser = await db.query.users.findFirst({
where: eq(users.id, user!.id!),
});
console.log('Sending back user details');
const userWishlists = await db.query.wishlists.findMany({
columns: {
@ -60,7 +64,16 @@ export const load: PageServerLoad = async (event) => {
console.log('Wishlists', userWishlists);
console.log('Collections', userCollection);
return { metaTagsChild: metaTags, user, wishlists: userWishlists, collections: userCollection };
return {
metaTagsChild: metaTags,
user: {
firstName: dbUser?.first_name,
lastName: dbUser?.last_name,
username: dbUser?.username,
},
wishlists: userWishlists,
collections: userCollection,
};
}
return { metaTagsChild: metaTags, user: null, wishlists: [], collections: [] };

View file

@ -1,12 +1,15 @@
<script lang="ts">
export let data;
const { data } = $props();
const { user, wishlists = [], collections = []} = data;
const welcome = $derived(`${data?.user?.firstName} ${data?.user?.lastName}` || user?.username)
$inspect(data);
</script>
<div class="container">
{#if user}
<h1>Welcome, {user.username}!</h1>
<h1>Welcome, {welcome}!</h1>
<div>
<h2>You wishlists:</h2>
{#each wishlists as wishlist}

View file

@ -1,25 +1,15 @@
<script lang="ts">
import { Image } from 'svelte-lazy-loader';
import { Dices, ExternalLinkIcon, MinusIcon, PlusIcon } from 'lucide-svelte';
import type { SavedGameType } from '$lib/types';
import { collectionStore } from '$lib/stores/collectionStore';
import { wishlistStore } from '$lib/stores/wishlistStore';
import type { PageData } from './$types';
import { Button } from '$components/ui/button';
import AddToList from '$components/AddToList.svelte';
import Badge from '$components/ui/badge/badge.svelte';
$: existsInCollection = $collectionStore.find((item: SavedGameType) => item.id === game.id);
$: existsInWishlist = $wishlistStore.find((item: SavedGameType) => item.id === game.id);
// $: collectionText = existsInCollection ? 'Remove from collection' : 'Add to collection';
// $: wishlistText = existsInWishlist ? 'Remove from wishlist' : 'Add to wishlist';
const { data } = $props();
const { game, user, in_collection, in_wishlist } = data;
export let data: PageData;
console.log('data', data);
$: ({ game, user, wishlist, collection, in_collection, in_wishlist } = data);
let seeMore: boolean = false;
let seeMore: boolean = $state(false);
</script>
<svelte:head>
@ -86,7 +76,7 @@
<section class="description" class:show={seeMore} class:hide={!seeMore} style="margin-top: 2rem;">
{@html game?.description}
</section>
<button class="btn button-icon" type="button" on:click={() => (seeMore = !seeMore)}
<button class="btn button-icon" type="button" onclick={() => (seeMore = !seeMore)}
>See
{#if !seeMore}
More

View file

@ -1,6 +1,3 @@
import { redirect } from 'sveltekit-flash-message/server';
import { notSignedInMessage } from '$lib/flashMessages';
export async function load(event) {
const { url, locals } = event;

View file

@ -4,7 +4,7 @@
import Logo from "$lib/components/logo.svelte";
import Transition from '$lib/components/transition.svelte';
export let data;
let { data, children } = $props();
</script>
<div class="container">
@ -31,7 +31,7 @@
style="
background-image:
url(https://images.unsplash.com/photo-1588591795084-1770cb3be374?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80"
/>
></div>
<div class="quote-wrapper">
<blockquote class="quote">
<p>
@ -43,7 +43,7 @@
</div>
<div class="auth-form">
<Transition url={data.url} transition={{ type: 'page' }}>
<slot />
{@render children()}
</Transition>
</div>
</div>

View file

@ -11,7 +11,7 @@
import * as Alert from "$components/ui/alert";
import { boredState } from '$lib/stores/boredState.js';
export let data;
let { data } = $props();
const superLoginForm = superForm(data.form, {
onSubmit: () => boredState.update((n) => ({ ...n, loading: true })),

View file

@ -13,10 +13,10 @@
const dev = process.env.NODE_ENV !== 'production';
export let data;
$: ({ user } = data);
const { data, children } = $props();
const { user } = data;
$: metaTags = {
const metaTags = $derived({
titleTemplate: '%s | Bored Game',
description: 'Bored Game, keep track of your games.',
openGraph: {
@ -26,7 +26,7 @@
description: 'Bored Game, keep track of your games',
},
...$page.data.metaTagsChild
}
});
const flash = getFlash(page, {
clearOnNavigate: true,
@ -41,34 +41,21 @@
});
$: if ($flash) {
if ($flash.type === 'success') {
toast.success($flash.message);
} else {
toast.error($flash.message, {
duration: 5000
});
$effect(() => {
if ($flash) {
if ($flash.type === 'success') {
toast.success($flash.message);
} else {
toast.error($flash.message, {
duration: 5000
});
}
// Clearing the flash message could sometimes
// be required here to avoid double-toasting.
flash.set(undefined);
}
// Clearing the flash message could sometimes
// be required here to avoid double-toasting.
flash.set(undefined);
}
// flash.subscribe(($flash) => {
// if (!$flash) return;
// if ($flash.type === 'success') {
// toast.success($flash.message);
// } else {
// toast.error($flash.message, {
// duration: 5000
// });
// }
// // Clearing the flash message could sometimes
// // be required here to avoid double-toasting.
// flash.set(undefined);
// });
});
onNavigate(async (navigation) => {
if (!document.startViewTransition) return;
@ -91,7 +78,7 @@
<PageLoadingIndicator />
<div class="layout">
<slot />
{@render children()}
</div>
<Toaster />