setup page and form

This commit is contained in:
2026-10-08 10:28:46 -04:00
parent 871cbdbb3e
commit 92d44d8806
18 changed files with 420 additions and 7 deletions

View File

@@ -1,9 +1,9 @@
## Status ## Status
### Last Completed ### Last Completed
Built out the schema for the most part Migrated the db and started it using `bunx prisma dev`. I can see it working with `bunx prisma studio`. Started working on structure and initial setup form.
### Next Up ### Next Up
Actually apply the schema and play around with adding data in studio Continue with initial setup and create functions to seed the db with first user and AllowList.
## Feature List ## Feature List
### Must Haves ### Must Haves

View File

@@ -11,7 +11,7 @@
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@fontsource-variable/inter": "^5.3.0", "@fontsource-variable/inter": "^5.3.0",
"@internationalized/date": "^3.12.0", "@internationalized/date": "^3.12.0",
"@lucide/svelte": "^1.52.0", "@lucide/svelte": "^1.53.0",
"@sveltejs/adapter-auto": "^8.0.0", "@sveltejs/adapter-auto": "^8.0.0",
"@sveltejs/kit": "^3.0.0", "@sveltejs/kit": "^3.0.0",
"@sveltejs/vite-plugin-svelte": "^7.2.0", "@sveltejs/vite-plugin-svelte": "^7.2.0",
@@ -131,7 +131,7 @@
"@layerstack/utils": ["@layerstack/utils@2.0.0-next.19", "", { "dependencies": { "d3-array": "^3.2.4", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0" } }, "sha512-HyXCEz9pge4rFWklrSgFr3YIwdE1gP8WM6GPetmnKPDfYLF1/oetRFKI9FqiE+dw4brnULGPKJsVhjM3vQfD4A=="], "@layerstack/utils": ["@layerstack/utils@2.0.0-next.19", "", { "dependencies": { "d3-array": "^3.2.4", "d3-time": "^3.1.0", "d3-time-format": "^4.1.0" } }, "sha512-HyXCEz9pge4rFWklrSgFr3YIwdE1gP8WM6GPetmnKPDfYLF1/oetRFKI9FqiE+dw4brnULGPKJsVhjM3vQfD4A=="],
"@lucide/svelte": ["@lucide/svelte@1.52.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-bQ8t3IcZxWqAdmdMZPyZEv8Qf+MDgBAf7n43eRQoXyBeLWr8HWToiMIO6vKlHxC/lnMiCDdTzm09hjy92ch46A=="], "@lucide/svelte": ["@lucide/svelte@1.53.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-RZTHcLAwwjAz+rO1S4IPQSTzDycGZWhSYa0GS3sXAoJ3GTHdBZyHCXutIxJketIAoaIkcmFK5Ucfo2+WBYpKLA=="],
"@oxc-project/types": ["@oxc-project/types@0.152.0", "", {}, "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw=="], "@oxc-project/types": ["@oxc-project/types@0.152.0", "", {}, "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw=="],

View File

@@ -20,7 +20,7 @@
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@fontsource-variable/inter": "^5.3.0", "@fontsource-variable/inter": "^5.3.0",
"@internationalized/date": "^3.12.0", "@internationalized/date": "^3.12.0",
"@lucide/svelte": "^1.52.0", "@lucide/svelte": "^1.53.0",
"@sveltejs/adapter-auto": "^8.0.0", "@sveltejs/adapter-auto": "^8.0.0",
"@sveltejs/kit": "^3.0.0", "@sveltejs/kit": "^3.0.0",
"@sveltejs/vite-plugin-svelte": "^7.2.0", "@sveltejs/vite-plugin-svelte": "^7.2.0",

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import * as Card from "../ui/card/index.ts";
import { Label } from "../ui/label/index.ts";
import { Input } from "../ui/input/index.ts";
import * as Select from "..//ui/select/index.js";
const roles = [
{ value: "COUPLE", label: "Couple" },
{ value: "PLANNER", label: "Planner" },
];
let value = $state("COUPLE");
</script>
<Card.Root class="w-lg">
<Card.Header>
<Card.Title>
First Time Setup
</Card.Title>
<Card.Description>
Complete the following information to finish setup
</Card.Description>
</Card.Header>
<Card.Content>
<form>
<div class="flex flex-col gap-6">
<div class="grid gap-2">
<Label for="email">I am a:</Label>
<Select.Root type="single" name="role" items={roles} bind:value>
<Select.Trigger class="w-full max-w-48">
<Select.Value placeholder="Select a role" />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Roles</Select.Label>
{#each roles as role (role.value)}
<Select.Item value={role.value} label={role.label}>
{role.label}
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
</div>
<div class="grid gap-2">
<Label for="email">Email</Label>
<Input id="email" type="email" placeholder="m@example.com" required />
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
<a
href="##"
class="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input id="password" type="password" required />
</div>
</div>
</form>
</Card.Content>
</Card.Root>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "#lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="field-content"
class={cn("gap-1 group/field-content flex flex-1 flex-col leading-snug", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import { cn, type WithElementRef } from "#lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
</script>
<p
bind:this={ref}
data-slot="field-description"
class={cn(
"text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-data-horizontal/field:text-balance",
"last:mt-0 nth-last-2:-mt-1",
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...restProps}
>
{@render children?.()}
</p>

View File

@@ -0,0 +1,58 @@
<script lang="ts">
import { cn, type WithElementRef } from "#lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
errors,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
children?: Snippet;
errors?: { message?: string }[];
} = $props();
const hasContent = $derived.by(() => {
// has slotted error
if (children) return true;
// no errors
if (!errors || errors.length === 0) return false;
// has an error but no message
if (errors.length === 1 && !errors[0]?.message) {
return false;
}
return true;
});
const isMultipleErrors = $derived(errors && errors.length > 1);
const singleErrorMessage = $derived(errors && errors.length === 1 && errors[0]?.message);
</script>
{#if hasContent}
<div
bind:this={ref}
role="alert"
data-slot="field-error"
class={cn("text-destructive text-sm font-normal", className)}
{...restProps}
>
{#if children}
{@render children()}
{:else if singleErrorMessage}
{singleErrorMessage}
{:else if isMultipleErrors}
<ul class="ml-4 flex list-disc flex-col gap-1">
{#each errors ?? [] as error, index (index)}
{#if error?.message}
<li>{error.message}</li>
{/if}
{/each}
</ul>
{/if}
</div>
{/if}

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { cn, type WithElementRef } from "#lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="field-group"
class={cn(
"gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4 group/field-group @container/field-group flex w-full flex-col",
className
)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import { Label } from "#lib/components/ui/label/index.js";
import { cn } from "#lib/utils.js";
import type { ComponentProps } from "svelte";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: ComponentProps<typeof Label> = $props();
</script>
<Label
bind:ref
data-slot="field-label"
class={cn(
"has-data-checked:bg-primary/5 has-data-checked:border-primary/30 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10 gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 *:data-[slot=field]:p-3 group/field-label peer/field-label flex w-fit",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
className
)}
{...restProps}
>
{@render children?.()}
</Label>

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import { cn, type WithElementRef } from "#lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
variant = "legend",
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLLegendElement>> & {
variant?: "legend" | "label";
} = $props();
</script>
<legend
bind:this={ref}
data-slot="field-legend"
data-variant={variant}
class={cn("mb-3 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base", className)}
{...restProps}
>
{@render children?.()}
</legend>

View File

@@ -0,0 +1,35 @@
<script lang="ts">
import { Separator } from "#lib/components/ui/separator/index.js";
import { cn, type WithElementRef } from "#lib/utils.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
children?: Snippet;
} = $props();
const hasContent = $derived(!!children);
</script>
<div
bind:this={ref}
data-slot="field-separator"
data-content={hasContent}
class={cn("-my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2 relative", className)}
{...restProps}
>
<Separator class="absolute inset-0 top-1/2" />
{#if children}
<span
class="text-muted-foreground px-2 relative mx-auto block w-fit bg-background"
data-slot="field-separator-content"
>
{@render children()}
</span>
{/if}
</div>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "#lib/utils.js";
import type { HTMLFieldsetAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLFieldsetAttributes> = $props();
</script>
<fieldset
bind:this={ref}
data-slot="field-set"
class={cn("gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col", className)}
{...restProps}
>
{@render children?.()}
</fieldset>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { cn, type WithElementRef } from "#lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="field-title"
class={cn("gap-2 leading-snug text-sm font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,47 @@
<script lang="ts" module>
import { tv, type VariantProps } from "tailwind-variants";
export const fieldVariants = tv({
base: "data-[invalid=true]:text-destructive gap-3 group/field flex w-full",
variants: {
orientation: {
vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
horizontal:
"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
responsive:
"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
},
},
defaultVariants: {
orientation: "vertical",
},
});
export type FieldOrientation = VariantProps<typeof fieldVariants>["orientation"];
</script>
<script lang="ts">
import { cn, type WithElementRef } from "#lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
orientation = "vertical",
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
orientation?: FieldOrientation;
} = $props();
</script>
<div
bind:this={ref}
role="group"
data-slot="field"
data-orientation={orientation}
class={cn(fieldVariants({ orientation }), className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,33 @@
import Content from "./field-content.svelte";
import Description from "./field-description.svelte";
import Error from "./field-error.svelte";
import Group from "./field-group.svelte";
import Label from "./field-label.svelte";
import Legend from "./field-legend.svelte";
import Separator from "./field-separator.svelte";
import Set from "./field-set.svelte";
import Title from "./field-title.svelte";
import Field from "./field.svelte";
export {
Field,
Set,
Legend,
Group,
Content,
Label,
Title,
Description,
Separator,
Error,
//
Set as FieldSet,
Legend as FieldLegend,
Group as FieldGroup,
Content as FieldContent,
Label as FieldLabel,
Title as FieldTitle,
Description as FieldDescription,
Separator as FieldSeparator,
Error as FieldError,
};

View File

@@ -10,6 +10,8 @@
<link rel="icon" href={favicon} /> <link rel="icon" href={favicon} />
</svelte:head> </svelte:head>
<div class="p-4"> <div class="w-full h-screen flex">
{@render children()} <div class="content-center mx-auto">
{@render children()}
</div>
</div> </div>

View File

@@ -1,3 +1,13 @@
<script lang="ts">
import { redirect } from "@sveltejs/kit";
const firstSetup: boolean = false;
if (firstSetup) {
redirect(302, '/setup')
}
</script>
<div class="h-screen flex flex-col"> <div class="h-screen flex flex-col">
<div> <div>
<h1>Welcome to SvelteKit</h1> <h1>Welcome to SvelteKit</h1>

View File

@@ -0,0 +1,7 @@
<script>
import SetupForm from "#lib/components/forms/SetupForm.svelte";
</script>
<div>
<SetupForm />
</div>