Moving to TS tailwindcss, move to Sonner toast.

This commit is contained in:
Bradley Shellnut 2024-10-02 16:01:38 -07:00
parent 4032838f49
commit ad74bc0f85
17 changed files with 217 additions and 358 deletions

View file

@ -2,7 +2,7 @@
"$schema": "https://shadcn-svelte.com/schema.json", "$schema": "https://shadcn-svelte.com/schema.json",
"style": "default", "style": "default",
"tailwind": { "tailwind": {
"config": "tailwind.config.js", "config": "tailwind.config.ts",
"css": "src/lib/styles/app.pcss", "css": "src/lib/styles/app.pcss",
"baseColor": "slate" "baseColor": "slate"
}, },

View file

@ -1,6 +1,8 @@
version: '3.8'
services: services:
postgres: postgres:
image: postgres:latest image: postgres:latest
container_name: boredgame_postgres
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
@ -11,6 +13,7 @@ services:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
redis: redis:
image: redis:latest image: redis:latest
container_name: boredgame_redis
ports: ports:
- '6379:6379' - '6379:6379'
volumes: volumes:
@ -23,18 +26,6 @@ services:
# - '3592:3592' # - '3592:3592'
# volumes: # volumes:
# - ./policies:/policies # - ./policies:/policies
# caddy:
# image: caddy:latest
# restart: unless-stopped
# ports:
# - "80:80"
# - "443:443"
# - "443:443/udp"
# volumes:
# - ./Caddyfile:/etc/caddy/Caddyfile
# - ./site:/srv
# - caddy_data:/data
# - caddy_config:/config
volumes: volumes:
postgres_data: postgres_data:
redis_data: redis_data:

View file

@ -116,6 +116,7 @@
"just-capitalize": "^3.2.0", "just-capitalize": "^3.2.0",
"just-kebab-case": "^4.2.0", "just-kebab-case": "^4.2.0",
"loader": "^2.1.1", "loader": "^2.1.1",
"mode-watcher": "^0.4.1",
"open-props": "^1.7.6", "open-props": "^1.7.6",
"oslo": "^1.2.1", "oslo": "^1.2.1",
"pg": "^8.13.0", "pg": "^8.13.0",
@ -124,8 +125,8 @@
"radix-svelte": "^0.9.0", "radix-svelte": "^0.9.0",
"rate-limit-redis": "^4.2.0", "rate-limit-redis": "^4.2.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"svelte-french-toast": "^1.2.0",
"svelte-lazy-loader": "^1.0.0", "svelte-lazy-loader": "^1.0.0",
"svelte-sonner": "^0.3.28",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwind-variants": "^0.2.1", "tailwind-variants": "^0.2.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

View file

