Alert on login failure and starting light/dark mode change.

This commit is contained in:
Bradley Shellnut 2023-07-01 16:12:17 -07:00
parent f80b8f5391
commit 9c71c3664f
10 changed files with 130 additions and 67 deletions

View file

@ -5,16 +5,19 @@ const atImport = require('postcss-import');
const config = { const config = {
plugins: [ plugins: [
tailwindcss(),
atImport(), atImport(),
// 'tailwindcss/nesting'(),
tailwindcss(),
postcssPresetEnv({ postcssPresetEnv({
stage: 2, stage: 2,
features: { features: {
'nesting-rules': true, 'nesting-rules': false,
'custom-media-queries': true, 'custom-media-queries': true,
'media-query-ranges': true 'media-query-ranges': true
} }
}) }),
] //Some plugins, like tailwindcss/nesting, need to run before Tailwind, tailwindcss(), //But others, like autoprefixer, need to run after, autoprefixer] ] //Some plugins, like tailwindcss/nesting, need to run before Tailwind, tailwindcss(), //But others, like autoprefixer, need to run after, autoprefixer]
}; };

View file

@ -6,34 +6,34 @@
<link rel="icon" href="%sveltekit.assets%/favicon-bored.png" /> <link rel="icon" href="%sveltekit.assets%/favicon-bored.png" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<script> <script>
const htmlElement = document.documentElement; // const htmlElement = document.documentElement;
const userTheme = localStorage.theme; // const userTheme = localStorage.theme;
const userFont = localStorage.font; // const userFont = localStorage.font;
const prefersDarkMode = window.matchMedia('prefers-color-scheme: dark').matches; // const prefersDarkMode = window.matchMedia('prefers-color-scheme: dark').matches;
const prefersLightMode = window.matchMedia('prefers-color-scheme: light').matches; // const prefersLightMode = window.matchMedia('prefers-color-scheme: light').matches;
// check if the user set a theme // // check if the user set a theme
if (userTheme) { // if (userTheme) {
htmlElement.dataset.theme = userTheme; // htmlElement.dataset.theme = userTheme;
} // }
// otherwise check for user preference // // otherwise check for user preference
if (!userTheme && prefersDarkMode) { // if (!userTheme && prefersDarkMode) {
htmlElement.dataset.theme = '🌛 Night'; // htmlElement.dataset.theme = '🌛 Night';
localStorage.theme = '🌛 Night'; // localStorage.theme = '🌛 Night';
} // }
if (!userTheme && prefersLightMode) { // if (!userTheme && prefersLightMode) {
htmlElement.dataset.theme = '☀️ Daylight'; // htmlElement.dataset.theme = '☀️ Daylight';
localStorage.theme = '☀️ Daylight'; // localStorage.theme = '☀️ Daylight';
} // }
// if nothing is set default to dark mode // // if nothing is set default to dark mode
if (!userTheme && !prefersDarkMode && !prefersLightMode) { // if (!userTheme && !prefersDarkMode && !prefersLightMode) {
htmlElement.dataset.theme = '🌛 Night'; // htmlElement.dataset.theme = '🌛 Night';
localStorage.theme = '🌛 Night'; // localStorage.theme = '🌛 Night';
} // }
</script> </script>
%sveltekit.head% %sveltekit.head%
</head> </head>

View file

@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
import { LogOut } from 'lucide-svelte';
import Button from '$components/ui/button/Button.svelte';
// import Profile from '../preferences/profile.svelte'; // import Profile from '../preferences/profile.svelte';
import logo from './bored-game.png'; import logo from './bored-game.png';
@ -23,9 +25,10 @@
action="/auth/signout" action="/auth/signout"
method="POST" method="POST"
> >
<button type="submit" class="btn" <Button type="submit">
><span>Sign out</span></button <LogOut class="mr-2 h-4 w-4"/>
> Sign out
</Button>
</form> </form>
{/if} {/if}
{#if !user} {#if !user}

View file

@ -0,0 +1,30 @@
<script lang="ts">
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
import { cn } from "$lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive"
}
},
defaultVariants: {
variant: "default"
}
}
);
let className: string | undefined | null = undefined;
export { className as class };
export let variant: VariantProps<typeof alertVariants>["variant"] =
"default";
</script>
<div class={cn(alertVariants({ variant }), className)} {...$$restProps}>
<slot />
</div>

