Moving validations to separate folder, upgrading superforms v2, and shadcn components.

This commit is contained in:
Bradley Shellnut 2024-02-25 22:59:29 -08:00
parent becf1d8349
commit 8f9db3fea5
53 changed files with 997 additions and 583 deletions

View file

@ -26,7 +26,7 @@
},
"devDependencies": {
"@melt-ui/pp": "^0.3.0",
"@melt-ui/svelte": "^0.74.1",
"@melt-ui/svelte": "^0.74.2",
"@playwright/test": "^1.41.2",
"@resvg/resvg-js": "^2.6.0",
"@sveltejs/adapter-auto": "^3.1.1",
@ -34,14 +34,14 @@
"@sveltejs/kit": "^2.5.1",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/cookie": "^0.6.0",
"@types/node": "^20.11.19",
"@types/pg": "^8.11.0",
"@types/node": "^20.11.20",
"@types/pg": "^8.11.1",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"autoprefixer": "^10.4.17",
"dotenv": "^16.4.5",
"drizzle-kit": "^0.20.14",
"eslint": "^8.56.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",
"just-clone": "^6.2.0",
@ -53,17 +53,17 @@
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.1",
"prisma": "^5.9.1",
"sass": "^1.71.0",
"sass": "^1.71.1",
"satori": "^0.10.13",
"satori-html": "^0.3.2",
"svelte": "^4.2.11",
"svelte": "^4.2.12",
"svelte-check": "^3.6.4",
"svelte-meta-tags": "^3.1.0",
"svelte-preprocess": "^5.1.3",
"svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-flash-message": "^2.4.2",
"sveltekit-rate-limiter": "^0.4.3",
"sveltekit-superforms": "^1.13.4",
"sveltekit-superforms": "^2.6.2",
"tailwindcss": "^3.4.1",
"ts-node": "^10.9.2",
"tslib": "^2.6.1",
@ -93,31 +93,31 @@
"@sveltejs/adapter-vercel": "^5.1.0",
"@types/feather-icons": "^4.29.4",
"@vercel/og": "^0.5.20",
"bits-ui": "^0.18.1",
"bits-ui": "^0.18.2",
"boardgamegeekclient": "^1.9.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cookie": "^0.6.0",
"drizzle-orm": "^0.29.4",
"feather-icons": "^4.29.1",
"formsnap": "^0.4.4",
"formsnap": "^0.5.1",
"html-entities": "^2.4.0",
"iconify-icon": "^2.0.0",
"just-kebab-case": "^4.2.0",
"loader": "^2.1.1",
"lucia": "3.0.1",
"lucide-svelte": "^0.335.0",
"lucide-svelte": "^0.340.0",
"mysql2": "^3.9.1",
"nanoid": "^5.0.6",
"open-props": "^1.6.19",
"oslo": "^1.1.2",
"oslo": "^1.1.3",
"pg": "^8.11.3",
"postgres": "^3.4.3",
"radix-svelte": "^0.9.0",
"svelte-french-toast": "^1.2.0",
"svelte-lazy-loader": "^1.0.0",
"tailwind-merge": "^2.2.1",
"tailwind-variants": "^0.1.20",
"tailwind-variants": "^0.2.0",
"tailwindcss-animate": "^1.0.6",
"zod-to-json-schema": "^3.22.4"
}

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,18 @@
<script lang="ts">
import { zodClient } from 'sveltekit-superforms/adapters';
import { ConicGradient } from '@skeletonlabs/skeleton';
import type { ConicStop } from '@skeletonlabs/skeleton';
import { i } from "@inlang/sdk-js";
import { superForm } from 'sveltekit-superforms/client';
//import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import { userSchema } from '$lib/config/zod-schemas';
import { userSchema } from '$lib/validations/zod-schemas';
import { AlertTriangle } from 'lucide-svelte';
import { i } from "@inlang/sdk-js";
import { signInSchema } from '$lib/validations/auth';
export let data;
const signInSchema = userSchema.pick({ email: true, password: true });
const { form, errors, enhance, delayed } = superForm(data.form, {
taintedMessage: null,
validators: signInSchema,
validators: zodClient(signInSchema),
delayMs: 0
});
const conicStops: ConicStop[] = [

View file

@ -1,11 +1,13 @@
<script lang="ts">
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import { signUpSchema } from '$lib/config/zod-schemas';
import { signUpSchema } from '$lib/validations/auth';
export let data;
const { form, errors, enhance } = superForm(data.form, {
taintedMessage: null,
validators: signUpSchema,
validators: zodClient(signUpSchema),
delayMs: 0
});
// $: termsValue = $form.terms as Writable<boolean>;

View file

@ -9,10 +9,7 @@
</script>
<AvatarPrimitive.Fallback
class={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
class={cn("flex h-full w-full items-center justify-center rounded-full bg-muted", className)}
{...$$restProps}
>
<slot />

View file

@ -11,10 +11,7 @@
<AvatarPrimitive.Root
{delayMs}
class={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
class={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
{...$$restProps}
>
<slot />

View file

@ -9,5 +9,5 @@ export {
//
Root as Avatar,
Image as AvatarImage,
Fallback as AvatarFallback
Fallback as AvatarFallback,
};

View file

@ -12,19 +12,19 @@ const buttonVariants = tv({
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline"
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10"
}
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default"
}
size: "default",
},
});
type Variant = VariantProps<typeof buttonVariants>["variant"];
@ -45,5 +45,5 @@ export {
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants
buttonVariants,
};

View file

@ -9,10 +9,7 @@
</script>
<div
class={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
class={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
{...$$restProps}
>
<slot />

View file

@ -18,7 +18,7 @@ export {
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle
Title as CardTitle,
};
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";

View file

@ -6,7 +6,7 @@
export let transition: $$Props["transition"] = slide;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150
duration: 150,
};
</script>

View file

@ -11,5 +11,5 @@ export {
//
Root as Collapsible,
Content as CollapsibleContent,
Trigger as CollapsibleTrigger
Trigger as CollapsibleTrigger,
};

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Check from "lucide-svelte/icons/check";
import { cn } from "$lib/utils";
import { Check } from "lucide-svelte";
type $$Props = DropdownMenuPrimitive.CheckboxItemProps;
type $$Events = DropdownMenuPrimitive.CheckboxItemEvents;
@ -14,7 +14,7 @@
<DropdownMenuPrimitive.CheckboxItem
bind:checked
class={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
className
)}
{...$$restProps}

View file

@ -6,6 +6,7 @@
type $$Events = DropdownMenuPrimitive.ContentEvents;
let className: $$Props["class"] = undefined;
export let sideOffset: $$Props["sideOffset"] = 4;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = undefined;
export { className as class };
@ -14,6 +15,7 @@
<DropdownMenuPrimitive.Content
{transition}
{transitionConfig}
{sideOffset}
class={cn(
"z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md focus:outline-none",
className

View file

@ -14,7 +14,7 @@
<DropdownMenuPrimitive.Item
class={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
inset && "pl-8",
className
)}

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import Circle from "lucide-svelte/icons/circle";
import { cn } from "$lib/utils";
import { Circle } from "lucide-svelte";
type $$Props = DropdownMenuPrimitive.RadioItemProps;
type $$Events = DropdownMenuPrimitive.RadioItemEvents;
@ -13,7 +13,7 @@
<DropdownMenuPrimitive.RadioItem
class={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
className
)}
{value}

View file

@ -8,9 +8,6 @@
export { className as class };
</script>
<span
class={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...$$restProps}
>
<span class={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...$$restProps}>
<slot />
</span>

View file

@ -9,7 +9,7 @@
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
x: -10,
y: 0
y: 0,
};
export { className as class };
</script>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right";
import { cn } from "$lib/utils";
import { ChevronRight } from "lucide-svelte";
type $$Props = DropdownMenuPrimitive.SubTriggerProps & {
inset?: boolean;
@ -15,7 +15,7 @@
<DropdownMenuPrimitive.SubTrigger
class={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent",
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}

View file

@ -44,5 +44,5 @@ export {
RadioGroup as DropdownMenuRadioGroup,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
CheckboxItem as DropdownMenuCheckboxItem
CheckboxItem as DropdownMenuCheckboxItem,
};

View file

@ -1,9 +1,9 @@
<script lang="ts">
import * as Button from "$lib/components/ui/button";
type $$Props = Button.Props;
type $$Events = Button.Events;
</script>
<Button.Root type="submit" {...$$restProps} on:click on:keydown>
<Button.Root type="submit" {...$$restProps}>
<slot />
</Button.Root>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { Form as FormPrimitive } from "formsnap";
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
@ -8,6 +8,10 @@
export { className as class };
</script>
<FormPrimitive.Description class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
<slot />
<FormPrimitive.Description
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
let:descriptionAttrs
>
<slot {descriptionAttrs} />
</FormPrimitive.Description>

View file

@ -0,0 +1,26 @@
<script lang="ts" context="module">
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { FormPathLeaves, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = unknown;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
import type { HTMLAttributes } from "svelte/elements";
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.ElementField {form} {name} let:constraints let:errors let:tainted let:value>
<div class={cn("space-y-2", className)}>
<slot {constraints} {errors} {tainted} {value} />
</div>
</FormPrimitive.ElementField>

View file

@ -0,0 +1,26 @@
<script lang="ts">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.FieldErrorsProps & {
errorClasses?: string | undefined | null;
};
let className: $$Props["class"] = undefined;
export { className as class };
export let errorClasses: $$Props["class"] = undefined;
</script>
<FormPrimitive.FieldErrors
class={cn("text-sm font-medium text-destructive", className)}
{...$$restProps}
let:errors
let:fieldErrorsAttrs
let:errorAttrs
>
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
{#each errors as error}
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
{/each}
</slot>
</FormPrimitive.FieldErrors>

View file

@ -0,0 +1,26 @@
<script lang="ts" context="module">
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { FormPath, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = unknown;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import type { HTMLAttributes } from "svelte/elements";
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Field {form} {name} let:constraints let:errors let:tainted let:value>
<div class={cn("space-y-2", className)}>
<slot {constraints} {errors} {tainted} {value} />
</div>
</FormPrimitive.Field>

View file

@ -0,0 +1,31 @@
<script lang="ts" context="module">
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { FormPath, SuperForm } from "sveltekit-superforms";
type T = Record<string, unknown>;
type U = unknown;
</script>
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.FieldsetProps<T, U>;
export let form: SuperForm<T>;
export let name: U;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Fieldset
{form}
{name}
let:constraints
let:errors
let:tainted
let:value
class={cn("space-y-2", className)}
>
<slot {constraints} {errors} {tainted} {value} />
</FormPrimitive.Fieldset>

View file

@ -1,6 +1,6 @@
<script lang="ts">
import type { Label as LabelPrimitive } from "bits-ui";
import { getFormField } from "formsnap";
import { getFormControl } from "formsnap";
import { cn } from "$lib/utils";
import { Label } from "$lib/components/ui/label";
@ -9,9 +9,9 @@
let className: $$Props["class"] = undefined;
export { className as class };
const { errors, ids } = getFormField();
const { labelAttrs } = getFormControl();
</script>
<Label for={$ids.input} class={cn($errors && "text-destructive", className)} {...$$restProps}>
<slot />
<Label {...$labelAttrs} class={cn("data-[fs-error]:text-destructive", className)} {...$$restProps}>
<slot {labelAttrs} />
</Label>

View file

@ -0,0 +1,17 @@
<script lang="ts">
import * as FormPrimitive from "formsnap";
import { cn } from "$lib/utils";
type $$Props = FormPrimitive.LegendProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<FormPrimitive.Legend
{...$$restProps}
class={cn("text-sm font-medium leading-none data-[fs-error]:text-destructive", className)}
let:legendAttrs
>
<slot {legendAttrs} />
</FormPrimitive.Legend>

View file

@ -1,82 +1,33 @@
import { Form as FormPrimitive, getFormField } from "formsnap";
import * as RadioGroupComp from "$lib/components/ui/radio-group";
import * as SelectComp from "$lib/components/ui/select";
import type { Writable } from "svelte/store";
import Item from "./form-item.svelte";
import Input from "./form-input.svelte";
import Textarea from "./form-textarea.svelte";
import * as FormPrimitive from "formsnap";
import Description from "./form-description.svelte";
import Label from "./form-label.svelte";
import Validation from "./form-validation.svelte";
import Checkbox from "./form-checkbox.svelte";
import Switch from "./form-switch.svelte";
import NativeSelect from "./form-native-select.svelte";
import RadioGroup from "./form-radio-group.svelte";
import Select from "./form-select.svelte";
import SelectTrigger from "./form-select-trigger.svelte";
import FieldErrors from "./form-field-errors.svelte";
import Field from "./form-field.svelte";
import Fieldset from "./form-fieldset.svelte";
import Legend from "./form-legend.svelte";
import ElementField from "./form-element-field.svelte";
import Button from "./form-button.svelte";
const Root = FormPrimitive.Root;
const Field = FormPrimitive.Field;
const Control = FormPrimitive.Control;
const RadioItem = RadioGroupComp.Item;
const NativeRadio = FormPrimitive.Radio;
const SelectContent = SelectComp.Content;
const SelectLabel = SelectComp.Label;
const SelectGroup = SelectComp.Group;
const SelectItem = SelectComp.Item;
const SelectSeparator = SelectComp.Separator;
export type TextareaGetFormField = Omit<ReturnType<typeof getFormField>, "value"> & {
value: Writable<string>;
};
export {
Root,
Field,
Control,
Item,
Input,
Label,
Button,
Switch,
Select,
Checkbox,
Textarea,
Validation,
RadioGroup,
RadioItem,
FieldErrors,
Description,
SelectContent,
SelectLabel,
SelectGroup,
SelectItem,
SelectSeparator,
SelectTrigger,
NativeSelect,
NativeRadio,
Fieldset,
Legend,
ElementField,
//
Root as Form,
Field as FormField,
Control as FormControl,
Item as FormItem,
Input as FormInput,
Textarea as FormTextarea,
Description as FormDescription,
Label as FormLabel,
Validation as FormValidation,
NativeSelect as FormNativeSelect,
NativeRadio as FormNativeRadio,
Checkbox as FormCheckbox,
Switch as FormSwitch,
RadioGroup as FormRadioGroup,
RadioItem as FormRadioItem,
Select as FormSelect,
SelectContent as FormSelectContent,
SelectLabel as FormSelectLabel,
SelectGroup as FormSelectGroup,
SelectItem as FormSelectItem,
SelectSeparator as FormSelectSeparator,
SelectTrigger as FormSelectTrigger,
Button as FormButton
FieldErrors as FormFieldErrors,
Fieldset as FormFieldset,
Legend as FormLegend,
ElementField as FormElementField,
Button as FormButton,
};

View file

@ -3,5 +3,5 @@ import Root from "./label.svelte";
export {
Root,
//
Root as Label
Root as Label,
};

View file

@ -20,5 +20,5 @@ export {
Link as PaginationLink,
PrevButton as PaginationPrevButton,
NextButton as PaginationNextButton,
Ellipsis as PaginationEllipsis
Ellipsis as PaginationEllipsis,
};

View file

@ -1,6 +1,6 @@
<script lang="ts">
import MoreHorizontal from "lucide-svelte/icons/more-horizontal";
import { cn } from "$lib/utils";
import { MoreHorizontal } from "lucide-svelte";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLSpanElement>;

View file

@ -23,7 +23,7 @@
class={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size
size,
}),
className
)}

View file

@ -1,8 +1,8 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { ChevronRight } from "lucide-svelte";
type $$Props = PaginationPrimitive.NextButtonProps;
type $$Events = PaginationPrimitive.NextButtonEvents;

View file

@ -1,8 +1,8 @@
<script lang="ts">
import { Pagination as PaginationPrimitive } from "bits-ui";
import ChevronLeft from "lucide-svelte/icons/chevron-left";
import { Button } from "$lib/components/ui/button";
import { cn } from "$lib/utils";
import { ChevronLeft } from "lucide-svelte";
type $$Props = PaginationPrimitive.PrevButtonProps;
type $$Events = PaginationPrimitive.PrevButtonEvents;

View file

@ -28,7 +28,7 @@
asChild
{...$$restProps}
>
<nav {...builder} class={cn("mx-auto flex flex-col w-full items-center", className)}>
<nav {...builder} class={cn("mx-auto flex w-full flex-col items-center", className)}>
<slot {pages} {range} {currentPage} />
</nav>
</PaginationPrimitive.Root>

View file

@ -1,90 +1,43 @@
import { z } from 'zod';
import { userSchema } from './zod-schemas';
export type ListGame = {
id: string;
game_name: string;
game_id: string;
collection_id: string;
wishlist_id: string;
times_played: number;
thumb_url: string | null;
in_collection: boolean;
in_wishlist: boolean;
};
export const modifyListGameSchema = z.object({
id: z.string()
export const profileSchema = userSchema.pick({
firstName: true,
lastName: true,
email: true,
username: true
});
export type ModifyListGame = typeof modifyListGameSchema;
export const userSchema = z.object({
firstName: z.string().trim().optional(),
lastName: z.string().trim().optional(),
email: z.string().email({ message: 'Please enter a valid email address' }).optional(),
username: z
.string()
.trim()
.min(3, { message: 'Username must be at least 3 characters' })
.max(50, { message: 'Username must be less than 50 characters' }),
password: z
.string({ required_error: 'Password is required' })
.trim(),
confirm_password: z
.string({ required_error: 'Confirm Password is required' })
.trim(),
verified: z.boolean().default(false),
token: z.string().optional(),
receiveEmail: z.boolean().default(false),
createdAt: z.date().optional(),
updatedAt: z.date().optional()
});
export const signUpSchema = userSchema
.pick({
firstName: true,
lastName: true,
email: true,
username: true,
password: true,
confirm_password: true,
terms: true
export const changeUserPasswordSchema = z
.object({
current_password: z.string({ required_error: 'Current Password is required' }),
password: z.string({ required_error: 'Password is required' }).trim(),
confirm_password: z.string({ required_error: 'Confirm Password is required' }).trim()
})
.superRefine(({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx);
});
export const signInSchema = userSchema.pick({
username: true,
password: true
});
export const updateUserPasswordSchema = userSchema
.pick({ password: true, confirm_password: true })
.superRefine(({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx);
});
export const changeUserPasswordSchema = z
.object({
current_password: z.string({ required_error: 'Current Password is required' }),
password: z
.string({ required_error: 'Password is required' })
.trim(),
confirm_password: z
.string({ required_error: 'Confirm Password is required' })
.trim()
})
.superRefine(({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx);
});
const refinePasswords = async function(confirm_password: string, password: string, ctx: z.RefinementCtx) {
export const refinePasswords = async function (
confirm_password: string,
password: string,
ctx: z.RefinementCtx
) {
comparePasswords(confirm_password, password, ctx);
checkPasswordStrength(password, ctx);
}
};
const comparePasswords = async function(confirm_password: string, password: string, ctx: z.RefinementCtx) {
const comparePasswords = async function (
confirm_password: string,
password: string,
ctx: z.RefinementCtx
) {
if (confirm_password !== password) {
ctx.addIssue({
code: 'custom',
@ -92,15 +45,14 @@ const comparePasswords = async function(confirm_password: string, password: stri
path: ['confirm_password']
});
}
}
};
const checkPasswordStrength = async function (password: string, ctx: z.RefinementCtx) {
const minimumLength = password.length < 8;
const maximumLength = password.length > 128;
const containsUppercase = (ch: string) => /[A-Z]/.test(ch);
const containsLowercase = (ch: string) => /[a-z]/.test(ch);
const containsSpecialChar = (ch: string) =>
/[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/.test(ch);
const containsSpecialChar = (ch: string) => /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/.test(ch);
let countOfUpperCase = 0,
countOfLowerCase = 0,
countOfNumbers = 0,
@ -139,7 +91,6 @@ const checkPasswordStrength = async function (password: string, ctx: z.Refinemen
errorMessage += ' Be less than 128 characters long.';
}
if (errorMessage.length > 'Your password:'.length) {
ctx.addIssue({
code: 'custom',
@ -147,4 +98,4 @@ const checkPasswordStrength = async function (password: string, ctx: z.Refinemen
path: ['password']
});
}
}
};

View file

@ -0,0 +1,21 @@
import { refinePasswords } from "./account";
import { userSchema } from "./zod-schemas";
export const signUpSchema = userSchema
.pick({
firstName: true,
lastName: true,
email: true,
username: true,
password: true,
confirm_password: true,
terms: true
})
.superRefine(({ confirm_password, password }, ctx) => {
refinePasswords(confirm_password, password, ctx);
});
export const signInSchema = userSchema.pick({
username: true,
password: true
});

View file

@ -0,0 +1,45 @@
import { z } from 'zod';
export type ListGame = {
id: string;
game_name: string;
game_id: string;
collection_id: string;
wishlist_id: string;
times_played: number;
thumb_url: string | null;
in_collection: boolean;
in_wishlist: boolean;
};
export const modifyListGameSchema = z.object({
id: z.string()
});
export type ModifyListGame = typeof modifyListGameSchema;
export const userSchema = z.object({
firstName: z.string().trim().optional(),
lastName: z.string().trim().optional(),
email: z.string()
.trim()
.max(64, { message: 'Email must be less than 64 characters' })
.email({ message: 'Please enter a valid email address' })
.optional(),
username: z
.string()
.trim()
.min(3, { message: 'Username must be at least 3 characters' })
.max(50, { message: 'Username must be less than 50 characters' }),
password: z
.string({ required_error: 'Password is required' })
.trim(),
confirm_password: z
.string({ required_error: 'Confirm Password is required' })
.trim(),
verified: z.boolean().default(false),
token: z.string().optional(),
receiveEmail: z.boolean().default(false),
createdAt: z.date().optional(),
updatedAt: z.date().optional()
});

View file

@ -4,11 +4,11 @@ import { drizzle } from 'drizzle-orm/postgres-js';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
const connection = postgres({
host: process.env.DATABASE_HOST,
host: process.env.DATABASE_HOST || 'localhost',
port: 3306,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_DB,
user: process.env.DATABASE_USER || 'root',
password: process.env.DATABASE_PASSWORD || '',
database: process.env.DATABASE_DB || 'boredgame',
ssl: 'require',
max: 1
});

View file

@ -1,10 +1,11 @@
import { type Actions, error, fail, redirect } from '@sveltejs/kit';
import { and, eq } from 'drizzle-orm';
import { superValidate } from 'sveltekit-superforms/server';
import { zod } from 'sveltekit-superforms/adapters';
import { modifyListGameSchema, type ListGame } from '$lib/config/zod-schemas.js';
import { search_schema } from '$lib/zodValidation.js';
import type { PageServerLoad } from './$types';
import db from '$lib/drizzle';
import { and, eq } from 'drizzle-orm';
import { collection_items, collections, games } from '../../../../schema';
export const load: PageServerLoad = async ({ fetch, url, locals }) => {
@ -26,8 +27,8 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
skip
};
const searchForm = await superValidate(searchData, search_schema);
const listManageForm = await superValidate(modifyListGameSchema);
const searchForm = await superValidate(searchData, zod(search_schema));
const listManageForm = await superValidate(zod(modifyListGameSchema));
try {
const collection = await db.query.collections.findFirst({
@ -98,7 +99,7 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const form = await superValidate(event, modifyListGameSchema);
const form = await superValidate(event, zod(modifyListGameSchema));
if (!event.locals.user) {
throw fail(401);
@ -160,7 +161,7 @@ export const actions: Actions = {
// Remove game from a wishlist
remove: async (event) => {
const { locals } = event;
const form = await superValidate(event, modifyListGameSchema);
const form = await superValidate(event, zod(modifyListGameSchema));
if (!locals.user) {
throw fail(401);

View file

@ -1,7 +1,8 @@
import { redirect } from "@sveltejs/kit";
import { superValidate } from "sveltekit-superforms/server";
import { zod } from 'sveltekit-superforms/adapters';
import type { PageServerLoad } from "../$types";
import { BggForm } from "$lib/zodValidation";
import { superValidate } from "sveltekit-superforms/server";
export const load: PageServerLoad = async ({ locals, fetch }) => {
const user = locals.user;
@ -9,7 +10,7 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
redirect(302, '/login');
}
const form = await superValidate({}, BggForm);
const form = await superValidate({}, zod(BggForm));
return { form };
}

View file

@ -44,7 +44,7 @@ export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const { params, locals, request } = event;
const form = await superValidate(event, modifyListGameSchema);
const form = await superValidate(event, zod(modifyListGameSchema));
if (!locals.user) {
throw fail(401);

View file

@ -1,5 +1,6 @@
import { fail, redirect, type Actions } from "@sveltejs/kit";
import { eq } from "drizzle-orm";
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { Argon2id } from "oslo/password";
import db from "$lib/drizzle";
@ -9,7 +10,7 @@ import type { PageServerLoad } from "./$types";
import { users } from "../../../../../schema";
export const load: PageServerLoad = async (event) => {
const form = await superValidate(event, changeUserPasswordSchema);
const form = await superValidate(event, zod(changeUserPasswordSchema));
const user = event.locals.user;
if (!user) {
@ -28,7 +29,7 @@ export const load: PageServerLoad = async (event) => {
export const actions: Actions = {
default: async (event) => {
const form = await superValidate(event, changeUserPasswordSchema);
const form = await superValidate(event, zod(changeUserPasswordSchema));
if (!form.valid) {
return fail(400, {

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import { changeUserPasswordSchema, userSchema } from '$lib/config/zod-schemas';
import { changeUserPasswordSchema, userSchema } from '$lib/validations/zod-schemas.js';
import { Label } from '$components/ui/label';
import { Input } from '$components/ui/input';
import { Button } from '$components/ui/button';
@ -9,7 +10,7 @@
const { form, errors, enhance, delayed, message } = superForm(data.form, {
taintedMessage: null,
validators: changeUserPasswordSchema,
validators: zodClient(changeUserPasswordSchema),
delayMs: 0
});
</script>

View file

@ -1,8 +1,9 @@
import { fail, type Actions } from '@sveltejs/kit';
import { eq } from 'drizzle-orm';
import { zod } from 'sveltekit-superforms/adapters';
import { message, setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import { userSchema } from '$lib/config/zod-schemas';
import { userSchema } from '$lib/validations/zod-schemas';
import type { PageServerLoad } from './$types';
import { users } from '../../../../schema';
import db from '$lib/drizzle';
@ -15,7 +16,7 @@ const profileSchema = userSchema.pick({
});
export const load: PageServerLoad = async (event) => {
const form = await superValidate(event, profileSchema);
const form = await superValidate(event, zod(profileSchema));
if (!event.locals.user) {
const message = { type: 'error', message: 'You are not signed in' } as const;
@ -37,7 +38,7 @@ export const load: PageServerLoad = async (event) => {
export const actions: Actions = {
default: async (event) => {
const form = await superValidate(event, profileSchema);
const form = await superValidate(event, zod(profileSchema));
if (!form.valid) {
return fail(400, {

View file

@ -1,7 +1,8 @@
<script lang="ts">
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import { AlertTriangle, KeyRound } from 'lucide-svelte';
import { userSchema } from '$lib/config/zod-schemas';
import { profileSchema } from '$lib/validations/account';
import * as Alert from "$lib/components/ui/alert";
import { Label } from '$lib/components/ui/label';
import { Input } from '$components/ui/input';
@ -9,16 +10,9 @@
export let data;
const profileSchema = userSchema.pick({
firstName: true,
lastName: true,
email: true,
username: true
});
const { form, errors, enhance, message } = superForm(data.form, {
taintedMessage: null,
validators: profileSchema,
validators: zodClient(profileSchema),
delayMs: 0
});
</script>

View file

@ -1,4 +1,5 @@
import { error, redirect, type Actions } from '@sveltejs/kit';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import { modifyListGameSchema } from '$lib/config/zod-schemas.js';
import db from '$lib/drizzle.js';
@ -49,7 +50,7 @@ export const actions: Actions = {
// Add game to a wishlist
add: async (event) => {
const { locals } = event;
const form = await superValidate(event, modifyListGameSchema);
const form = await superValidate(event, zod(modifyListGameSchema));
try {
if (!locals.user) {
@ -111,7 +112,7 @@ export const actions: Actions = {
// Remove game from a wishlist
remove: async (event) => {
const { locals } = event;
const form = await superValidate(event, modifyListGameSchema);
const form = await superValidate(event, zod(modifyListGameSchema));
try {
if (!locals.user) {

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { fade, fly } from 'svelte/transition';
import { Image } from 'svelte-lazy-loader';
import { Dices, ExternalLinkIcon, MinusIcon, PlusIcon } from 'lucide-svelte';
import type { SavedGameType } from '$lib/types';

View file

@ -1,4 +1,5 @@
import { error } from '@sveltejs/kit';
import { zod } from 'sveltekit-superforms/adapters';
import { superValidate } from 'sveltekit-superforms/server';
import kebabCase from 'just-kebab-case';
import type { GameType, SearchQuery } from '$lib/types';
@ -16,7 +17,7 @@ async function searchForGames(
try {
console.log('urlQueryParams search games', urlQueryParams);
const headers: HeadersInit = new Headers();
const headers = new Headers();
headers.set('Content-Type', 'application/json');
const requestInit: RequestInit = {
method: 'GET',
@ -112,7 +113,7 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
skip: Number(searchParams.skip || defaults.skip),
limit: Number(searchParams.limit || defaults.limit),
exact: searchParams.exact ? searchParams.exact === 'true' : defaults.exact
}, search_schema);
}, zod(search_schema));
const queryParams: SearchQuery = {
limit: form.data?.limit,
@ -189,7 +190,7 @@ export const load: PageServerLoad = async ({ locals, fetch, url }) => {
export const actions = {
random: async ({ request, locals, fetch }): Promise<any> => {
const form = await superValidate(request, search_schema);
const form = await superValidate(request, zod(search_schema));
const queryParams: SearchQuery = {
order_by: 'rank',
ascending: false,

View file

@ -1,21 +1,17 @@
import { fail, type Actions } from '@sveltejs/kit';
import { eq, sql } from 'drizzle-orm';
import { eq } from 'drizzle-orm';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { redirect } from 'sveltekit-flash-message/server';
import { lucia } from '$lib/server/auth';
import { Argon2id } from 'oslo/password';
import { userSchema } from '$lib/config/zod-schemas';
import db from '$lib/drizzle';
import { signInSchema } from '$lib/validations/auth'
import { collections, users, wishlists } from '../../../schema';
import type { PageServerLoad } from './$types';
const signInSchema = userSchema.pick({
username: true,
password: true
});
export const load: PageServerLoad = async (event) => {
const form = await superValidate(event, signInSchema);
const form = await superValidate(event, zod(signInSchema));
console.log('login load event', event);
if (event.locals.user) {
@ -31,7 +27,7 @@ export const load: PageServerLoad = async (event) => {
export const actions: Actions = {
default: async (event) => {
const { locals } = event;
const form = await superValidate(event, signInSchema);
const form = await superValidate(event, zod(signInSchema));
if (!form.valid) {
form.data.password = '';

View file

@ -1,9 +1,9 @@
<script lang="ts">
import { page } from '$app/stores';
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import * as flashModule from 'sveltekit-flash-message/client';
import { AlertCircle } from "lucide-svelte";
import { signInSchema } from '$lib/config/zod-schemas.js';
import { signInSchema } from '$lib/validations/zod-schemas.js';
import { Label } from '$components/ui/label';
import { Input } from '$components/ui/input';
import { Button } from '$components/ui/button';
@ -26,7 +26,7 @@
},
syncFlashMessage: false,
taintedMessage: null,
validators: signInSchema,
validators: zodClient(signInSchema),
validationMethod: 'oninput',
delayMs: 0,
});

View file

@ -1,13 +1,14 @@
<script lang="ts">
import { slide } from 'svelte/transition';
import { quintIn } from 'svelte/easing';
import { zodClient } from 'sveltekit-superforms/adapters';
import { superForm } from 'sveltekit-superforms/client';
import * as flashModule from 'sveltekit-flash-message/client';
import { ChevronsUpDown } from "lucide-svelte";
import { Button } from '$components/ui/button';
import { Label } from '$components/ui/label';
import { Input } from '$components/ui/input';
import { signUpSchema } from '$lib/config/zod-schemas.js';
import { signUpSchema } from '$lib/validations/zod-schemas.js';
import * as Collapsible from '$lib/components/ui/collapsible';
import * as Alert from '$lib/components/ui/alert';
import { boredState } from '$lib/stores/boredState.js';
@ -25,7 +26,7 @@
},
},
taintedMessage: null,
validators: signUpSchema,
validators: zodClient(signUpSchema),
delayMs: 0,
});