@ -134,6 +134,9 @@ importers:
loader: loader:
specifier: ^2.1.1 specifier: ^2.1.1
version: 2.1.1 version: 2.1.1
mode-watcher:
specifier: ^0.4.1
version: 0.4.1(svelte@5.0.0-next.175)
open-props: open-props:
specifier: ^1.7.6 specifier: ^1.7.6
version: 1.7.6 version: 1.7.6
@ -158,12 +161,12 @@ importers:
reflect-metadata: reflect-metadata:
specifier: ^0.2.2 specifier: ^0.2.2
version: 0.2.2 version: 0.2.2
svelte-french-toast:
specifier: ^1.2.0
version: 1.2.0(svelte@5.0.0-next.175)
svelte-lazy-loader: svelte-lazy-loader:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0 version: 1.0.0
svelte-sonner:
specifier: ^0.3.28
version: 0.3.28(svelte@5.0.0-next.175)
tailwind-merge: tailwind-merge:
specifier: ^2.5.2 specifier: ^2.5.2
version: 2.5.2 version: 2.5.2
@ -3454,6 +3457,11 @@ packages:
mlly@1.7.1: mlly@1.7.1:
resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==}
mode-watcher@0.4.1:
resolution: {integrity: sha512-bNC+1NXmwEFZtziCdZSgP7HFQTpqJPcQn9GwwJQGSf6SBF3neEPYV1uRwkYuAQwbsvsXIYtzaqgedDzJ7D1mhg==}
peerDependencies:
svelte: ^4.0.0 || ^5.0.0-next.1
mri@1.2.0: mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -4369,11 +4377,6 @@ packages:
svelte: svelte:
optional: true optional: true
svelte-french-toast@1.2.0:
resolution: {integrity: sha512-5PW+6RFX3xQPbR44CngYAP1Sd9oCq9P2FOox4FZffzJuZI2mHOB7q5gJBVnOiLF5y3moVGZ7u2bYt7+yPAgcEQ==}
peerDependencies:
svelte: ^3.57.0 || ^4.0.0
svelte-headless-table@0.18.2: svelte-headless-table@0.18.2:
resolution: {integrity: sha512-dnDTaXW5CNzRUjHVbc/Hb0Zv80zU4VcIUnAja6OuZriXvim1AqcWYQCHPRzBGwqj1m3YEHHNvspSzY0o5HzA0A==} resolution: {integrity: sha512-dnDTaXW5CNzRUjHVbc/Hb0Zv80zU4VcIUnAja6OuZriXvim1AqcWYQCHPRzBGwqj1m3YEHHNvspSzY0o5HzA0A==}
peerDependencies: peerDependencies:
@ -4480,16 +4483,16 @@ packages:
resolution: {integrity: sha512-DIFm0kSNscVxtBmKkBiygAHB5otoqN1aVmJ3t57jZhJfCB7Np/lUSoTtSrvPFjmlBbMeOsb1VQ06cut1+rBYOg==} resolution: {integrity: sha512-DIFm0kSNscVxtBmKkBiygAHB5otoqN1aVmJ3t57jZhJfCB7Np/lUSoTtSrvPFjmlBbMeOsb1VQ06cut1+rBYOg==}
engines: {node: '>=16'} engines: {node: '>=16'}
svelte-sonner@0.3.28:
resolution: {integrity: sha512-K3AmlySeFifF/cKgsYNv5uXqMVNln0NBAacOYgmkQStLa/UoU0LhfAACU6Gr+YYC8bOCHdVmFNoKuDbMEsppJg==}
peerDependencies:
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0-next.1
svelte-subscribe@2.0.1: svelte-subscribe@2.0.1:
resolution: {integrity: sha512-eKXIjLxB4C7eQWPqKEdxcGfNXm2g/qJ67zmEZK/GigCZMfrTR3m7DPY93R6MX+5uoqM1FRYxl8LZ1oy4URWi2A==} resolution: {integrity: sha512-eKXIjLxB4C7eQWPqKEdxcGfNXm2g/qJ67zmEZK/GigCZMfrTR3m7DPY93R6MX+5uoqM1FRYxl8LZ1oy4URWi2A==}
peerDependencies: peerDependencies:
svelte: ^4.0.0 svelte: ^4.0.0
svelte-writable-derived@3.1.1:
resolution: {integrity: sha512-w4LR6/bYZEuCs7SGr+M54oipk/UQKtiMadyOhW0PTwAtJ/Ai12QS77sLngEcfBx2q4H8ZBQucc9ktSA5sUGZWw==}
peerDependencies:
svelte: ^3.2.1 || ^4.0.0-next.1 || ^5.0.0-next.94
svelte@4.2.19: svelte@4.2.19:
resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -7753,6 +7756,10 @@ snapshots:
pkg-types: 1.2.0 pkg-types: 1.2.0
ufo: 1.5.4 ufo: 1.5.4
mode-watcher@0.4.1(svelte@5.0.0-next.175):
dependencies:
svelte: 5.0.0-next.175
mri@1.2.0: {} mri@1.2.0: {}
mrmime@2.0.0: {} mrmime@2.0.0: {}
@ -8719,11 +8726,6 @@ snapshots:
optionalDependencies: optionalDependencies:
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
svelte-french-toast@1.2.0(svelte@5.0.0-next.175):
dependencies:
svelte: 5.0.0-next.175
svelte-writable-derived: 3.1.1(svelte@5.0.0-next.175)
svelte-headless-table@0.18.2(svelte@5.0.0-next.175): svelte-headless-table@0.18.2(svelte@5.0.0-next.175):
dependencies: dependencies:
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
@ -8781,11 +8783,11 @@ snapshots:
svelte: 4.2.19 svelte: 4.2.19
tslib: 2.7.0 tslib: 2.7.0
svelte-subscribe@2.0.1(svelte@5.0.0-next.175): svelte-sonner@0.3.28(svelte@5.0.0-next.175):
dependencies: dependencies:
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175
svelte-writable-derived@3.1.1(svelte@5.0.0-next.175): svelte-subscribe@2.0.1(svelte@5.0.0-next.175):
dependencies: dependencies:
svelte: 5.0.0-next.175 svelte: 5.0.0-next.175

