Rename db folder to server, remove levelup dependencies, add sveltekit flash message, add toast library, and use both on signup.

This commit is contained in:
Bradley Shellnut 2023-07-30 16:31:39 -07:00
parent a62bd44279
commit a32adc3ae8
18 changed files with 188 additions and 129 deletions

View file

@ -67,8 +67,6 @@
"@fontsource/fira-mono": "^4.5.10",
"@iconify-icons/line-md": "^1.2.23",
"@iconify-icons/mdi": "^1.2.47",
"@leveluptuts/svelte-side-menu": "^1.0.5",
"@leveluptuts/svelte-toy": "^2.0.3",
"@lucia-auth/adapter-mysql": "^1.1.1",
"@lucia-auth/adapter-prisma": "^3.0.0",
"@lukeed/uuid": "^2.0.1",
@ -85,6 +83,7 @@
"lucide-svelte": "^0.256.1",
"open-props": "^1.5.10",
"radix-svelte": "^0.8.0",
"svelte-french-toast": "^1.2.0",
"svelte-lazy": "^1.2.1",
"svelte-lazy-loader": "^1.0.0",
"svelte-legos": "^0.2.1",

View file

@ -17,12 +17,6 @@ dependencies:
'@iconify-icons/mdi':
specifier: ^1.2.47
version: 1.2.47
'@leveluptuts/svelte-side-menu':
specifier: ^1.0.5
version: 1.0.5
'@leveluptuts/svelte-toy':
specifier: ^2.0.3
version: 2.0.3
'@lucia-auth/adapter-mysql':
specifier: ^1.1.1
version: 1.1.1(lucia-auth@1.8.0)
@ -71,6 +65,9 @@ dependencies:
radix-svelte:
specifier: ^0.8.0
version: 0.8.0(svelte@4.1.1)
svelte-french-toast:
specifier: ^1.2.0
version: 1.2.0(svelte@4.1.1)
svelte-lazy:
specifier: ^1.2.1
version: 1.2.1(svelte@4.1.1)
@ -1143,16 +1140,6 @@ packages:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.15
/@leveluptuts/svelte-side-menu@1.0.5:
resolution: {integrity: sha512-czPmr0LEjVhr7qXYZtH4PrUrfHPYg9nS7ZHH+xDINKoajkERWlHlsBtdoJC5ZTMzGvdhLCLfF70q4xeMzJgS7w==}
dev: false
/@leveluptuts/svelte-toy@2.0.3:
resolution: {integrity: sha512-A2pjSG4UQbWLffD3r3cC8zaNwtTmBoNJJ2TIlNzVtdlUXk1xrYtscMopB+N2ztHnfjlKWyIee4TEnF3OBVwIfQ==}
dependencies:
lodash.set: 4.3.2
dev: false
/@lucia-auth/adapter-mysql@1.1.1(lucia-auth@1.8.0):
resolution: {integrity: sha512-br+/OBDNJ+eRc6RrZnnC20ef+2VEMrXFxNYvsbryPw64ito7vg40STblpENdjJF0o4R10mjWTO43wQ+56jyXLA==}
peerDependencies:
@ -2719,10 +2706,6 @@ packages:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash.set@4.3.2:
resolution: {integrity: sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==}
dev: false
/logform@2.5.1:
resolution: {integrity: sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==}
dependencies:
@ -3857,6 +3840,15 @@ packages:
svelte: 4.1.1
dev: true
/svelte-french-toast@1.2.0(svelte@4.1.1):
resolution: {integrity: sha512-5PW+6RFX3xQPbR44CngYAP1Sd9oCq9P2FOox4FZffzJuZI2mHOB7q5gJBVnOiLF5y3moVGZ7u2bYt7+yPAgcEQ==}
peerDependencies:
svelte: ^3.57.0 || ^4.0.0
dependencies:
svelte: 4.1.1
svelte-writable-derived: 3.1.0(svelte@4.1.1)
dev: false
/svelte-hmr@0.15.2(svelte@4.1.1):
resolution: {integrity: sha512-q/bAruCvFLwvNbeE1x3n37TYFb3mTBJ6TrCq6p2CoFbSTNhDE9oAtEfpy+wmc9So8AG0Tja+X0/mJzX9tSfvIg==}
engines: {node: ^12.20 || ^14.13.1 || >= 16}
@ -3939,6 +3931,14 @@ packages:
typescript: 5.1.6
dev: true
/svelte-writable-derived@3.1.0(svelte@4.1.1):
resolution: {integrity: sha512-cTvaVFNIJ036vSDIyPxJYivKC7ZLtcFOPm1Iq6qWBDo1fOHzfk6ZSbwaKrxhjgy52Rbl5IHzRcWgos6Zqn9/rg==}
peerDependencies:
svelte: ^3.2.1 || ^4.0.0-next.1
dependencies:
svelte: 4.1.1
dev: false
/svelte@4.1.1:
resolution: {integrity: sha512-Enick5fPFISLoVy0MFK45cG+YlQt6upw8skEK9zzTpJnH1DqEv8xOZwizCGSo3Q6HZ7KrZTM0J18poF7aQg5zw==}
engines: {node: '>=16'}

