feat: 🎨 Move routes around, add auth layout, move postcss file

This commit is contained in:
Bradley Shellnut 2023-09-11 15:15:53 -07:00
parent 13162f8270
commit 485615b5bb
31 changed files with 420 additions and 240 deletions

View file

@ -1,13 +1,13 @@
{ {
"$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.js",
"css": "src/app.postcss", "css": "src/lib/styles/app.postcss",
"baseColor": "slate" "baseColor": "slate"
}, },
"aliases": { "aliases": {
"components": "$lib/components", "components": "$lib/components",
"utils": "$lib/utils" "utils": "$lib/utils"
} }
} }

View file

@ -1,6 +1,6 @@
import { error, fail, redirect } from '@sveltejs/kit'; import { error, fail, redirect } from '@sveltejs/kit';
import { setError, superValidate } from 'sveltekit-superforms/server'; import { setError, superValidate } from 'sveltekit-superforms/server';
import type { PageServerLoad } from '../../$types.js'; import type { PageServerLoad } from '../../../$types.js';
import prisma from '$lib/prisma.js'; import prisma from '$lib/prisma.js';
import { modifyListGameSchema, type ListGame } from '$lib/config/zod-schemas.js'; import { modifyListGameSchema, type ListGame } from '$lib/config/zod-schemas.js';
import type { CollectionItemWithGame } from '$lib/types.js'; import type { CollectionItemWithGame } from '$lib/types.js';

View file