17
src/app.d.ts vendored
View file

@ -21,17 +21,12 @@ declare global {
parseApiResponse: typeof parseApiResponse; parseApiResponse: typeof parseApiResponse;
getAuthedUser: () => Promise<Returned<User> | null>; getAuthedUser: () => Promise<Returned<User> | null>;
getAuthedUserOrThrow: () => Promise<Returned<User>>; getAuthedUserOrThrow: () => Promise<Returned<User>>;
auth: import('lucia').AuthRequest; }
user: import('lucia').User | null; namespace Superforms {
session: import('lucia').Session | null; type Message = {
startTimer: number; type: 'error' | 'success' | 'info',
ip: string; text: string
country: string; }
error: string;
errorId: string;
errorStackTrace: string;
message: unknown;
track: unknown;
} }
interface Error { interface Error {
code?: string; code?: string;

View file

@ -6,40 +6,11 @@
<meta name="description" content="Bored? Find a game! Bored Game!" /> <meta name="description" content="Bored? Find a game! Bored Game!" />
<link rel="icon" href="%sveltekit.assets%/favicon-bored-game.svg" /> <link rel="icon" href="%sveltekit.assets%/favicon-bored-game.svg" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<script> <noscript>Please enable JavaScript.</noscript>
// const htmlElement = document.documentElement;
// const userTheme = localStorage.theme;
// const userFont = localStorage.font;
// const prefersDarkMode = window.matchMedia('prefers-color-scheme: dark').matches;
// const prefersLightMode = window.matchMedia('prefers-color-scheme: light').matches;
// // check if the user set a theme
// if (userTheme) {
// htmlElement.dataset.theme = userTheme;
// }
// // otherwise check for user preference
// if (!userTheme && prefersDarkMode) {
// htmlElement.dataset.theme = '🌛 Night';
// localStorage.theme = '🌛 Night';
// }
// if (!userTheme && prefersLightMode) {
// htmlElement.dataset.theme = '☀️ Daylight';
// localStorage.theme = '☀️ Daylight';
// }
// // if nothing is set default to dark mode
// if (!userTheme && !prefersDarkMode && !prefersLightMode) {
// htmlElement.dataset.theme = '🌛 Night';
// localStorage.theme = '🌛 Night';
// }
</script>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body> <body data-sveltekit-preload-data="hover">
<div id="svelte">%sveltekit.body%</div> <div id="svelte" style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View file

@ -1,15 +1,14 @@
<script lang="ts"> <script lang="ts">
import { applyAction, enhance } from '$app/forms' import { applyAction, enhance } from '$app/forms';
import { invalidateAll } from '$app/navigation' import { invalidateAll } from '$app/navigation';
import Logo from '$components/logo.svelte' import Logo from '$components/logo.svelte';
import * as Avatar from '$components/ui/avatar' import * as Avatar from '$components/ui/avatar';
import * as DropdownMenu from '$components/ui/dropdown-menu' import * as DropdownMenu from '$components/ui/dropdown-menu';
import { ListChecks, ListTodo, LogOut, Settings } from 'lucide-svelte' import { ListChecks, ListTodo, LogOut, Settings } from 'lucide-svelte';
import toast from 'svelte-french-toast'
let { user = null } = $props() let { user = null } = $props();
let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)') let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)');
</script> </script>
<header> <header>
@ -63,26 +62,7 @@ let avatar: string = $derived(user?.username?.slice(0, 1).toUpperCase() || ':)')
</DropdownMenu.Item> </DropdownMenu.Item>
</a> </a>
<DropdownMenu.Item> <DropdownMenu.Item>
<form <form action="/logout" method="POST">
use:enhance={() => {
return async ({ result }) => {
console.log(result);
if (result.type === 'success' || result.type === 'redirect') {
toast.success('Logged Out');
} else if (result.type === 'error') {
console.log(result);
toast.error(`Error: ${result.error.message}`);
} else {
toast.error(`Something went wrong.`);
console.log(result);
}
await invalidateAll();
await applyAction(result);
};
}}
action="/logout"
method="POST"
>
<button type="submit"> <button type="submit">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<LogOut class="mr-2 h-4 w-4" /> <LogOut class="mr-2 h-4 w-4" />