2
src/app.d.ts vendored
View file

@ -10,7 +10,7 @@ type User = Omit<User, 'created_at' | 'updated_at'>;
declare global {
namespace App {
interface PageData {
flash?: { type: 'success' | 'error'; message: string };
flash?: { type: 'success' | 'error' | 'info'; message: string };
}
interface Locals {
auth: import('lucia').AuthRequest;

View file

@ -19,6 +19,9 @@
import RemoveCollectionDialog from '../../dialog/RemoveCollectionDialog.svelte';
import RemoveWishlistDialog from '../../dialog/RemoveWishlistDialog.svelte';
import type { ListGameSchema, SearchSchema } from '$lib/zodValidation';
import { Label } from '$components/ui/label';
import { Input } from '$components/ui/input';
import { Button } from '$components/ui/button';
interface RemoveGameEvent extends Event {
detail: GameType | SavedGameType;
@ -147,22 +150,11 @@
<form id="search-form" action="/search" method="GET">
<div class="search">
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}>
<label class="label" for="q">
<span>Search</span>
<input
id="q"
class="input"
name="q"
bind:value={$form.q}
data-invalid={$errors?.q}
{...$constraints.q}
type="search"
aria-label="Search board games"
placeholder="Search board games"
/>
</label>
{#if $errors?.q}<span class="invalid">{$errors?.q}</span>{/if}
<Label for="label">Search</Label>
<Input type="text" id="q" class={$errors.q && "outline outline-destructive"} name="search" placeholder="Search board games" data-invalid={$errors.q} bind:value={$form.q} />
{#if $errors.q}
<p class="text-sm text-destructive">{$errors.q}</p>
{/if}
<input id="skip" type="hidden" name="skip" bind:value={$form.skip} />
<input id="limit" type="hidden" name="limit" bind:value={$form.limit} />
</fieldset>
@ -196,15 +188,7 @@
{/if}
</div>
{#if showButton}
<button
id="search-submit"
class="btn"
type="submit"
disabled={submitting}
bind:this={submitButton}
>
Submit
</button>
<Button type="submit">Submit</Button>
{/if}
</form>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { fly, fade } from 'svelte/transition';
import { flip } from 'svelte/animate';
import Portal from '../../Portal.svelte';
import Portal from '$lib/Portal.svelte';
import ToastMessage from './ToastMessage.svelte';
import { toast } from './toast';
</script>
@ -10,6 +10,8 @@
<div class="toast-wrapper">
{#each $toast as toastData (toastData.id)}
<div
role="button"
tabindex="0"
aria-label={toastData.dismissible ? 'Click to dismiss' : `${toastData.message}`}
on:click={() => toastData.dismissible && toast.remove(toastData.id)}
on:keydown={() => toastData.dismissible && toast.remove(toastData.id)}

View file

@ -1,6 +1,8 @@
import { Prisma } from '@prisma/client';
import type { SvelteComponent } from 'svelte';
export type Message = { status: 'error' | 'success' | 'warning' | 'info'; text: string };
export const gameInclude = Prisma.validator<Prisma.CollectionItemInclude>()({
game: {
select: {

View file

@ -1,11 +1,9 @@
import { loadFlash } from 'sveltekit-flash-message/server';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ url, locals }) => {
export const load: LayoutServerLoad = loadFlash(async ({ url, locals }) => {
return {
url: url.pathname,
user: locals.user
};
};
// loadFlash(
});

View file

@ -1,11 +1,11 @@
<script lang="ts">
import "../app.postcss";
import { onMount } from "svelte";
// import { getFlash } from 'sveltekit-flash-message/client';
import { getFlash } from 'sveltekit-flash-message/client';
import toast, { Toaster } from 'svelte-french-toast';
import { navigating, page } from '$app/stores';
import { browser } from '$app/environment';
import debounce from 'just-debounce-it';
// import { Toy } from '@leveluptuts/svelte-toy';
import 'iconify-icon';
import Analytics from '$lib/components/analytics.svelte';
import Header from '$lib/components/header/index.svelte';
@ -16,9 +16,7 @@
import { boredState } from '$lib/stores/boredState';
import { collectionStore } from '$lib/stores/collectionStore';
import { wishlistStore } from '$lib/stores/wishlistStore';
import Toast from '$lib/components/toast/Toast.svelte';
import { theme } from '$state/theme';
// import '$styles/styles.pcss';
import type { SavedGameType } from '$lib/types';
$: {
@ -33,7 +31,6 @@
}
$: isOpen = $boredState?.dialog?.isOpen;
// const flash = getFlash(page);
if (browser) {
const collator = new Intl.Collator('en');
@ -74,6 +71,27 @@
export let data;
$: ({ user } = data);
const flash = getFlash(page);
let flashType;
let flashMessage;
$: flashType = $flash?.type;
$: flashMessage = $flash?.message;
console.log('flashType', flashType);
console.log('flashMessage', flashMessage);
// if ($flash && flashType && flashMessage) {
// switch (flashType) {
// case 'success':
// toast.success(flashMessage);
// break;
// case 'error':
// toast.error(flashMessage);
// break;
// default:
// toast.error(flashMessage);
// }
// }
onMount(() => {
// set the theme to the user's active theme
$theme = user?.theme || 'system';
@ -85,27 +103,29 @@
<Analytics />
{/if}
<!-- {#if dev}
<Toy
register={{
boredState,
collectionStore,
wishlistStore,
gameStore,
toast
}}
/>
{/if} -->
<div class="wrapper">
<Header user="{data.user}"></Header>
<Header user={data.user} />
<main>
<Transition url={data.url} transition={{ type: 'page' }}>
<slot></slot>
<slot />
</Transition>
</main>
<Footer />
</div>
{#if $flash}
<div class="status"
class:error={$flash.type == 'error'}
class:success={$flash.type == 'success'}
>
{$flash.message}
</div>
{/if}
<Toaster />
{#if $boredState?.loading}
<Portal>
<div class="loading">
@ -120,13 +140,16 @@
<svelte:component this={$boredState?.dialog?.content}></svelte:component>
</div>
{/if}
<Toast></Toast>
<!-- {#if $flash}
{@const bg = $flash.type == 'success' ? '#3D9970' : '#FF4136'}
<div style:background-color={bg} class="flash">{$flash.message}</div>
{/if} -->
<style lang="postcss">
.flash {
display: inline-block;
position: absolute;
place-items: center;
padding: 0.5rem;
border-radius: 2px;
}
.loading {
position: fixed;
top: 50%;

View file

@ -1,5 +1,6 @@
import { fail, redirect } from '@sveltejs/kit';
import { fail } from '@sveltejs/kit';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import { auth } from '$lib/server/lucia';
import prisma from '$lib/prisma.js';
import { userSchema } from '$lib/config/zod-schemas';
@ -13,7 +14,8 @@ export const load = async (event) => {
console.log('sign in load event', event);
const session = await event.locals.auth.validate();
if (session) {
throw redirect(302, '/');
const message = { type: 'info', message: 'You are already signed in' };
throw redirect('/', message, event);
}
const form = await superValidate(event, signInSchema);
return {
@ -40,9 +42,9 @@ export const actions = {
});
event.locals.auth.setSession(session);
const user = await prisma.authUser.findUnique({
const user = await prisma.user.findUnique({
where: {
id: session.userId
id: session.user.userId
},
include: {
roles: {
@ -86,6 +88,8 @@ export const actions = {
}
form.data.username = '';
form.data.password = '';
return { form };
const message = { type: 'success', message: 'Signed In!' };
// return { form, message };
throw redirect('/', message, event);
}
};

View file

@ -1,5 +1,6 @@
<script lang="ts">
import { superForm } from 'sveltekit-superforms/client';
import * as flashModule from 'sveltekit-flash-message/client';
import { AlertCircle } from "lucide-svelte";
import { userSchema } from '$lib/config/zod-schemas.js';
import Label from '$components/ui/label/Label.svelte';
@ -9,6 +10,17 @@
export let data;
const { form, errors, enhance, delayed } = superForm(data.form, {
flashMessage: {
module: flashModule,
onError: ({ result, message }) => {
// Error handling for the flash message:
// - result is the ActionResult
// - message is the flash store (not the status message store)
const errorMessage = result.error.message
message.set({ type: 'error', message: errorMessage });
}
},
syncFlashMessage: false,
taintedMessage: null,
validationMethod: 'oninput',
delayMs: 0,

View file

@ -1,11 +1,13 @@
import { fail, redirect } from '@sveltejs/kit';
import { fail, redirect, error } from '@sveltejs/kit';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect as flashRedirect } from 'sveltekit-flash-message/server';
import { LuciaError } from 'lucia';
import { auth } from '$lib/server/lucia';
import { userSchema } from '$lib/config/zod-schemas';
import { add_user_to_role } from '$db/roles';
import { add_user_to_role } from '$server/roles';
import prisma from '$lib/prisma.js';
import { Schema } from 'zod';
import type { Message } from '$lib/types.js';
const signUpSchema = userSchema
.pick({
@ -38,7 +40,7 @@ export const load = async (event) => {
if (session) {
throw redirect(302, '/');
}
const form = await superValidate(event, signUpSchema);
const form = await superValidate<typeof signUpSchema, Message>(event, signUpSchema);
return {
form
};
@ -46,7 +48,7 @@ export const load = async (event) => {
export const actions = {
default: async (event) => {
const form = await superValidate(event, signUpSchema);
const form = await superValidate<typeof signUpSchema, Message>(event, signUpSchema);
if (!form.valid) {
return fail(400, {
@ -99,13 +101,18 @@ export const actions = {
event.locals.auth.setSession(session);
// const message = { type: 'success', message: 'Signed Up!' } as const;
// throw flashRedirect(message, event);
} catch (error) {
if (error instanceof LuciaError && error.message === `DUPLICATE_KEY_ID`) {
} catch (e) {
if (e instanceof LuciaError && e.message === `DUPLICATE_KEY_ID`) {
// key already exists
console.error(error);
console.error('Lucia Error: ', e);
}
console.log(error);
return setError(form, '', 'Unable to create your account. Please try again.');
console.log(e);
const message = {
type: 'error',
message: 'Unable to create your account. Please try again.'
};
throw error(500, message);
// return setError(form, '', message);
}
}
};

View file

@ -1,9 +1,12 @@
<script lang="ts">
import { page } from '$app/stores';
import { superForm } from 'sveltekit-superforms/client';
import * as flashModule from 'sveltekit-flash-message/client';
import Button from '$components/ui/button/Button.svelte';
import Input from '$components/ui/input/Input.svelte';
import Label from '$components/ui/label/Label.svelte';
import { userSchema } from '$lib/config/zod-schemas.js';
import { superForm } from 'sveltekit-superforms/client';
import toast from 'svelte-french-toast';
export let data;
@ -17,12 +20,35 @@ import { userSchema } from '$lib/config/zod-schemas.js';
});
const { form, errors, constraints, enhance, delayed } = superForm(data.form, {
flashMessage: {
module: flashModule,
onError: ({ result, message }) => {
const errorMessage = result.error.message;
message.set({ type: 'error', message: errorMessage });
}
},
taintedMessage: null,
validators: signUpSchema,
delayMs: 0,
});
const flash = flashModule.getFlash(page);
$: {
if ($flash) {
toast.error($flash.message, {
duration: 5000
});
}
}
</script>
<!-- {#if $flash}
{@const bg = $flash.type == 'success' ? '#3D9970' : '#FF4136'}
<div style:background-color={bg} class="flash">{$flash.message}</div>
{/if} -->
<div class="page">
<form method="POST" action="/auth/signup" use:enhance>
<div class="grid w-full max-w-sm items-center gap-2.5">

View file

@ -5,6 +5,8 @@
import { superForm } from 'sveltekit-superforms/client';
import type { SuperValidated } from 'sveltekit-superforms';
import type { ModifyListGame } from '$lib/config/zod-schemas.js';
import { onMount } from 'svelte';
import toast from 'svelte-french-toast';
// import { collectionStore } from '$lib/stores/collectionStore';
// import type { GameType, SavedGameType } from '$lib/types';
// import { boredState } from '$lib/stores/boredState';

View file

@ -14,7 +14,7 @@ const config = {
$assets: './src/assets',
$components: './src/components',
'$components/*': 'src/lib/components/*',
$db: './src/db',
$server: './src/server',
$lib: './src/lib',
$state: './src/state',
$styles: './src/styles',