@ -0,0 +1,213 @@
<script lang="ts">
// import "../app.postcss";
import { onMount } from "svelte";
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 'iconify-icon';
import Transition from '$lib/components/transition/index.svelte';
import Analytics from '$lib/components/analytics.svelte';
import Header from '$lib/components/header/index.svelte';
import Footer from '$lib/components/footer.svelte';
import Loading from '$lib/components/loading.svelte';
import Portal from '$lib/Portal.svelte';
import { boredState } from '$lib/stores/boredState';
import { collectionStore } from '$lib/stores/collectionStore';
import { wishlistStore } from '$lib/stores/wishlistStore';
import { theme } from '$state/theme';
import type { SavedGameType } from '$lib/types';
$: {
if ($navigating) {
debounce(() => {
boredState.update((n) => ({ ...n, loading: true }));
}, 250);
}
if (!$navigating) {
boredState.update((n) => ({ ...n, loading: false }));
}
}
$: isOpen = $boredState?.dialog?.isOpen;
if (browser) {
const collator = new Intl.Collator('en');
let collectionEmpty = $collectionStore.length === 0 || false;
let wishlistEmpty = $wishlistStore.length === 0 || false;
if (wishlistEmpty && localStorage?.wishlist && localStorage?.wishlist?.length !== 0) {
const wishlist: SavedGameType[] = JSON.parse(localStorage.wishlist);
if (wishlist?.length !== 0) {
wishlist.sort((a, b) => collator.compare(a.name, b.name));
for (const item of wishlist) {
if (!item?.searchTerms) {
item.searchTerms = `${item?.name?.toLowerCase()}`;
}
if (!item?.includeInRandom) {
item.includeInRandom = false;
}
}
wishlistStore.addAll(wishlist);
}
}
if (collectionEmpty && localStorage?.collection && localStorage?.collection?.length !== 0) {
const collection: SavedGameType[] = JSON.parse(localStorage.collection);
if (collection?.length !== 0) {
collection.sort((a, b) => collator.compare(a.name, b.name));
for (const item of collection) {
if (!item?.searchTerms) {
item.searchTerms = `${item?.name?.toLowerCase()}`;
}
}
collectionStore.addAll(collection);
}
}
}
const dev = process.env.NODE_ENV !== 'production';
export let data;
$: ({ user } = data);
const flash = getFlash(page, {
clearAfterMs: 6000
});
let flashType;
let flashMessage;
$: flashType = $flash?.type;
$: flashMessage = $flash?.message;
// 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';
document.querySelector('html')?.setAttribute('data-theme', $theme);
});
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);
});
</script>
{#if !dev}
<Analytics />
{/if}
<div class="wrapper">
<Header user={data.user} />
<main>
<Transition url={data.url} transition={{ type: 'page' }}>
<slot />
</Transition>
</main>
<Footer />
</div>
<Toaster />
{#if $boredState?.loading}
<Portal>
<div class="loading">
<Loading></Loading>
<h3>Loading...</h3>
</div>
<div class="background"></div>
</Portal>
{/if}
{#if isOpen}
<div class="container">
<svelte:component this={$boredState?.dialog?.content}></svelte:component>
</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%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 101;
display: grid;
place-items: center;
gap: 1rem;
& h3 {
color: white;
}
}
.background {
background: black;
opacity: 0.8;
cursor: none;
inset: 0;
position: fixed;
z-index: 100;
}
.wrapper {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
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>

View file

@ -66,7 +66,7 @@
<AddToList {in_collection} {in_wishlist} game_id={game.id} {wishlist} {collection} /> <AddToList {in_collection} {in_wishlist} game_id={game.id} {wishlist} {collection} />
{:else} {:else}
<span> <span>
<Button href="/sign-up">Sign Up</Button> or <Button href="/login">Sign In</Button> to add to a list. <Button href="/sign-up">Sign Up</Button> or <Button href="/login">Login</Button> to add to a list.
</span> </span>
{/if} {/if}
</div> </div>

View file

@ -0,0 +1,156 @@
<script lang="ts">
import { page } from "$app/stores";
import { Button } from "$lib/components/ui/button";
import Logo from "$lib/components/logo.svelte";
$: link = {
label: $page.url.pathname === "/sign-up" ? "Login" : "Sign up",
href: $page.url.pathname === "/sign-up" ? "/login" : "/sign-up"
};
$: console.log($page.url.pathname);
</script>
<div class="container">
<Button
href={$page.url.pathname === "/sign-up" ? "/login" : "/sign-up"}
variant="ghost"
class="auth-button"
>
{#if $page.url.pathname === "/sign-up"}
Login
{:else}
Sign up
{/if}
</Button>
<div class="auth-marketing">
<div
class="image"
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"
/>
<a href="/" class="logo">
<div class="logo-image">
<Logo />
</div>
Bored Game
</a>
<div class="quote-wrapper">
<blockquote class="quote">
<p>
"How many games do I own? What was the last one I played? What haven't I played in a long time? If this sound like you then Bored Game is your new best friend."
</p>
<footer>Bradley</footer>
</blockquote>
</div>
</div>
<div class="auth-form">
<slot />
</div>
</div>
<style lang="postcss">
.container {
display: none;
position: relative;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
min-height: 100vh;
@media (min-width: 768px) {
display: grid
}
@media (min-width: 1024px) {
padding-left: 0;
padding-right: 0;
grid-template-columns: repeat(2, minmax(0, 1fr));
max-width: none;
}
}
:global(.auth-button) {
position: absolute;
top: 1rem;
right: 1rem;
@media (min-width: 768px) {
top: 2rem;
right: 2rem;
}
}
.auth-marketing {
display: none;
position: relative;
padding: 2.5rem;
flex-direction: column;
height: 100%;
color: #ffffff;
@media (min-width: 1024px) {
display: flex;
}
.image {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-size: cover;
}
.logo {
display: flex;
position: relative;
z-index: 20;
gap: 0.5rem;
align-items: center;
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 500;
transition-property: color, background-color, border-color,text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
&:hover {
color: hsl(var(--muted-foreground));
}
.logo-image {
width: 2rem;
height: 2rem;
}
}
.quote-wrapper {
position: relative;
z-index: 20;
margin-top: auto;
.quote {
margin-top: 0.5rem;
p {
font-size: 1.125rem;
line-height: 1.75rem;
}
}
.footer {
font-size: 0.875rem;
line-height: 1.25rem;
}
}
}
.auth-form {
@media (min-width: 1024px) {
padding: 2rem;
}
}
</style>

View file

@ -45,19 +45,17 @@
</svelte:head> </svelte:head>
<div class="login"> <div class="login">
<form method="POST" use:enhance> <form method="POST" use:enhance class="grid max-w-sm items-center gap-2">
<div class="grid w-full max-w-sm items-center gap-2"> <h2
<h2 class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0"
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0" >
> Log into your account
Log into your account </h2>
</h2> <Label for="username">Username</Label>
<Label for="username">Username</Label> <Input type="text" id="username" name="username" placeholder="Username" autocomplete="username" data-invalid={$errors.username} bind:value={$form.username} required />
<Input type="text" id="username" name="username" placeholder="Username" autocomplete="username" data-invalid={$errors.username} bind:value={$form.username} required /> <Label for="password">Password</Label>
<Label for="password">Password</Label> <Input type="password" id="password" name="password" placeholder="Password" autocomplete="password" data-invalid={$errors.password} bind:value={$form.password} required />
<Input type="password" id="password" name="password" placeholder="Password" autocomplete="password" data-invalid={$errors.password} bind:value={$form.password} required /> <Button type="submit">Login</Button>
<Button type="submit">Sign In</Button>
</div>
</form> </form>
{#if $errors._errors} {#if $errors._errors}
<Alert.Root variant="destructive"> <Alert.Root variant="destructive">
@ -68,11 +66,23 @@
</Alert.Description> </Alert.Description>
</Alert.Root> </Alert.Root>
{/if} {/if}
<p class="px-8 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our
<a href="/terms-of-service" class="underline underline-offset-4 hover:text-primary">
Terms of Service
</a>
and
<a href="/privacy" class="underline underline-offset-4 hover:text-primary"> Privacy Policy </a>
.
</p>
</div> </div>
<style lang="postcss"> <style lang="postcss">
.login { .login {
display: grid; display: grid;
gap: 2rem; width: 100%;
max-width: 24rem;
margin-left: auto;
margin-right: auto;
} }
</style> </style>

View file

@ -43,7 +43,7 @@
<title>Bored Game | Sign Up</title> <title>Bored Game | Sign Up</title>
</svelte:head> </svelte:head>
<div class="page"> <div class="page sign-up">
<form method="POST" action="/sign-up" use:enhance> <form method="POST" action="/sign-up" use:enhance>
<div class="grid w-full max-w-sm items-center gap-2.5"> <div class="grid w-full max-w-sm items-center gap-2.5">
<h2 <h2
@ -120,5 +120,12 @@
</form> </form>
</div> </div>
<style scoped> <style lang="postcss">
.sign-up {
display: grid;
width: 100%;
max-width: 24rem;
margin-left: auto;
margin-right: auto;
}
</style> </style>

View file

@ -1,213 +1,7 @@
<script lang="ts"> <script lang="ts">
import "../app.postcss"; import "$lib/styles/app.postcss";
import { onMount } from "svelte";
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 'iconify-icon';
import Transition from '$lib/components/transition/index.svelte';
import Analytics from '$lib/components/analytics.svelte';
import Header from '$lib/components/header/index.svelte';
import Footer from '$lib/components/footer.svelte';
import Loading from '$lib/components/loading.svelte';
import Portal from '$lib/Portal.svelte';
import { boredState } from '$lib/stores/boredState';
import { collectionStore } from '$lib/stores/collectionStore';
import { wishlistStore } from '$lib/stores/wishlistStore';
import { theme } from '$state/theme';
import type { SavedGameType } from '$lib/types';
$: {
if ($navigating) {
debounce(() => {
boredState.update((n) => ({ ...n, loading: true }));
}, 250);
}
if (!$navigating) {
boredState.update((n) => ({ ...n, loading: false }));
}
}
$: isOpen = $boredState?.dialog?.isOpen;
if (browser) {
const collator = new Intl.Collator('en');
let collectionEmpty = $collectionStore.length === 0 || false;
let wishlistEmpty = $wishlistStore.length === 0 || false;
if (wishlistEmpty && localStorage?.wishlist && localStorage?.wishlist?.length !== 0) {
const wishlist: SavedGameType[] = JSON.parse(localStorage.wishlist);
if (wishlist?.length !== 0) {
wishlist.sort((a, b) => collator.compare(a.name, b.name));
for (const item of wishlist) {
if (!item?.searchTerms) {
item.searchTerms = `${item?.name?.toLowerCase()}`;
}
if (!item?.includeInRandom) {
item.includeInRandom = false;
}
}
wishlistStore.addAll(wishlist);
}
}
if (collectionEmpty && localStorage?.collection && localStorage?.collection?.length !== 0) {
const collection: SavedGameType[] = JSON.parse(localStorage.collection);
if (collection?.length !== 0) {
collection.sort((a, b) => collator.compare(a.name, b.name));
for (const item of collection) {
if (!item?.searchTerms) {
item.searchTerms = `${item?.name?.toLowerCase()}`;
}
}
collectionStore.addAll(collection);
}
}
}
const dev = process.env.NODE_ENV !== 'production';
export let data;
$: ({ user } = data);
const flash = getFlash(page, {
clearAfterMs: 6000
});
let flashType;
let flashMessage;
$: flashType = $flash?.type;
$: flashMessage = $flash?.message;
// 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';
document.querySelector('html')?.setAttribute('data-theme', $theme);
});
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);
});
</script> </script>
{#if !dev} <div class="relative flex min-h-screen flex-col">
<Analytics /> <slot />
{/if} </div>
<div class="wrapper">
<Header user={data.user} />
<main>
<Transition url={data.url} transition={{ type: 'page' }}>
<slot />
</Transition>
</main>
<Footer />
</div>
<Toaster />
{#if $boredState?.loading}
<Portal>
<div class="loading">
<Loading></Loading>
<h3>Loading...</h3>
</div>
<div class="background"></div>
</Portal>
{/if}
{#if isOpen}
<div class="container">
<svelte:component this={$boredState?.dialog?.content}></svelte:component>
</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%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 101;
display: grid;
place-items: center;
gap: 1rem;
& h3 {
color: white;
}
}
.background {
background: black;
opacity: 0.8;
cursor: none;
inset: 0;
position: fixed;
z-index: 100;
}
.wrapper {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
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>