View file

@ -0,0 +1 @@
export { default as Toaster } from "./sonner.svelte";

View file

@ -0,0 +1,20 @@
<script lang="ts">
import { Toaster as Sonner, type ToasterProps as SonnerProps } from "svelte-sonner";
import { mode } from "mode-watcher";
type $$Props = SonnerProps;
</script>
<Sonner
theme={$mode}
class="toaster group"
toastOptions={{
classes: {
toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton: "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...$$restProps}
/>

View file

@ -0,0 +1,20 @@
import { toast } from 'svelte-sonner'
import { message, type ErrorStatus, type SuperValidated } from 'sveltekit-superforms'
export type Message = {
type: 'error' | 'success' | 'info'
text: string
}
export function errorMessage(form: SuperValidated<any>, text: string | null, status: ErrorStatus = 500) {
return message(form, { text: text || 'Error', type: 'error' }, { status })
}
export function successMessage(form: SuperValidated<any>, text: string | null) {
return message(form, { text: text || 'Success', type: 'success' })
}
export function toastMessage(message: Message | undefined) {
if (!message) return
toast[message.type](message.text)
}

View file

@ -1,6 +1,7 @@
import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages' import { forbiddenMessage, notSignedInMessage } from '$lib/flashMessages'
import { user_roles } from '$lib/server/api/databases/tables' import { user_roles } from '$lib/server/api/databases/tables'
import { db } from '$lib/server/api/packages/drizzle' import { db } from '$lib/server/api/packages/drizzle'
import { errorMessage } from '$lib/utils/superforms'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
import { loadFlash, redirect } from 'sveltekit-flash-message/server' import { loadFlash, redirect } from 'sveltekit-flash-message/server'

View file

@ -3,7 +3,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { theme } from '$state/theme'; import { theme } from '$state/theme';
import toast, { Toaster } from 'svelte-french-toast'; import { toastMessage } from '$lib/utils/superforms.js';
const { data } = $props(); const { data } = $props();
const { user } = data; const { user } = data;
@ -19,29 +19,13 @@
$theme = user?.theme || 'system'; $theme = user?.theme || 'system';
document.querySelector('html')?.setAttribute('data-theme', $theme); 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
});
}
// Clearing the flash message could sometimes
// be required here to avoid double-toasting.
flash.set(undefined);
}
});
</script> </script>
<h1>Do the admin stuff</h1> <h1>Do the admin stuff</h1>
{@render children()} {@render children()}
<Toaster /> <!-- <Toaster /> -->
<style lang="postcss"> <style lang="postcss">
:global(main) { :global(main) {

View file

@ -10,7 +10,7 @@ const { data, children } = $props()
<Header user={data.authedUser} /> <Header user={data.authedUser} />
<main <main
class="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 p-4 md:gap-8 md:p-10" class="flex min-h-[calc(100vh-theme(spacing.16))] flex-1 flex-col gap-4 p-4 md:gap-8 md:p-10"
> >
{@render children()} {@render children()}
</main> </main>
@ -19,27 +19,4 @@ const { data, children } = $props()
</div> </div>
<style lang="postcss"> <style lang="postcss">
/*main {*/
/* flex: 1;*/
/* display: flex;*/
/* flex-direction: column;*/
/* max-width: 850px;*/
/* margin: 0 auto;*/
/* padding: 2rem 0rem;*/
/* max-width: 80vw;*/
/* @media (min-width: 1600px) {*/
/* max-width: 70vw;*/
/* }*/
/* box-sizing: border-box;*/
/*}*/
:global(.dialog-overlay) {
position: fixed;
inset: 0;
z-index: 100;
background-color: rgb(0 0 0);
opacity: 0.8;
}
</style> </style>

View file

@ -12,21 +12,11 @@ export const load: PageServerLoad = async (event) => {
const authedUser = await locals.getAuthedUser() const authedUser = await locals.getAuthedUser()
if (authedUser) { if (authedUser) {
console.log('user already signed in')
const message = { type: 'success', message: 'You are already signed in' } as const const message = { type: 'success', message: 'You are already signed in' } as const
throw redirect('/', message, event) throw redirect('/', message, event)
// redirect(302, '/', message, event)
} }
// if (userFullyAuthenticated(user, session)) {
// const message = { type: 'success', message: 'You are already signed in' } as const;
// throw redirect('/', message, event);
// } else if (userNotFullyAuthenticated(user, session)) {
// await lucia.invalidateSession(locals.session!.id!);
// const sessionCookie = lucia.createBlankSessionCookie();
// cookies.set(sessionCookie.name, sessionCookie.value, {
// path: '.',
// ...sessionCookie.attributes,
// });
// }
const form = await superValidate(event, zod(signinUsernameDto)) const form = await superValidate(event, zod(signinUsernameDto))
return { return {
@ -59,81 +49,6 @@ export const actions: Actions = {
}) })
} }
// let session;
// let sessionCookie;
// const user: Users | undefined = await db.query.usersTable.findFirst({
// where: or(eq(usersTable.username, form.data.username), eq(usersTable.email, form.data.username)),
// });
//
// if (!user) {
// form.data.password = '';
// return setError(form, 'username', 'Your username or password is incorrect.');
// }
//
// let twoFactorDetails;
//
try {
// const password = form.data.password;
// console.log('user', JSON.stringify(user, null, 2));
//
// if (!user?.hashed_password) {
// console.log('invalid username/password');
// form.data.password = '';
// return setError(form, 'password', 'Your username or password is incorrect.');
// }
//
// const validPassword = await new Argon2id().verify(user.hashed_password, password);
// if (!validPassword) {
// console.log('invalid password');
// form.data.password = '';
// return setError(form, 'password', 'Your username or password is incorrect.');
// }
//
// console.log('ip', locals.ip);
// console.log('country', locals.country);
//
// twoFactorDetails = await db.query.twoFactor.findFirst({
// where: eq(twoFactor.userId, user?.id),
// });
//
// if (twoFactorDetails?.secret && twoFactorDetails?.enabled) {
// await db.update(twoFactor).set({
// initiatedTime: new Date(),
// });
//
// session = await lucia.createSession(user.id, {
// ip_country: locals.country,
// ip_address: locals.ip,
// twoFactorAuthEnabled:
// twoFactorDetails?.enabled &&
// twoFactorDetails?.secret !== null &&
// twoFactorDetails?.secret !== '',
// isTwoFactorAuthenticated: false,
// });
// } else {
// session = await lucia.createSession(user.id, {
// ip_country: locals.country,
// ip_address: locals.ip,
// twoFactorAuthEnabled: false,
// isTwoFactorAuthenticated: false,
// });
// }
// console.log('logging in session', session);
// sessionCookie = lucia.createSessionCookie(session.id);
// console.log('logging in session cookie', sessionCookie);
} catch (e) {
// TODO: need to return error message to the client
console.error(e)
form.data.password = ''
return setError(form, '', 'Your username or password is incorrect.')
}
// console.log('setting session cookie', sessionCookie);
// event.cookies.set(sessionCookie.name, sessionCookie.value, {
// path: '.',
// ...sessionCookie.attributes,
// });
form.data.username = '' form.data.username = ''
form.data.password = '' form.data.password = ''

