Add a site-wide dark mode
Build and Push to Digital Ocean Container Registry / build-and-push (push) Failing after 4s
Build and Push to Digital Ocean Container Registry / migrate (push) Skipped

This commit is contained in:
NeilClack
2026-09-11 12:49:46 -05:00
parent a909db39b1
commit 960673bbc6
28 changed files with 745 additions and 340 deletions
+13
View File
@@ -3,6 +3,19 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
(function () {
try {
const theme = localStorage.getItem('theme');
const dark =
theme === 'dark' ||
(!theme && window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList.toggle('dark', dark);
} catch (e) {
// ignore
}
})();
</script>
%sveltekit.head%
</head>
+2 -2
View File
@@ -14,9 +14,9 @@
<section class="mt-8">
<div>
<h2 class="text-lg font-semibold text-slate-900">{title}</h2>
<h2 class="text-lg font-semibold text-slate-900 dark:text-slate-100">{title}</h2>
{#if description}
<p class="mt-1 text-sm text-slate-500">{description}</p>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">{description}</p>
{/if}
</div>
<div class="mt-4">
+14 -10
View File
@@ -95,14 +95,14 @@
<div class="mx-auto w-full">
<div class="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<h2 class="text-xl font-semibold text-slate-900">{title}</h2>
<h2 class="text-xl font-semibold text-slate-900 dark:text-slate-100">{title}</h2>
<div class="flex w-full flex-col gap-3 sm:w-auto sm:flex-row sm:items-center">
<input
type="search"
bind:value={search}
placeholder={searchPlaceholder}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none sm:w-64"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none sm:w-64 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if onNew}
<button
@@ -128,11 +128,13 @@
</div>
</div>
<div class="w-full overflow-x-auto rounded-lg border border-slate-200 bg-white shadow-sm">
<table class="w-full min-w-max text-left text-sm text-slate-700">
<div
class="w-full overflow-x-auto rounded-lg border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
>
<table class="w-full min-w-max text-left text-sm text-slate-700 dark:text-slate-300">
<thead>
<tr
class="border-b border-slate-200 bg-slate-100 text-xs font-semibold tracking-wide text-slate-500"
class="border-b border-slate-200 bg-slate-100 text-xs font-semibold tracking-wide text-slate-500 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400"
>
{#if draggable}
<th scope="col" class="w-10 px-4 py-3"></th>
@@ -145,8 +147,10 @@
<tbody>
{#each rows as record, i (rowKey(record, i))}
<tr
class="transition {i % 2 === 0 ? 'bg-white' : 'bg-slate-50'} {onRowClick
? 'cursor-pointer hover:bg-indigo-50'
class="transition {i % 2 === 0
? 'bg-white dark:bg-slate-900'
: 'bg-slate-50 dark:bg-slate-950'} {onRowClick
? 'cursor-pointer hover:bg-indigo-50 dark:hover:bg-indigo-950'
: ''}"
{draggable}
ondragstart={(e) => handleDragStart(e, i)}
@@ -158,7 +162,7 @@
{#if draggable}
<td class="px-4 py-3" onclick={(e) => e.stopPropagation()} aria-hidden="true">
<span
class="cursor-grab text-slate-400 select-none active:cursor-grabbing"
class="cursor-grab text-slate-400 select-none active:cursor-grabbing dark:text-slate-500"
title="Drag to reorder">⠿</span
>
</td>
@@ -175,7 +179,7 @@
copyValue(column.value(record));
}}
title="Copy to clipboard"
class="ml-1 inline-flex text-slate-400 transition hover:text-indigo-600"
class="ml-1 inline-flex text-slate-400 transition hover:text-indigo-600 dark:text-slate-500 dark:hover:text-indigo-400"
>
<svg
viewBox="0 0 16 16"
@@ -203,7 +207,7 @@
<tr>
<td
colspan={columns.length + (draggable ? 1 : 0)}
class="bg-white px-4 py-10 text-center text-slate-500"
class="bg-white dark:bg-slate-900 px-4 py-10 text-center text-slate-500 dark:text-slate-400"
>
No results found.
</td>
+5 -5
View File
@@ -53,18 +53,18 @@
role="dialog"
aria-modal="true"
aria-label={title}
class="relative z-10 flex max-h-[90vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl"
class="relative z-10 flex max-h-[90vh] w-full max-w-2xl flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl dark:border-slate-800 dark:bg-slate-900"
>
<div
class="flex shrink-0 items-center justify-between gap-3 border-b border-slate-200 px-6 py-4"
class="flex shrink-0 items-center justify-between gap-3 border-b border-slate-200 px-6 py-4 dark:border-slate-800"
>
<h2 class="text-lg font-bold text-slate-900">{title}</h2>
<h2 class="text-lg font-bold text-slate-900 dark:text-slate-100">{title}</h2>
<div class="flex items-center gap-2">
{#if canDelete && recordId}
<button
type="button"
onclick={confirmDelete}
class="rounded-md bg-red-50 px-3 py-1.5 text-sm font-medium text-red-600 ring-1 ring-red-200 transition ring-inset hover:bg-red-600 hover:text-white"
class="rounded-md bg-red-50 px-3 py-1.5 text-sm font-medium text-red-600 ring-1 ring-red-200 transition ring-inset hover:bg-red-600 hover:text-white dark:bg-red-950/50 dark:text-red-400 dark:ring-red-900"
>
Delete
</button>
@@ -73,7 +73,7 @@
type="button"
onclick={onClose}
aria-label="Close"
class="rounded-md p-1.5 text-2xl leading-none text-slate-400 transition hover:bg-slate-100 hover:text-red-600"
class="rounded-md p-1.5 text-2xl leading-none text-slate-400 transition hover:bg-slate-100 hover:text-red-600 dark:text-slate-500 dark:hover:bg-slate-800"
>
&times;
</button>
+3 -3
View File
@@ -26,15 +26,15 @@
{#if visible}
<div
role="alert"
class="fixed right-4 bottom-4 z-50 flex items-center gap-3 rounded-lg border border-slate-700 bg-slate-900 px-4 py-3 text-sm text-white shadow-lg"
class="fixed right-4 bottom-4 z-50 flex items-center gap-3 rounded-lg border border-slate-700 bg-slate-900 px-4 py-3 text-sm text-white shadow-lg dark:border-slate-300 dark:bg-slate-950"
>
<span>{message}</span>
<span class="text-xs text-slate-400">{remaining}s</span>
<span class="text-xs text-slate-400 dark:text-slate-500">{remaining}s</span>
<button
type="button"
onclick={dismiss}
aria-label="Dismiss"
class="rounded p-1 text-slate-400 transition hover:text-white"
class="rounded p-1 text-slate-400 transition hover:text-white dark:text-slate-500"
>
&times;
</button>
+3 -1
View File
@@ -3,7 +3,9 @@
</script>
<div class="mx-auto w-full max-w-md px-4 py-8 sm:py-12">
<div class="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm sm:p-8">
<div
class="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm sm:p-8 dark:border-slate-800 dark:bg-slate-900"
>
{@render children()}
</div>
</div>
@@ -16,8 +16,12 @@
<div>
<div>
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">Dashboard</h1>
<p class="mt-1 text-sm text-slate-500 sm:text-base">Manage your account and license keys.</p>
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-slate-100">
Dashboard
</h1>
<p class="mt-1 text-sm text-slate-500 sm:text-base dark:text-slate-400">
Manage your account and license keys.
</p>
</div>
<!--
@@ -31,11 +35,13 @@
<DashboardSection title="License Keys" description="Your active and inactive license keys.">
{#if data.licenseKeys.length > 0}
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
<div
class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
>
<!-- Desktop / tablet: table -->
<table class="hidden w-full text-left text-sm sm:table">
<thead
class="border-b border-slate-200 bg-slate-50 text-xs tracking-wide text-slate-500 uppercase"
class="border-b border-slate-200 bg-slate-50 text-xs tracking-wide text-slate-500 uppercase dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400"
>
<tr>
<th class="px-6 py-3 font-semibold">Key</th>
@@ -46,19 +52,27 @@
</thead>
<tbody class="divide-y divide-slate-100">
{#each data.licenseKeys as key (key.key)}
<tr class="hover:bg-slate-50">
<td class="px-6 py-4 font-mono text-slate-800">{key.key}</td>
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800">
<td class="px-6 py-4 font-mono text-slate-800 dark:text-slate-200">{key.key}</td>
<td class="px-6 py-4">
{#if key.active}
<span class="text-lg text-green-600" title="Active" aria-label="Active">☑</span>
<span
class="text-lg text-green-600 dark:text-green-400"
title="Active"
aria-label="Active">☑</span
>
{:else}
<span class="text-lg text-slate-300" title="Inactive" aria-label="Inactive"
>☐</span
<span
class="text-lg text-slate-300 dark:text-slate-600"
title="Inactive"
aria-label="Inactive">☐</span
>
{/if}
</td>
<td class="px-6 py-4 text-slate-600">{formatDate(key.createdAt)}</td>
<td class="px-6 py-4 text-slate-600">
<td class="px-6 py-4 text-slate-600 dark:text-slate-400"
>{formatDate(key.createdAt)}</td
>
<td class="px-6 py-4 text-slate-600 dark:text-slate-400">
{key.lastValidatedAt ? formatDate(key.lastValidatedAt) : 'Never'}
</td>
</tr>
@@ -71,19 +85,29 @@
{#each data.licenseKeys as key (key.key)}
<li class="flex flex-col gap-2 px-4 py-4">
<div class="flex items-center justify-between gap-4">
<span class="min-w-0 font-mono text-sm break-all text-slate-800">{key.key}</span>
<span class="min-w-0 font-mono text-sm break-all text-slate-800 dark:text-slate-200"
>{key.key}</span
>
<span class="shrink-0">
{#if key.active}
<span class="text-lg text-green-600" title="Active" aria-label="Active">☑</span>
<span
class="text-lg text-green-600 dark:text-green-400"
title="Active"
aria-label="Active">☑</span
>
{:else}
<span class="text-lg text-slate-300" title="Inactive" aria-label="Inactive"
>☐</span
<span
class="text-lg text-slate-300 dark:text-slate-600"
title="Inactive"
aria-label="Inactive">☐</span
>
{/if}
</span>
</div>
<span class="text-xs text-slate-500">Created {formatDate(key.createdAt)}</span>
<span class="text-xs text-slate-500">
<span class="text-xs text-slate-500 dark:text-slate-400"
>Created {formatDate(key.createdAt)}</span
>
<span class="text-xs text-slate-500 dark:text-slate-400">
Last verified {key.lastValidatedAt ? formatDate(key.lastValidatedAt) : 'Never'}
</span>
</li>
@@ -91,8 +115,12 @@
</ul>
</div>
{:else}
<div class="rounded-2xl border border-dashed border-slate-300 bg-white p-8 text-center">
<p class="text-sm text-slate-600">You don't have any license keys yet.</p>
<div
class="rounded-2xl border border-dashed border-slate-300 bg-white p-8 text-center dark:border-slate-700 dark:bg-slate-900"
>
<p class="text-sm text-slate-600 dark:text-slate-400">
You don't have any license keys yet.
</p>
</div>
{/if}
</DashboardSection>
@@ -62,26 +62,40 @@
</script>
<div class="mx-auto w-full max-w-3xl">
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">Profile</h1>
<p class="mt-1 text-sm text-slate-500 sm:text-base">Manage your account details and addresses.</p>
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-slate-100">
Profile
</h1>
<p class="mt-1 text-sm text-slate-500 sm:text-base dark:text-slate-400">
Manage your account details and addresses.
</p>
<!-- Email (read-only) -->
<div class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6">
<p class="text-xs tracking-wide text-slate-500 uppercase">Email</p>
<p class="mt-1 text-base break-words text-slate-900">{data.user.email}</p>
<div
class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6 dark:border-slate-800 dark:bg-slate-900"
>
<p class="text-xs tracking-wide text-slate-500 uppercase dark:text-slate-400">Email</p>
<p class="mt-1 text-base break-words text-slate-900 dark:text-slate-100">{data.user.email}</p>
</div>
<!-- Profile Form -->
<section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6">
<h2 class="text-lg font-semibold text-slate-900">Profile</h2>
<section
class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6 dark:border-slate-800 dark:bg-slate-900"
>
<h2 class="text-lg font-semibold text-slate-900 dark:text-slate-100">Profile</h2>
{#if form?.action === 'updateProfile' && form?.success}
<div class="mt-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800" role="status">
<div
class="mt-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800 dark:bg-green-950/50 dark:text-green-300"
role="status"
>
Profile updated successfully.
</div>
{/if}
{#if form?.action === 'updateProfile' && form?.error}
<div class="mt-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700" role="alert">
<div
class="mt-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
role="alert"
>
{form.error}
</div>
{/if}
@@ -89,30 +103,36 @@
<form method="POST" action="?/updateProfile" class="mt-5 flex flex-col gap-4">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-1.5">
<label for="firstName" class="text-sm font-medium text-slate-700"> First Name </label>
<label for="firstName" class="text-sm font-medium text-slate-700 dark:text-slate-300">
First Name
</label>
<input
type="text"
id="firstName"
name="firstName"
value={data.user.profile?.firstName ?? ''}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="lastName" class="text-sm font-medium text-slate-700"> Last Name </label>
<label for="lastName" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Last Name
</label>
<input
type="text"
id="lastName"
name="lastName"
value={data.user.profile?.lastName ?? ''}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
</div>
<div class="flex flex-col gap-1.5">
<label for="phoneNumber" class="text-sm font-medium text-slate-700"> Phone Number </label>
<label for="phoneNumber" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Phone Number
</label>
<input
type="tel"
id="phoneNumber"
@@ -120,7 +140,7 @@
bind:value={phoneNumber}
oninput={handlePhoneInput}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div>
@@ -135,38 +155,49 @@
</section>
<!-- Addresses -->
<section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6">
<section
class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6 dark:border-slate-800 dark:bg-slate-900"
>
<div class="flex items-center justify-between gap-4">
<h2 class="text-lg font-semibold text-slate-900">Addresses</h2>
<h2 class="text-lg font-semibold text-slate-900 dark:text-slate-100">Addresses</h2>
<button
type="button"
onclick={() => (showAddressForm ? closeAddressForm() : openAddAddress())}
class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50"
class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
{showAddressForm ? 'Cancel' : 'Add Address'}
</button>
</div>
{#if form?.action === 'addAddress' && form?.success}
<div class="mt-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800" role="status">
<div
class="mt-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800 dark:bg-green-950/50 dark:text-green-300"
role="status"
>
Address added successfully.
</div>
{/if}
{#if form?.action === 'updateAddress' && form?.success}
<div class="mt-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800" role="status">
<div
class="mt-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800 dark:bg-green-950/50 dark:text-green-300"
role="status"
>
Address updated successfully.
</div>
{/if}
{#if form?.action === 'deleteAddress' && form?.success}
<div class="mt-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800" role="status">
<div
class="mt-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800 dark:bg-green-950/50 dark:text-green-300"
role="status"
>
Address deleted successfully.
</div>
{/if}
{#if (form?.action === 'addAddress' ||
form?.action === 'updateAddress' ||
form?.action === 'deleteAddress') &&
form?.error}
<div class="mt-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700" role="alert">
{#if (form?.action === 'addAddress' || form?.action === 'updateAddress' || form?.action === 'deleteAddress') && form?.error}
<div
class="mt-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
role="alert"
>
{form.error}
</div>
{/if}
@@ -181,61 +212,71 @@
<input type="hidden" name="id" value={editingAddressId} />
{/if}
<div class="flex flex-col gap-1.5">
<label for="street" class="text-sm font-medium text-slate-700">Street</label>
<label for="street" class="text-sm font-medium text-slate-700 dark:text-slate-300"
>Street</label
>
<input
type="text"
id="street"
name="street"
bind:value={addrStreet}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-1.5">
<label for="city" class="text-sm font-medium text-slate-700">City</label>
<label for="city" class="text-sm font-medium text-slate-700 dark:text-slate-300"
>City</label
>
<input
type="text"
id="city"
name="city"
bind:value={addrCity}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="state" class="text-sm font-medium text-slate-700">State</label>
<label for="state" class="text-sm font-medium text-slate-700 dark:text-slate-300"
>State</label
>
<input
type="text"
id="state"
name="state"
bind:value={addrState}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-1.5">
<label for="postalCode" class="text-sm font-medium text-slate-700"> Postal Code </label>
<label for="postalCode" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Postal Code
</label>
<input
type="text"
id="postalCode"
name="postalCode"
bind:value={addrPostal}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="country" class="text-sm font-medium text-slate-700">Country</label>
<label for="country" class="text-sm font-medium text-slate-700 dark:text-slate-300"
>Country</label
>
<input
type="text"
id="country"
name="country"
bind:value={addrCountry}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
</div>
@@ -253,18 +294,20 @@
{#if data.user.profile?.addresses && data.user.profile.addresses.length > 0}
<div class="mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2">
{#each data.user.profile.addresses as address (address.id)}
<div class="rounded-lg border border-slate-200 bg-slate-50 p-4">
<p class="text-sm font-medium text-slate-900">{address.street}</p>
<p class="mt-1 text-sm text-slate-600">
<div
class="rounded-lg border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-950"
>
<p class="text-sm font-medium text-slate-900 dark:text-slate-100">{address.street}</p>
<p class="mt-1 text-sm text-slate-600 dark:text-slate-400">
{address.city}, {address.state}
{address.postalCode}
</p>
<p class="text-sm text-slate-600">{address.country}</p>
<p class="text-sm text-slate-600 dark:text-slate-400">{address.country}</p>
<div class="mt-3 flex gap-2">
<button
type="button"
onclick={() => openEditAddress(address)}
class="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Edit
</button>
@@ -278,7 +321,7 @@
<input type="hidden" name="id" value={address.id} />
<button
type="submit"
class="rounded-md border border-red-300 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50"
class="rounded-md border border-red-300 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 dark:border-red-800 dark:bg-slate-900 dark:text-red-400 dark:hover:bg-red-950/50"
>
Delete
</button>
@@ -288,7 +331,7 @@
{/each}
</div>
{:else if !showAddressForm}
<p class="mt-5 text-sm text-slate-500">No addresses on file.</p>
<p class="mt-5 text-sm text-slate-500 dark:text-slate-400">No addresses on file.</p>
{/if}
</section>
</div>
@@ -43,7 +43,7 @@
<Window>
<form method="POST" class="flex flex-col items-center gap-5">
<h1 class="text-center text-2xl font-bold text-slate-900">
<h1 class="text-center text-2xl font-bold text-slate-900 dark:text-slate-100">
Please finish setting up your profile
</h1>
@@ -55,44 +55,50 @@
/>
{:else}
<div
class="flex h-24 w-24 items-center justify-center rounded-full bg-indigo-100 text-3xl font-semibold text-indigo-700 sm:h-28 sm:w-28"
class="flex h-24 w-24 items-center justify-center rounded-full bg-indigo-100 text-3xl font-semibold text-indigo-700 sm:h-28 sm:w-28 dark:bg-indigo-900 dark:text-indigo-300"
>
{data.user?.name?.charAt(0)?.toUpperCase() ?? '?'}
</div>
{/if}
<p class="text-base text-slate-900">{data.user?.email}</p>
<p class="text-base text-slate-900 dark:text-slate-100">{data.user?.email}</p>
<div class="flex w-full flex-col gap-4">
<input type="hidden" name="userId" value={data.user?.id} />
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-1.5">
<label for="firstName" class="text-sm font-medium text-slate-700"> First Name </label>
<label for="firstName" class="text-sm font-medium text-slate-700 dark:text-slate-300">
First Name
</label>
<input
id="firstName"
type="text"
name="firstName"
placeholder="First Name"
bind:value={firstName}
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="lastName" class="text-sm font-medium text-slate-700"> Last Name </label>
<label for="lastName" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Last Name
</label>
<input
id="lastName"
type="text"
name="lastName"
placeholder="Last Name"
bind:value={lastName}
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
</div>
<div class="flex flex-col gap-1.5">
<label for="phoneNumber" class="text-sm font-medium text-slate-700"> Phone Number </label>
<label for="phoneNumber" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Phone Number
</label>
<input
id="phoneNumber"
type="tel"
@@ -100,7 +106,7 @@
placeholder="(555) 555-5555"
bind:value={phoneNumber}
oninput={handlePhoneInput}
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
</div>
+1 -1
View File
@@ -4,7 +4,7 @@
let { children }: { children: Snippet } = $props();
</script>
<div class="min-h-screen bg-slate-50 text-slate-900">
<div class="min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
<main class="mx-auto w-full max-w-6xl px-4 py-6 sm:px-6 sm:py-8">
{@render children()}
</main>
@@ -24,19 +24,26 @@
<div class="mx-auto w-full">
<div class="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
<div>
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">Audit Log</h1>
<p class="mt-1 text-sm text-slate-500">
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-slate-100">
Audit Log
</h1>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
Every database change, newest first. You can see activity from roles beneath your own.
</p>
</div>
<p class="text-sm text-slate-500">{first}–{last} of {data.total}</p>
<p class="text-sm text-slate-500 dark:text-slate-400">{first}–{last} of {data.total}</p>
</div>
<!-- Filters -->
<form method="GET" class="mt-6 rounded-2xl border border-slate-200 bg-white p-4 shadow-sm">
<form
method="GET"
class="mt-6 rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900"
>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
<div class="flex flex-col gap-1">
<label for="q" class="text-xs font-medium tracking-wide text-slate-500 uppercase"
<label
for="q"
class="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400"
>Search</label
>
<input
@@ -45,19 +52,21 @@
type="search"
value={data.filters.q}
placeholder="User, email, page, action…"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1">
<label for="role" class="text-xs font-medium tracking-wide text-slate-500 uppercase"
<label
for="role"
class="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400"
>Role</label
>
<select
id="role"
name="role"
value={data.filters.role}
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
>
<option value="">All roles</option>
{#each data.roleOptions as role (role.id)}
@@ -67,14 +76,16 @@
</div>
<div class="flex flex-col gap-1">
<label for="action" class="text-xs font-medium tracking-wide text-slate-500 uppercase"
<label
for="action"
class="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400"
>Action</label
>
<select
id="action"
name="action"
value={data.filters.action}
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
>
<option value="">All actions</option>
{#each data.actionOptions as action (action)}
@@ -84,14 +95,16 @@
</div>
<div class="flex flex-col gap-1">
<label for="entity" class="text-xs font-medium tracking-wide text-slate-500 uppercase"
<label
for="entity"
class="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400"
>Entity</label
>
<select
id="entity"
name="entity"
value={data.filters.entity}
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
>
<option value="">All entities</option>
{#each data.entityOptions as entity (entity)}
@@ -101,7 +114,9 @@
</div>
<div class="flex flex-col gap-1">
<label for="page" class="text-xs font-medium tracking-wide text-slate-500 uppercase"
<label
for="page"
class="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400"
>Page</label
>
<input
@@ -110,12 +125,14 @@
type="text"
value={data.filters.page}
placeholder="/admin/users"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1">
<label for="from" class="text-xs font-medium tracking-wide text-slate-500 uppercase"
<label
for="from"
class="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400"
>From</label
>
<input
@@ -123,31 +140,36 @@
name="from"
type="date"
value={data.filters.from}
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1">
<label for="to" class="text-xs font-medium tracking-wide text-slate-500 uppercase">To</label
<label
for="to"
class="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400"
>To</label
>
<input
id="to"
name="to"
type="date"
value={data.filters.to}
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1">
<label for="sort" class="text-xs font-medium tracking-wide text-slate-500 uppercase"
<label
for="sort"
class="text-xs font-medium tracking-wide text-slate-500 uppercase dark:text-slate-400"
>Order</label
>
<select
id="sort"
name="sort"
value={data.filters.sort}
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-10 rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
>
<option value="desc">Newest first</option>
<option value="asc">Oldest first</option>
@@ -158,7 +180,7 @@
<div class="mt-4 flex items-center justify-end gap-3">
<a
href={resolve('/admin/audit-log')}
class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Clear
</a>
@@ -172,11 +194,13 @@
</form>
<!-- Results -->
<div class="mt-6 overflow-x-auto rounded-2xl border border-slate-200 bg-white shadow-sm">
<table class="w-full min-w-max text-left text-sm text-slate-700">
<div
class="mt-6 overflow-x-auto rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
>
<table class="w-full min-w-max text-left text-sm text-slate-700 dark:text-slate-300">
<thead>
<tr
class="border-b border-slate-200 bg-slate-50 text-xs font-semibold tracking-wide text-slate-500 uppercase"
class="border-b border-slate-200 bg-slate-50 text-xs font-semibold tracking-wide text-slate-500 uppercase dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400"
>
<th scope="col" class="px-4 py-3">When</th>
<th scope="col" class="px-4 py-3">User</th>
@@ -191,44 +215,47 @@
</thead>
<tbody class="divide-y divide-slate-100">
{#each data.records as record (record.id)}
<tr class="align-top hover:bg-slate-50">
<td class="px-4 py-3 whitespace-nowrap text-slate-500">
<tr class="align-top hover:bg-slate-50 dark:hover:bg-slate-800">
<td class="px-4 py-3 whitespace-nowrap text-slate-500 dark:text-slate-400">
{formatWhen(record.createdAt)}
</td>
<td class="px-4 py-3 font-mono text-xs text-slate-600">
<td class="px-4 py-3 font-mono text-xs text-slate-600 dark:text-slate-400">
{record.userId ?? '—'}
</td>
<td class="px-4 py-3 text-slate-700">{record.email ?? '—'}</td>
<td class="px-4 py-3 text-slate-700 dark:text-slate-300">{record.email ?? '—'}</td>
<td class="px-4 py-3">
<span
class="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-medium text-slate-700"
class="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-medium text-slate-700 dark:bg-slate-800 dark:text-slate-300"
>
{record.roleName ?? (record.roleId != null ? `Role ${record.roleId}` : 'System')}
</span>
</td>
<td class="px-4 py-3 font-mono text-xs text-slate-600">{record.page}</td>
<td class="px-4 py-3 font-mono text-xs text-slate-600 dark:text-slate-400"
>{record.page}</td
>
<td class="px-4 py-3">
<span
class="inline-flex items-center rounded-full bg-indigo-50 px-2.5 py-0.5 text-xs font-medium text-indigo-700"
class="inline-flex items-center rounded-full bg-indigo-50 px-2.5 py-0.5 text-xs font-medium text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300"
>
{record.action}
</span>
</td>
<td class="px-4 py-3 text-slate-700">
{record.entity ?? '—'}{#if record.entityId}<span class="text-slate-400"
>#{record.entityId}</span
<td class="px-4 py-3 text-slate-700 dark:text-slate-300">
{record.entity ?? '—'}{#if record.entityId}<span
class="text-slate-400 dark:text-slate-500">#{record.entityId}</span
>{/if}
</td>
<td class="max-w-xs px-4 py-3">
{#if record.before == null}
<span class="text-slate-400">—</span>
<span class="text-slate-400 dark:text-slate-500">—</span>
{:else}
<details>
<summary class="cursor-pointer text-xs text-indigo-600 hover:text-indigo-700"
<summary
class="cursor-pointer text-xs text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
>view</summary
>
<pre
class="mt-2 max-h-64 overflow-auto rounded-md bg-slate-50 p-2 text-xs whitespace-pre-wrap text-slate-700">{formatJson(
class="mt-2 max-h-64 overflow-auto rounded-md bg-slate-50 p-2 text-xs whitespace-pre-wrap text-slate-700 dark:bg-slate-950 dark:text-slate-300">{formatJson(
record.before
)}</pre>
</details>
@@ -236,14 +263,15 @@
</td>
<td class="max-w-xs px-4 py-3">
{#if record.after == null}
<span class="text-slate-400">—</span>
<span class="text-slate-400 dark:text-slate-500">—</span>
{:else}
<details>
<summary class="cursor-pointer text-xs text-indigo-600 hover:text-indigo-700"
<summary
class="cursor-pointer text-xs text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
>view</summary
>
<pre
class="mt-2 max-h-64 overflow-auto rounded-md bg-slate-50 p-2 text-xs whitespace-pre-wrap text-slate-700">{formatJson(
class="mt-2 max-h-64 overflow-auto rounded-md bg-slate-50 p-2 text-xs whitespace-pre-wrap text-slate-700 dark:bg-slate-950 dark:text-slate-300">{formatJson(
record.after
)}</pre>
</details>
@@ -252,7 +280,9 @@
</tr>
{:else}
<tr>
<td colspan="9" class="px-4 py-12 text-center text-slate-500">No events found.</td>
<td colspan="9" class="px-4 py-12 text-center text-slate-500 dark:text-slate-400"
>No events found.</td
>
</tr>
{/each}
</tbody>
@@ -262,7 +292,9 @@
<!-- Pagination -->
{#if data.totalPages > 1}
<div class="mt-4 flex items-center justify-between">
<p class="text-sm text-slate-500">Page {data.page} of {data.totalPages}</p>
<p class="text-sm text-slate-500 dark:text-slate-400">
Page {data.page} of {data.totalPages}
</p>
<form method="GET" action={resolve('/admin/audit-log')} class="flex gap-2">
{#if data.filters.q}<input type="hidden" name="q" value={data.filters.q} />{/if}
{#if data.filters.role}<input type="hidden" name="role" value={data.filters.role} />{/if}
@@ -285,7 +317,7 @@
type="submit"
name="p"
value={data.page - 1}
class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Previous
</button>
@@ -295,7 +327,7 @@
type="submit"
name="p"
value={data.page + 1}
class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-lg border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Next
</button>
@@ -133,20 +133,25 @@
<AdminModal title="Create License Key" open={createOpen} onClose={closeCreate}>
{#if createSuccess}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{createSuccess}
</div>
{/if}
{#if createError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{createError}
</div>
{/if}
<form method="POST" action="?/createKey" use:enhance class="flex flex-col gap-4">
<div>
<label for="field-email" class="mb-1 block text-sm font-medium text-slate-700">Email</label>
<label
for="field-email"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Email</label
>
<input
id="field-email"
type="email"
@@ -154,9 +159,9 @@
required
bind:value={email}
placeholder="user@example.com"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
<p class="mt-1 text-sm text-slate-500">
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
If no account exists for this email yet, the key will be attached when they register.
</p>
</div>
@@ -165,7 +170,7 @@
<button
type="button"
onclick={closeCreate}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
@@ -182,23 +187,25 @@
<AdminModal title="Replace License Key" open={replaceOpen} onClose={closeReplace}>
{#if replaceSuccess}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{replaceSuccess}
</div>
{/if}
{#if replaceError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{replaceError}
</div>
{/if}
{#if replaceRecord}
<div class="flex flex-col gap-4">
<p class="text-sm text-slate-700">
<p class="text-sm text-slate-700 dark:text-slate-300">
Generate a new key to replace <strong>{String(replaceRecord.key)}</strong>?
</p>
<p class="text-sm text-slate-500">
<p class="text-sm text-slate-500 dark:text-slate-400">
The existing key will be deleted and a new one will be created.
</p>
@@ -208,7 +215,7 @@
<button
type="button"
onclick={closeReplace}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
@@ -116,13 +116,15 @@
>
{#if successMessage}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{successMessage}
</div>
{/if}
{#if formError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{formError}
</div>
{/if}
@@ -133,17 +135,20 @@
{/if}
<div>
<label for="field-name" class="mb-1 block text-sm font-medium text-slate-700">Name</label>
<label
for="field-name"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name</label
>
<input
id="field-name"
type="text"
name="name"
required
bind:value={name}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.name}
<p class="mt-1 text-sm text-red-600">{fieldErrors.name}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.name}</p>
{/if}
</div>
@@ -151,7 +156,7 @@
<button
type="button"
onclick={closeModal}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
@@ -116,13 +116,15 @@
>
{#if successMessage}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{successMessage}
</div>
{/if}
{#if formError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{formError}
</div>
{/if}
@@ -133,17 +135,20 @@
{/if}
<div>
<label for="field-name" class="mb-1 block text-sm font-medium text-slate-700">Name</label>
<label
for="field-name"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name</label
>
<input
id="field-name"
type="text"
name="name"
required
bind:value={name}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.name}
<p class="mt-1 text-sm text-red-600">{fieldErrors.name}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.name}</p>
{/if}
</div>
@@ -151,7 +156,7 @@
<button
type="button"
onclick={closeModal}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
@@ -192,13 +192,15 @@
>
{#if successMessage}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{successMessage}
</div>
{/if}
{#if formError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{formError}
</div>
{/if}
@@ -209,31 +211,34 @@
{/if}
<div>
<label for="field-name" class="mb-1 block text-sm font-medium text-slate-700">Name</label>
<label
for="field-name"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name</label
>
<input
id="field-name"
type="text"
name="name"
required
bind:value={name}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.name}
<p class="mt-1 text-sm text-red-600">{fieldErrors.name}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.name}</p>
{/if}
</div>
<div>
<label
for="field-solfeggioFrequencyId"
class="mb-1 block text-sm font-medium text-slate-700">Frequency</label
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Frequency</label
>
<select
id="field-solfeggioFrequencyId"
name="solfeggioFrequencyId"
required
bind:value={solfeggioFrequencyId}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
>
<option value="">-- Select Frequency --</option>
{#each data.frequencies as freq (String(freq.id))}
@@ -241,20 +246,23 @@
{/each}
</select>
{#if fieldErrors.solfeggioFrequencyId}
<p class="mt-1 text-sm text-red-600">{fieldErrors.solfeggioFrequencyId}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">
{fieldErrors.solfeggioFrequencyId}
</p>
{/if}
</div>
<div>
<label for="field-categoryId" class="mb-1 block text-sm font-medium text-slate-700"
>Category</label
<label
for="field-categoryId"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Category</label
>
<select
id="field-categoryId"
name="categoryId"
required
bind:value={categoryId}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
>
<option value="">-- Select Category --</option>
{#each data.categories as cat (String(cat.id))}
@@ -262,20 +270,21 @@
{/each}
</select>
{#if fieldErrors.categoryId}
<p class="mt-1 text-sm text-red-600">{fieldErrors.categoryId}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.categoryId}</p>
{/if}
</div>
<div>
<label for="field-groupId" class="mb-1 block text-sm font-medium text-slate-700"
>Group</label
<label
for="field-groupId"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Group</label
>
<select
id="field-groupId"
name="groupId"
required
bind:value={groupId}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
>
<option value="">-- Select Group --</option>
{#each data.groups as group (String(group.id))}
@@ -283,42 +292,45 @@
{/each}
</select>
{#if fieldErrors.groupId}
<p class="mt-1 text-sm text-red-600">{fieldErrors.groupId}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.groupId}</p>
{/if}
</div>
<div>
<label for="field-content" class="mb-1 block text-sm font-medium text-slate-700"
>Content</label
<label
for="field-content"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Content</label
>
<div class="rounded-md border border-slate-300 bg-white">
<div class="flex gap-1 border-b border-slate-200 px-2 py-1.5">
<div
class="rounded-md border border-slate-300 bg-white dark:border-slate-700 dark:bg-slate-900"
>
<div class="flex gap-1 border-b border-slate-200 px-2 py-1.5 dark:border-slate-800">
<button
type="button"
onclick={toggleBold}
title="Bold"
class="rounded px-2 py-1 text-sm font-bold text-slate-600 transition hover:bg-slate-100"
class="rounded px-2 py-1 text-sm font-bold text-slate-600 transition hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-800"
>B</button
>
<button
type="button"
onclick={toggleItalic}
title="Italic"
class="rounded px-2 py-1 text-sm text-slate-600 italic transition hover:bg-slate-100"
class="rounded px-2 py-1 text-sm text-slate-600 italic transition hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-800"
>I</button
>
<button
type="button"
onclick={toggleHighlight}
title="Highlight"
class="rounded px-2 py-1 text-sm text-slate-600 transition hover:bg-slate-100"
class="rounded px-2 py-1 text-sm text-slate-600 transition hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-800"
>H</button
>
<button
type="button"
onclick={setColor}
title="Cycle text color"
class="rounded px-2 py-1 text-sm text-slate-600 transition hover:bg-slate-100"
class="rounded px-2 py-1 text-sm text-slate-600 transition hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-800"
>A</button
>
</div>
@@ -329,13 +341,13 @@
role="textbox"
tabindex="0"
contenteditable="true"
class="px-3 py-2 text-sm text-slate-900 focus:outline-none"
class="px-3 py-2 text-sm text-slate-900 focus:outline-none dark:text-slate-100"
></div>
{/key}
<input type="hidden" name="content" value={content} />
</div>
{#if fieldErrors.content}
<p class="mt-1 text-sm text-red-600">{fieldErrors.content}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.content}</p>
{/if}
</div>
@@ -343,7 +355,7 @@
<button
type="button"
onclick={closeModal}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
@@ -148,14 +148,8 @@ export const actions = {
};
}
const {
id,
profileId,
isDefaultMailing,
isDefaultBilling,
isDefaultShipping,
...addressData
} = form.data;
const { id, profileId, isDefaultMailing, isDefaultBilling, isDefaultShipping, ...addressData } =
form.data;
if (!profileId) {
return { success: false, action: 'upsertAddress', addressError: 'Missing profile ID' };
@@ -71,13 +71,15 @@
<AdminModal title="Create Profile" {open} {onClose}>
{#if successMessage}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{successMessage}
</div>
{/if}
{#if formError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{formError}
</div>
{/if}
@@ -89,7 +91,7 @@
<button
type="button"
onclick={onClose}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
@@ -156,13 +156,15 @@
<AdminModal title="Edit Profile" {open} {onClose} {canDelete} recordId={String(record.id ?? '')}>
{#if successMessage}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{successMessage}
</div>
{/if}
{#if formError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{formError}
</div>
{/if}
@@ -176,7 +178,7 @@
<button
type="button"
onclick={onClose}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
@@ -189,13 +191,13 @@
</div>
</form>
<div class="mt-8 border-t border-slate-200 pt-6">
<div class="mt-8 border-t border-slate-200 pt-6 dark:border-slate-800">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-slate-900">Addresses</h3>
<h3 class="text-sm font-semibold text-slate-900 dark:text-slate-100">Addresses</h3>
<button
type="button"
onclick={toggleAddressForm}
class="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
{addressFormOpen ? 'Cancel' : 'Add Address +'}
</button>
@@ -208,13 +210,17 @@
<input type="hidden" name="id" value={editingAddressId} />
{/if}
{#if addressError}
<div class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{addressError}
</div>
{/if}
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label for="addr-street" class="mb-1 block text-sm font-medium text-slate-700"
<label
for="addr-street"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"
>Street</label
>
<input
@@ -223,11 +229,13 @@
name="street"
required
bind:value={addrStreet}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
</div>
<div>
<label for="addr-city" class="mb-1 block text-sm font-medium text-slate-700">City</label
<label
for="addr-city"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">City</label
>
<input
id="addr-city"
@@ -235,12 +243,13 @@
name="city"
required
bind:value={addrCity}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
</div>
<div>
<label for="addr-state" class="mb-1 block text-sm font-medium text-slate-700"
>State</label
<label
for="addr-state"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">State</label
>
<input
id="addr-state"
@@ -248,11 +257,13 @@
name="state"
required
bind:value={addrState}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
</div>
<div>
<label for="addr-postal" class="mb-1 block text-sm font-medium text-slate-700"
<label
for="addr-postal"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"
>Postal Code</label
>
<input
@@ -261,11 +272,13 @@
name="postalCode"
required
bind:value={addrPostal}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
</div>
<div>
<label for="addr-country" class="mb-1 block text-sm font-medium text-slate-700"
<label
for="addr-country"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"
>Country</label
>
<input
@@ -274,11 +287,11 @@
name="country"
required
bind:value={addrCountry}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
</div>
</div>
<div class="flex flex-wrap gap-4 text-sm text-slate-700">
<div class="flex flex-wrap gap-4 text-sm text-slate-700 dark:text-slate-300">
<label class="flex items-center gap-2">
<input type="checkbox" name="isDefaultMailing" bind:checked={addrMailing} />
Default Mailing
@@ -295,7 +308,7 @@
<div class="flex justify-end">
<button
type="submit"
class="rounded-md bg-slate-800 px-4 py-2 text-sm font-medium text-white transition hover:bg-slate-700"
class="rounded-md bg-slate-800 px-4 py-2 text-sm font-medium text-white transition hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600"
>
{editingAddressId ? 'Update Address' : 'Save Address'}
</button>
@@ -304,11 +317,11 @@
{/if}
{#if addresses.length > 0}
<div class="mt-4 overflow-x-auto rounded-md border border-slate-200">
<table class="w-full min-w-max text-left text-sm text-slate-700">
<div class="mt-4 overflow-x-auto rounded-md border border-slate-200 dark:border-slate-800">
<table class="w-full min-w-max text-left text-sm text-slate-700 dark:text-slate-300">
<thead>
<tr
class="border-b border-slate-200 bg-slate-100 text-xs font-semibold tracking-wide text-slate-500"
class="border-b border-slate-200 bg-slate-100 text-xs font-semibold tracking-wide text-slate-500 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400"
>
<th scope="col" class="px-3 py-2">Street</th>
<th scope="col" class="px-3 py-2">City</th>
@@ -323,7 +336,7 @@
</thead>
<tbody>
{#each addresses as addr, i (String(addr.id ?? i))}
<tr class="bg-white">
<tr class="bg-white dark:bg-slate-900">
<td class="px-3 py-2 whitespace-nowrap">{addr.street as string}</td>
<td class="px-3 py-2 whitespace-nowrap">{addr.city as string}</td>
<td class="px-3 py-2 whitespace-nowrap">{addr.state as string}</td>
@@ -337,7 +350,7 @@
<button
type="button"
onclick={() => openEditAddress(addr)}
class="rounded-md border border-slate-300 bg-white px-2 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-2 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Edit
</button>
@@ -345,7 +358,7 @@
<input type="hidden" name="id" value={String(addr.id ?? '')} />
<button
type="submit"
class="rounded-md border border-red-300 bg-white px-2 py-1 text-xs font-medium text-red-600 transition hover:bg-red-50"
class="rounded-md border border-red-300 bg-white px-2 py-1 text-xs font-medium text-red-600 transition hover:bg-red-50 dark:border-red-800 dark:bg-slate-900 dark:text-red-400 dark:hover:bg-red-950/50"
>
Delete
</button>
@@ -358,7 +371,7 @@
</table>
</div>
{:else}
<p class="mt-2 text-sm text-slate-600">No addresses on file.</p>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">No addresses on file.</p>
{/if}
</div>
</AdminModal>
@@ -24,7 +24,10 @@
<div class="flex flex-col gap-4">
<div>
<label for="field-email" class="mb-1 block text-sm font-medium text-slate-700">Email</label>
<label
for="field-email"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Email</label
>
<input
id="field-email"
type="email"
@@ -32,16 +35,17 @@
required
bind:value={email}
placeholder="user@example.com"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.email}
<p class="mt-1 text-sm text-red-600">{fieldErrors.email}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.email}</p>
{/if}
</div>
<div>
<label for="field-firstName" class="mb-1 block text-sm font-medium text-slate-700"
>First Name</label
<label
for="field-firstName"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">First Name</label
>
<input
id="field-firstName"
@@ -49,16 +53,17 @@
name="firstName"
required
bind:value={firstName}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.firstName}
<p class="mt-1 text-sm text-red-600">{fieldErrors.firstName}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.firstName}</p>
{/if}
</div>
<div>
<label for="field-lastName" class="mb-1 block text-sm font-medium text-slate-700"
>Last Name</label
<label
for="field-lastName"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Last Name</label
>
<input
id="field-lastName"
@@ -66,15 +71,18 @@
name="lastName"
required
bind:value={lastName}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.lastName}
<p class="mt-1 text-sm text-red-600">{fieldErrors.lastName}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.lastName}</p>
{/if}
</div>
<div>
<label for="field-phoneNumber" class="mb-1 block text-sm font-medium text-slate-700">
<label
for="field-phoneNumber"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300"
>
Phone Number
</label>
<input
@@ -83,10 +91,10 @@
name="phoneNumber"
required
bind:value={phoneNumber}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.phoneNumber}
<p class="mt-1 text-sm text-red-600">{fieldErrors.phoneNumber}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.phoneNumber}</p>
{/if}
</div>
</div>
@@ -112,13 +112,15 @@
>
{#if successMessage}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{successMessage}
</div>
{/if}
{#if formError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{formError}
</div>
{/if}
@@ -129,22 +131,28 @@
{/if}
<div>
<label for="field-name" class="mb-1 block text-sm font-medium text-slate-700">Name</label>
<label
for="field-name"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name</label
>
<input
id="field-name"
type="text"
name="name"
required
bind:value={name}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.name}
<p class="mt-1 text-sm text-red-600">{fieldErrors.name}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.name}</p>
{/if}
</div>
<div>
<label for="field-value" class="mb-1 block text-sm font-medium text-slate-700">Value</label>
<label
for="field-value"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Value</label
>
<input
id="field-value"
type="number"
@@ -153,10 +161,10 @@
min="1"
step="1"
bind:value
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.value}
<p class="mt-1 text-sm text-red-600">{fieldErrors.value}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.value}</p>
{/if}
</div>
@@ -164,7 +172,7 @@
<button
type="button"
onclick={closeModal}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
+23 -12
View File
@@ -131,13 +131,15 @@
>
{#if successMessage}
<div
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700"
class="mb-4 rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700 dark:border-green-900 dark:bg-green-950/50 dark:text-green-300"
>
{successMessage}
</div>
{/if}
{#if formError}
<div class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
<div
class="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600 dark:border-red-900 dark:bg-red-950/50 dark:text-red-400"
>
{formError}
</div>
{/if}
@@ -148,43 +150,52 @@
{/if}
<div>
<label for="field-name" class="mb-1 block text-sm font-medium text-slate-700">Name</label>
<label
for="field-name"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Name</label
>
<input
id="field-name"
type="text"
name="name"
required
bind:value={name}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.name}
<p class="mt-1 text-sm text-red-600">{fieldErrors.name}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.name}</p>
{/if}
</div>
<div>
<label for="field-email" class="mb-1 block text-sm font-medium text-slate-700">Email</label>
<label
for="field-email"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Email</label
>
<input
id="field-email"
type="email"
name="email"
required
bind:value={email}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
/>
{#if fieldErrors.email}
<p class="mt-1 text-sm text-red-600">{fieldErrors.email}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.email}</p>
{/if}
</div>
<div>
<label for="field-roleId" class="mb-1 block text-sm font-medium text-slate-700">Role</label>
<label
for="field-roleId"
class="mb-1 block text-sm font-medium text-slate-700 dark:text-slate-300">Role</label
>
<select
id="field-roleId"
name="roleId"
required
bind:value={roleId}
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none"
class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition focus:border-indigo-500 focus:outline-none dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
>
<option value="">-- Select Role --</option>
{#each data.roleOptions as role (String(role.id))}
@@ -192,7 +203,7 @@
{/each}
</select>
{#if fieldErrors.roleId}
<p class="mt-1 text-sm text-red-600">{fieldErrors.roleId}</p>
<p class="mt-1 text-sm text-red-600 dark:text-red-400">{fieldErrors.roleId}</p>
{/if}
</div>
@@ -200,7 +211,7 @@
<button
type="button"
onclick={closeModal}
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50"
class="rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Cancel
</button>
+156 -22
View File
@@ -3,6 +3,7 @@
import favicon from '$lib/assets/favicon.svg';
import { page } from '$app/state';
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
import type { Snippet } from 'svelte';
import type { LayoutData } from './$types';
import './layout.css';
@@ -12,6 +13,17 @@
let menuOpen = $state(false);
let adminOpen = $state(false);
let dark = $state(false);
onMount(() => {
dark = document.documentElement.classList.contains('dark');
});
function toggleTheme() {
dark = !dark;
document.documentElement.classList.toggle('dark', dark);
localStorage.setItem('theme', dark ? 'dark' : 'light');
}
const isAdmin = $derived((data.userRoleId ?? 4) <= 3);
const adminModels = [
@@ -49,8 +61,8 @@
function activeClass(path: string) {
return isActive(path)
? 'bg-indigo-50 text-indigo-700'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900';
? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300'
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800 hover:text-slate-900 dark:hover:text-slate-100';
}
function closeMenu() {
@@ -75,7 +87,9 @@
<meta name="twitter:card" content="summary" />
</svelte:head>
<header class="sticky top-0 z-40 border-b border-slate-200 bg-white/90 backdrop-blur">
<header
class="sticky top-0 z-40 border-b border-slate-200 bg-white/90 backdrop-blur dark:border-slate-800 dark:bg-slate-900/90"
>
<div class="mx-auto flex h-14 max-w-6xl items-center justify-between gap-4 px-4">
<a href={resolve('/')} class="flex shrink-0 items-center gap-2">
<span
@@ -96,7 +110,9 @@
/></svg
>
</span>
<span class="text-base font-semibold tracking-tight text-slate-900">Radiant Wave</span>
<span class="text-base font-semibold tracking-tight text-slate-900 dark:text-slate-100"
>Radiant Wave</span
>
</a>
{#if $session.data?.user}
@@ -128,8 +144,8 @@
onclick={() => (adminOpen = !adminOpen)}
class={`flex items-center gap-1 rounded-md px-3 py-2 text-sm font-medium transition ${
isActive('/admin')
? 'bg-indigo-50 text-indigo-700'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100'
}`}
>
Admin
@@ -150,7 +166,7 @@
{#if adminOpen}
<div
class="absolute top-full left-0 mt-1 min-w-56 rounded-lg border border-slate-200 bg-white p-1 shadow-lg"
class="absolute top-full left-0 mt-1 min-w-56 rounded-lg border border-slate-200 bg-white p-1 shadow-lg dark:border-slate-800 dark:bg-slate-900"
>
{#each adminModels as model (model.slug)}
<a
@@ -158,8 +174,8 @@
onclick={() => (adminOpen = false)}
class={`block rounded-md px-3 py-2 text-sm transition ${
isActive('/admin/' + model.slug)
? 'bg-indigo-50 text-indigo-700'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100'
}`}
>
{model.displayName}
@@ -170,11 +186,47 @@
</div>
{/if}
<span class="mx-2 h-5 w-px bg-slate-200" aria-hidden="true"></span>
<button
type="button"
aria-label="Toggle dark mode"
aria-pressed={dark}
onclick={toggleTheme}
class="flex h-9 w-9 items-center justify-center rounded-md text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100"
>
{#if dark}
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><circle cx="12" cy="12" r="4" /><path
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
/></svg
>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" /></svg
>
{/if}
</button>
<span class="mx-2 h-5 w-px bg-slate-200 dark:bg-slate-700" aria-hidden="true"></span>
<button
type="button"
onclick={handleSignOut}
class="rounded-md px-3 py-2 text-sm font-medium text-slate-500 transition hover:bg-slate-100 hover:text-slate-900"
class="rounded-md px-3 py-2 text-sm font-medium text-slate-500 transition hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100"
>
Sign Out
</button>
@@ -193,15 +245,87 @@
>
Sign In
</a>
<button
type="button"
aria-label="Toggle dark mode"
aria-pressed={dark}
onclick={toggleTheme}
class="ml-1 flex h-9 w-9 items-center justify-center rounded-md text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100"
>
{#if dark}
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><circle cx="12" cy="12" r="4" /><path
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
/></svg
>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" /></svg
>
{/if}
</button>
</nav>
{/if}
<button
type="button"
aria-label="Toggle dark mode"
aria-pressed={dark}
onclick={toggleTheme}
class="flex h-10 w-10 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 md:hidden dark:text-slate-300 dark:hover:bg-slate-800"
>
{#if dark}
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><circle cx="12" cy="12" r="4" /><path
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"
/></svg
>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" /></svg
>
{/if}
</button>
<button
type="button"
aria-label="Toggle navigation menu"
aria-expanded={menuOpen}
onclick={() => (menuOpen = !menuOpen)}
class="flex h-10 w-10 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 md:hidden"
class="flex h-10 w-10 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 md:hidden dark:text-slate-300 dark:hover:bg-slate-800"
>
{#if menuOpen}
<svg
@@ -240,7 +364,7 @@
{#if menuOpen}
<div
class="fixed inset-0 top-14 z-50 overflow-y-auto border-t border-slate-200 bg-white shadow-lg md:hidden"
class="fixed inset-0 top-14 z-50 overflow-y-auto border-t border-slate-200 bg-white shadow-lg md:hidden dark:border-slate-800 dark:bg-slate-900"
>
<nav class="mx-auto max-w-6xl px-4 py-4">
{#if $session.data?.user}
@@ -268,15 +392,15 @@
</a>
{#if isAdmin}
<div class="mt-2 border-t border-slate-200 pt-2">
<div class="mt-2 border-t border-slate-200 pt-2 dark:border-slate-800">
<button
type="button"
aria-expanded={adminOpen}
onclick={() => (adminOpen = !adminOpen)}
class={`flex w-full items-center justify-between rounded-lg px-4 py-3 text-base font-semibold transition ${
isActive('/admin')
? 'bg-indigo-50 text-indigo-700'
: 'text-slate-700 hover:bg-indigo-50'
? 'bg-indigo-50 text-indigo-700 dark:bg-indigo-950 dark:text-indigo-300'
: 'text-slate-700 hover:bg-indigo-50 dark:text-slate-300 dark:hover:bg-indigo-950'
}`}
>
<span>Admin</span>
@@ -314,7 +438,7 @@
<button
type="button"
onclick={handleSignOut}
class="mt-2 rounded-lg border-t border-slate-200 px-4 py-3 text-base font-medium text-slate-500 hover:bg-slate-100"
class="mt-2 rounded-lg border-t border-slate-200 px-4 py-3 text-base font-medium text-slate-500 hover:bg-slate-100 dark:border-slate-800 dark:text-slate-400 dark:hover:bg-slate-800"
>
Sign Out
</button>
@@ -341,21 +465,31 @@
</div>
{/if}
<div class="flex min-h-[calc(100dvh-3.5rem)] flex-col bg-slate-50 text-slate-900">
<div
class="flex min-h-[calc(100dvh-3.5rem)] flex-col bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100"
>
<main class="flex-1">
{@render children()}
</main>
<footer class="sticky bottom-0 z-10 border-t border-slate-200 bg-white">
<footer
class="sticky bottom-0 z-10 border-t border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900"
>
<div
class="mx-auto flex max-w-6xl flex-col items-center justify-between gap-2 px-4 py-6 text-sm text-slate-500 sm:flex-row"
class="mx-auto flex max-w-6xl flex-col items-center justify-between gap-2 px-4 py-6 text-sm text-slate-500 sm:flex-row dark:text-slate-400"
>
<p>&copy; {new Date().getFullYear()} Radiant Wave Technologies. All rights reserved.</p>
{#if $session.data?.user}
<a href={resolve('/profile')} class="transition hover:text-slate-900">
<a
href={resolve('/profile')}
class="transition hover:text-slate-900 dark:hover:text-slate-100"
>
Manage your profile
</a>
{:else}
<a href={resolve('/login')} class="transition hover:text-slate-900">Sign in</a>
<a
href={resolve('/login')}
class="transition hover:text-slate-900 dark:hover:text-slate-100">Sign in</a
>
{/if}
</div>
</footer>
+4 -2
View File
@@ -9,7 +9,9 @@
<section class="py-16 sm:py-24">
<div class="mx-auto w-full max-w-6xl px-4">
<div class="mx-auto flex max-w-3xl flex-col items-center text-center">
<h1 class="mt-6 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
<h1
class="mt-6 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl dark:text-slate-100"
>
Welcome to the Radiant Wave Portal
</h1>
@@ -30,7 +32,7 @@
</a>
<a
href={resolve('/register')}
class="inline-flex h-12 w-full items-center justify-center rounded-lg border border-slate-300 bg-white px-8 text-base font-medium text-slate-700 transition hover:bg-slate-50 sm:w-auto"
class="inline-flex h-12 w-full items-center justify-center rounded-lg border border-slate-300 bg-white px-8 text-base font-medium text-slate-700 transition hover:bg-slate-50 sm:w-auto dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800"
>
Create Account
</a>
+14 -9
View File
@@ -8,34 +8,39 @@
<Window>
{#if form?.success}
<div class="rounded-lg bg-green-50 px-4 py-6 text-center">
<h1 class="text-xl font-bold text-slate-900">Check your email</h1>
<p class="mt-3 text-sm text-slate-600">
<div class="rounded-lg bg-green-50 px-4 py-6 text-center dark:bg-green-950/50">
<h1 class="text-xl font-bold text-slate-900 dark:text-slate-100">Check your email</h1>
<p class="mt-3 text-sm text-slate-600 dark:text-slate-400">
If an account with that email exists, we've sent a password reset link.
</p>
</div>
{:else}
{#if form?.error && !form?.success}
<div class="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700" role="alert">
<div
class="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
role="alert"
>
{form.error}
</div>
{/if}
<form method="POST" class="flex flex-col gap-4">
<h1 class="text-2xl font-bold text-slate-900">Forgot Password</h1>
<p class="text-sm text-slate-600">
<h1 class="text-2xl font-bold text-slate-900 dark:text-slate-100">Forgot Password</h1>
<p class="text-sm text-slate-600 dark:text-slate-400">
Enter your email and we'll send you a link to reset your password.
</p>
<div class="flex flex-col gap-1.5">
<label for="email" class="text-sm font-medium text-slate-700"> Email Address </label>
<label for="email" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Email Address
</label>
<input
id="email"
type="email"
name="email"
placeholder="you@example.com"
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
@@ -49,7 +54,7 @@
<a
href={resolve('/login')}
class="mt-6 block text-center text-sm text-indigo-600 hover:text-indigo-700"
class="mt-6 block text-center text-sm text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
>
Back to Login
</a>
+25
View File
@@ -2,8 +2,28 @@
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
@custom-variant dark (&:where(.dark, .dark *));
@layer base {
html {
color-scheme: light;
}
html.dark {
color-scheme: dark;
}
.dark input:not([type='checkbox']):not([type='radio']),
.dark textarea,
.dark select {
background-color: var(--color-slate-900);
color: var(--color-slate-100);
border-color: var(--color-slate-700);
}
body {
background-color: var(--color-slate-50);
color: var(--color-slate-900);
font-family:
ui-sans-serif,
system-ui,
@@ -16,4 +36,9 @@
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
html.dark body {
background-color: var(--color-slate-950);
color: var(--color-slate-100);
}
}
+35 -17
View File
@@ -28,49 +28,59 @@
<Window>
{#if verified}
<div class="mb-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800" role="status">
<div
class="mb-4 rounded-lg bg-green-50 px-4 py-3 text-sm text-green-800 dark:bg-green-950/50 dark:text-green-300"
role="status"
>
Your email has been verified. You can now log in.
</div>
{/if}
{#if error}
<div class="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700" role="alert">
<div
class="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
role="alert"
>
{error}
</div>
{/if}
<form onsubmit={handleSubmit} class="flex flex-col gap-4">
<h1 class="text-2xl font-bold text-slate-900">Login</h1>
<h1 class="text-2xl font-bold text-slate-900 dark:text-slate-100">Login</h1>
<div class="flex flex-col gap-1.5">
<label for="email" class="text-sm font-medium text-slate-700"> Email Address </label>
<label for="email" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Email Address
</label>
<input
id="email"
type="email"
placeholder="Email Address"
bind:value={email}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="password" class="text-sm font-medium text-slate-700"> Password </label>
<label for="password" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Password
</label>
<input
id="password"
type="password"
placeholder="Password"
bind:value={password}
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<label class="flex items-center gap-2 text-sm text-slate-600">
<label class="flex items-center gap-2 text-sm text-slate-600 dark:text-slate-400">
<input
type="checkbox"
bind:checked={rememberMe}
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500"
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500 dark:border-slate-700 dark:text-indigo-400"
/>
Remember me
</label>
@@ -84,12 +94,18 @@
</form>
<div class="mt-6 flex flex-col gap-2 text-center text-sm">
<a href={resolve('/forgot-password')} class="text-indigo-600 hover:text-indigo-700">
<a
href={resolve('/forgot-password')}
class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
>
Forgot Password?
</a>
<span class="text-slate-500">
<span class="text-slate-500 dark:text-slate-400">
Don't have an account?
<a href={resolve('/register')} class="font-medium text-indigo-600 hover:text-indigo-700">
<a
href={resolve('/register')}
class="font-medium text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
>
Register
</a>
</span>
@@ -97,18 +113,20 @@
<div class="mt-6">
<div class="flex items-center gap-3">
<div class="h-px flex-1 bg-slate-200"></div>
<span class="text-sm text-slate-500">Or</span>
<div class="h-px flex-1 bg-slate-200"></div>
<div class="h-px flex-1 bg-slate-200 dark:bg-slate-700"></div>
<span class="text-sm text-slate-500 dark:text-slate-400">Or</span>
<div class="h-px flex-1 bg-slate-200 dark:bg-slate-700"></div>
</div>
<button
type="button"
aria-label="Sign In with Google"
onclick={async () => {
await authClient.signIn.social({ provider: 'google', callbackURL: '/dashboard' });
}}
class="mx-auto mt-4 flex h-11 items-center justify-center rounded-lg bg-white hover:bg-slate-50"
class="mx-auto mt-4 flex h-11 items-center justify-center rounded-lg bg-white hover:bg-slate-50 dark:bg-slate-900 dark:hover:bg-slate-800"
>
<img src="light/web_light_sq_SI.svg" alt="Sign In with Google" class="" />
<img src="light/web_light_sq_SI.svg" alt="Sign In with Google" class="dark:hidden" />
<img src="dark/web_dark_sq_SI.svg" alt="" aria-hidden="true" class="hidden dark:block" />
</button>
</div>
</Window>
+46 -23
View File
@@ -9,9 +9,9 @@
<Window>
{#if form?.success}
<div class="rounded-lg bg-green-50 px-4 py-6 text-center">
<h1 class="text-xl font-bold text-slate-900">Account created!</h1>
<p class="mt-3 text-sm text-slate-600">
<div class="rounded-lg bg-green-50 px-4 py-6 text-center dark:bg-green-950/50">
<h1 class="text-xl font-bold text-slate-900 dark:text-slate-100">Account created!</h1>
<p class="mt-3 text-sm text-slate-600 dark:text-slate-400">
We've sent a verification email. Check your inbox and click the link to verify your email
address, then log in.
</p>
@@ -24,72 +24,85 @@
</div>
{:else}
{#if form?.error}
<div class="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700" role="alert">
<div
class="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
role="alert"
>
{form.error}
</div>
{/if}
<form method="POST" class="flex flex-col gap-4">
<h1 class="text-2xl font-bold text-slate-900">Create Account</h1>
<h1 class="text-2xl font-bold text-slate-900 dark:text-slate-100">Create Account</h1>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-1.5">
<label for="firstName" class="text-sm font-medium text-slate-700"> First Name </label>
<label for="firstName" class="text-sm font-medium text-slate-700 dark:text-slate-300">
First Name
</label>
<input
id="firstName"
type="text"
name="firstName"
placeholder="First Name"
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="lastName" class="text-sm font-medium text-slate-700"> Last Name </label>
<label for="lastName" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Last Name
</label>
<input
id="lastName"
type="text"
name="lastName"
placeholder="Last Name"
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
</div>
<div class="flex flex-col gap-1.5">
<label for="email" class="text-sm font-medium text-slate-700"> Email Address </label>
<label for="email" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Email Address
</label>
<input
id="email"
type="email"
name="email"
placeholder="Email Address"
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="password" class="text-sm font-medium text-slate-700"> Password </label>
<label for="password" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Password
</label>
<input
id="password"
type="password"
name="password"
placeholder="Password"
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="password2" class="text-sm font-medium text-slate-700"> Verify Password </label>
<label for="password2" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Verify Password
</label>
<input
id="password2"
type="password"
name="password2"
placeholder="Verify Password"
required
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
@@ -101,26 +114,36 @@
</button>
</form>
<div class="mt-6 border-t border-slate-200 pt-6">
<div class="mt-6 border-t border-slate-200 pt-6 dark:border-slate-800">
<div class="flex items-center gap-3">
<div class="h-px flex-1 bg-slate-200"></div>
<span class="text-sm text-slate-500">Or</span>
<div class="h-px flex-1 bg-slate-200"></div>
<div class="h-px flex-1 bg-slate-200 dark:bg-slate-700"></div>
<span class="text-sm text-slate-500 dark:text-slate-400">Or</span>
<div class="h-px flex-1 bg-slate-200 dark:bg-slate-700"></div>
</div>
<button
type="button"
aria-label="Sign Up with Google"
onclick={async () => {
await authClient.signIn.social({ provider: 'google', callbackURL: '/profile/setup' });
}}
class="mt-4 flex h-11 w-full items-center justify-center rounded-lg border border-slate-300 bg-white hover:bg-slate-50"
class="mt-4 flex h-11 w-full items-center justify-center rounded-lg border border-slate-300 bg-white hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:hover:bg-slate-800"
>
<img src="light/web_light_sq_SU.svg" alt="Sign Up with Google" class="h-6" />
<img src="light/web_light_sq_SU.svg" alt="Sign Up with Google" class="h-6 dark:hidden" />
<img
src="dark/web_dark_sq_SU.svg"
alt=""
aria-hidden="true"
class="hidden h-6 dark:block"
/>
</button>
</div>
<p class="mt-6 text-center text-sm text-slate-500">
<p class="mt-6 text-center text-sm text-slate-500 dark:text-slate-400">
Already have an account?
<a href={resolve('/login')} class="font-medium text-indigo-600 hover:text-indigo-700">
<a
href={resolve('/login')}
class="font-medium text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
>
Login
</a>
</p>
+12 -7
View File
@@ -7,23 +7,28 @@
<Window>
{#if form?.error}
<div class="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700" role="alert">
<div
class="mb-4 rounded-lg bg-red-50 px-4 py-3 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
role="alert"
>
{form.error}
</div>
{/if}
<form method="POST" class="flex flex-col gap-4">
<h1 class="text-2xl font-bold text-slate-900">
<h1 class="text-2xl font-bold text-slate-900 dark:text-slate-100">
{data.invite ? 'Set Your Password' : 'Reset Password'}
</h1>
{#if data.invite}
<p class="text-sm text-slate-600">
<p class="text-sm text-slate-600 dark:text-slate-400">
Welcome! Choose a password to finish setting up your account.
</p>
{/if}
<div class="flex flex-col gap-1.5">
<label for="password" class="text-sm font-medium text-slate-700"> New Password </label>
<label for="password" class="text-sm font-medium text-slate-700 dark:text-slate-300">
New Password
</label>
<input
id="password"
type="password"
@@ -31,12 +36,12 @@
placeholder="••••••••"
required
minlength="8"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>
<div class="flex flex-col gap-1.5">
<label for="password2" class="text-sm font-medium text-slate-700">
<label for="password2" class="text-sm font-medium text-slate-700 dark:text-slate-300">
Confirm New Password
</label>
<input
@@ -46,7 +51,7 @@
placeholder="••••••••"
required
minlength="8"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500"
class="h-11 w-full rounded-lg border-slate-300 px-3 text-sm text-slate-900 focus:border-indigo-500 focus:ring-indigo-500 dark:border-slate-700 dark:text-slate-100"
/>
</div>