View file

@ -0,0 +1,10 @@
<script lang="ts">
import { cn } from "$lib/utils";
let className: string | undefined | null = undefined;
export { className as class };
</script>
<div class={cn("text-sm [&_p]:leading-relaxed", className)} {...$$restProps}>
<slot />
</div>

View file

@ -0,0 +1,15 @@
<script lang="ts">
import { cn } from "$lib/utils";
let className: string | undefined | null = undefined;
export { className as class };
export let level: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" = "h5";
</script>
<svelte:element
this={level}
class={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</svelte:element>

View file

@ -0,0 +1,3 @@
export { default as Alert } from "./Alert.svelte";
export { default as AlertDescription } from "./AlertDescription.svelte";
export { default as AlertTitle } from "./AlertTitle.svelte";

View file

@ -52,7 +52,7 @@ export const actions = {
// TODO: need to return error message to the client // TODO: need to return error message to the client
console.error(e); console.error(e);
form.data.password = ''; form.data.password = '';
return setError(form, '', 'The username or password is incorrect.'); return setError(form, '', 'Your username or password is incorrect.');
} }
form.data.username = ''; form.data.username = '';
form.data.password = ''; form.data.password = '';

View file

@ -1,46 +1,43 @@
<script lang="ts"> <script lang="ts">
import { superForm } from 'sveltekit-superforms/client'; import { superForm } from 'sveltekit-superforms/client';
import { AlertCircle } from "lucide-svelte";
import { userSchema } from '$lib/config/zod-schemas.js'; import { userSchema } from '$lib/config/zod-schemas.js';
import Label from '$components/ui/label/Label.svelte'; import Label from '$components/ui/label/Label.svelte';
import Input from '$components/ui/input/Input.svelte'; import Input from '$components/ui/input/Input.svelte';
import Button from '$components/ui/button/Button.svelte'; import Button from '$components/ui/button/Button.svelte';
import { Alert, AlertDescription, AlertTitle } from "$components/ui/alert";
export let data; export let data;
const signInSchema = userSchema.pick({ username: true, password: true });
const { form, errors, enhance, delayed } = superForm(data.form, { const { form, errors, enhance, delayed } = superForm(data.form, {
taintedMessage: null, taintedMessage: null,
validators: signInSchema,
validationMethod: 'oninput', validationMethod: 'oninput',
delayMs: 0, delayMs: 0,
}); });
console.log($errors);
</script> </script>
<form method="POST" use:enhance> <div>
{#if $errors._errors} {#if $errors._errors}
<aside class="alert"> <Alert variant="destructive">
<div class="alert-message"> <AlertCircle class="h-4 w-4" />
<h3>There was an error signing in</h3> <AlertTitle>Error</AlertTitle>
<p>{$errors._errors}</p> <AlertDescription>
</div> {$errors._errors}
</aside> </AlertDescription>
</Alert>
{/if} {/if}
<div class="grid w-full max-w-sm items-center gap-2"> <form method="POST" use:enhance>
<h2 <div class="grid w-full max-w-sm items-center gap-2">
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0" <h2
> class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0"
Sign into your account >
</h2> Sign into your account
<Label for="username">Username</Label> </h2>
<Input type="text" id="username" name="username" placeholder="Username" autocomplete="username" data-invalid={$errors.username} bind:value={$form.username} /> <Label for="username">Username</Label>
{#if $errors.username} <Input type="text" id="username" name="username" placeholder="Username" autocomplete="username" data-invalid={$errors.username} bind:value={$form.username} />
<p class="text-sm text-muted-foreground">{$errors.username}</p> <Label for="password">Password</Label>
{/if} <Input type="password" id="password" name="password" placeholder="Password" autocomplete="new-password" data-invalid={$errors.password} bind:value={$form.password} />
<Label for="password">Password</Label> <Button type="submit">Sign In</Button>
<Input type="password" id="password" name="password" placeholder="Password" autocomplete="new-password" data-invalid={$errors.password} bind:value={$form.password} /> </div>
{#if $errors.password} </form>
<p class="text-sm text-muted-foreground">{$errors.password}</p> </div>
{/if}
<Button type="submit">Sign In</Button>
</div>
</form>

View file

@ -25,43 +25,45 @@ import { userSchema } from '$lib/config/zod-schemas.js';
<div class="page"> <div class="page">
<form method="POST" action="/auth/signup" use:enhance> <form method="POST" action="/auth/signup" use:enhance>
<div class="grid w-full max-w-sm items-center gap-2"> <div class="grid w-full max-w-sm items-center gap-2.5">
<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"
> >
Signup for an account Signup for an account
</h2> </h2>
<Label for="firstName">First Name</Label> <Label for="firstName">First Name</Label>
<Input type="text" id="firstName" name="firstName" placeholder="First Name" autocomplete="given-name" data-invalid={$errors.firstName} bind:value={$form.firstName} /> <Input
type="text"
id="firstName" class={$errors.firstName && "outline outline-destructive"} name="firstName" placeholder="First Name" autocomplete="given-name" data-invalid={$errors.firstName} bind:value={$form.firstName} />
{#if $errors.firstName} {#if $errors.firstName}
<p class="text-sm text-destructive">{$errors.firstName}</p> <p class="text-sm text-destructive">{$errors.firstName}</p>
{/if} {/if}
<Label for="firstName">Last Name</Label> <Label for="firstName">Last Name</Label>
<Input type="text" id="lastName" name="lastName" placeholder="Last Name" autocomplete="family-name" data-invalid={$errors.lastName} bind:value={$form.lastName} /> <Input type="text" id="lastName" class={$errors.firstName && "outline outline-destructive"} name="lastName" placeholder="Last Name" autocomplete="family-name" data-invalid={$errors.lastName} bind:value={$form.lastName} />
{#if $errors.lastName} {#if $errors.lastName}
<p class="text-sm text-destructive">{$errors.lastName}</p> <p class="text-sm text-destructive">{$errors.lastName}</p>
{/if} {/if}
<Label for="email">Email</Label> <Label for="email">Email</Label>
<Input type="email" id="email" name="email" placeholder="Email" autocomplete="email" data-invalid={$errors.email} bind:value={$form.email} /> <Input type="email" id="email" class={$errors.email && "outline outline-destructive"} name="email" placeholder="Email" autocomplete="email" data-invalid={$errors.email} bind:value={$form.email} />
{#if $errors.email} {#if $errors.email}
<p class="text-sm text-destructive">{$errors.email}</p> <p class="text-sm text-destructive">{$errors.email}</p>
{/if} {/if}
<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} /> <Input type="text" id="username" class={$errors.username && "outline outline-destructive"} name="username" placeholder="Username" autocomplete="username" data-invalid={$errors.username} bind:value={$form.username} />
{#if $errors.username} {#if $errors.username}
<p class="text-sm text-destructive">{$errors.username}</p> <p class="text-sm text-destructive">{$errors.username}</p>
{/if} {/if}
<Label for="password">Password</Label> <Label for="password">Password</Label>
<Input type="password" id="password" name="password" placeholder="Password" autocomplete="new-password" data-invalid={$errors.password} bind:value={$form.password} /> <Input type="password" id="password" class={$errors.password && "outline outline-destructive"} name="password" placeholder="Password" autocomplete="new-password" data-invalid={$errors.password} bind:value={$form.password} />
{#if $errors.password} {#if $errors.password}
<p class="text-sm text-destructive">{$errors.password}</p> <p class="text-sm text-destructive">{$errors.password}</p>
{/if} {/if}
<Label for="confirm_password">Confirm Password</Label> <Label for="confirm_password">Confirm Password</Label>
<Input type="password" id="confirm_password" name="confirm_password" placeholder="Confirm Password" autocomplete="new-password" data-invalid={$errors.confirm_password} bind:value={$form.confirm_password} /> <Input type="password" id="confirm_password" class={$errors.confirm_password && "outline outline-destructive"} name="confirm_password" placeholder="Confirm Password" autocomplete="new-password" data-invalid={$errors.confirm_password} bind:value={$form.confirm_password} />
{#if $errors.confirm_password} {#if $errors.confirm_password}
<p class="text-sm text-destructive">{$errors.confirm_password}</p> <p class="text-sm text-destructive">{$errors.confirm_password}</p>
{/if} {/if}
<div class="flex place-content-"> <div class="grid grid-cols-2">
<Button type="submit">Signup</Button> <Button type="submit">Signup</Button>
<Button variant="link" href="/">or Cancel</Button> <Button variant="link" href="/">or Cancel</Button>
</div> </div>