View file

@ -1,71 +1,67 @@
<script lang="ts"> <script lang="ts">
import '$lib/styles/app.pcss' import '$lib/styles/app.pcss';
import { onMount } from 'svelte' import { onMount } from 'svelte';
import toast, { Toaster } from 'svelte-french-toast' import { MetaTags } from 'svelte-meta-tags';
import { MetaTags } from 'svelte-meta-tags' import { getFlash } from 'sveltekit-flash-message/client';
import { getFlash } from 'sveltekit-flash-message/client' import 'iconify-icon';
import 'iconify-icon' import { ModeWatcher } from 'mode-watcher';
import { onNavigate } from '$app/navigation' import { Toaster } from '$lib/components/ui/sonner';
import { page } from '$app/stores' import { onNavigate } from '$app/navigation';
import Analytics from '$components/Analytics.svelte' import { page } from '$app/stores';
import PageLoadingIndicator from '$lib/page_loading_indicator.svelte' import Analytics from '$components/Analytics.svelte';
import { theme } from '$state/theme' import PageLoadingIndicator from '$lib/page_loading_indicator.svelte';
import { theme } from '$state/theme';
import { toastMessage } from '$lib/utils/superforms.js';
const dev = process.env.NODE_ENV !== 'production' const dev = process.env.NODE_ENV !== 'production';
const { data, children } = $props() const { data, children } = $props();
const { user } = data const { user } = data;
const metaTags = $derived({ const metaTags = $derived({
titleTemplate: '%s | Bored Game',
description: 'Bored Game, keep track of your gamesTable.',
openGraph: {
type: 'website',
titleTemplate: '%s | Bored Game', titleTemplate: '%s | Bored Game',
locale: 'en_US', description: 'Bored Game, keep track of your gamesTable.',
description: 'Bored Game, keep track of your gamesTable', openGraph: {
}, type: 'website',
...$page.data.metaTagsChild, titleTemplate: '%s | Bored Game',
}) locale: 'en_US',
description: 'Bored Game, keep track of your gamesTable',
},
...$page.data.metaTagsChild,
});
const flash = getFlash(page, { const flash = getFlash(page, {
clearOnNavigate: true, clearOnNavigate: true,
clearAfterMs: 3000, clearAfterMs: 3000,
clearArray: true, clearArray: true,
}) });
onMount(() => { onMount(() => {
// set the theme to the user's active theme // set the theme to the user's active theme
$theme = user?.theme || 'system' $theme = user?.theme || 'system';
document.querySelector('html')?.setAttribute('data-theme', $theme) document.querySelector('html')?.setAttribute('data-theme', $theme);
}) });
$effect(() => { $effect(() => {
if ($flash) { console.log('flash', $flash);
if ($flash.type === 'success') { if ($flash) {
toast.success($flash.message) toastMessage({ type: $flash.type, text: $flash.message });
} else { // Clearing the flash message could sometimes
toast.error($flash.message, { // be required here to avoid double-toasting.
duration: 5000, flash.set(undefined);
})
} }
});
// Clearing the flash message could sometimes onNavigate(async (navigation) => {
// be required here to avoid double-toasting. if (!document.startViewTransition) return;
flash.set(undefined)
}
})
onNavigate(async (navigation) => { return new Promise((oldStateCaptureResolve) => {
if (!document.startViewTransition) return document.startViewTransition(async () => {
oldStateCaptureResolve();
return new Promise((oldStateCaptureResolve) => { await navigation.complete;
document.startViewTransition(async () => { });
oldStateCaptureResolve() });
await navigation.complete });
})
})
})
</script> </script>
{#if !dev} {#if !dev}
@ -74,5 +70,6 @@ onNavigate(async (navigation) => {
<MetaTags {...metaTags} /> <MetaTags {...metaTags} />
<PageLoadingIndicator /> <PageLoadingIndicator />
{@render children()} <!-- <ModeWatcher /> -->
<Toaster /> <Toaster />
{@render children()}

View file

@ -1,65 +0,0 @@
import { fontFamily } from "tailwindcss/defaultTheme";
import tailwindcssAnimate from "tailwindcss-animate";
/** @type {import('tailwindcss').Config} */
const config = {
darkMode: ["class"],
content: ["./src/**/*.{html,js,svelte,ts}"],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px"
}
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))"
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))"
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))"
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))"
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))"
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))"
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))"
}
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)"
},
fontFamily: {
sans: [...fontFamily.sans]
}
}
},
plugins: [tailwindcssAnimate]
};
export default config;

69
tailwind.config.ts Normal file
View file

@ -0,0 +1,69 @@
import { fontFamily } from 'tailwindcss/defaultTheme'
import type { Config } from 'tailwindcss'
import tailwindcssAnimate from 'tailwindcss-animate'
const config: Config = {
darkMode: ['class'],
content: ['./src/**/*.{html,js,svelte,ts}'],
safelist: ['dark'],
theme: {
spacing: {
'16': '4rem',
},
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
fontFamily: {
sans: [...fontFamily.sans],
},
},
},
plugins: [tailwindcssAnimate],
}
export default config