Add a site-wide dark mode
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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>© {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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user