ui changes and cs prices
All checks were successful
Test and deploy staging / staging (push) Successful in 11m52s

This commit is contained in:
2026-09-25 00:44:06 +00:00
parent c757d0882a
commit abac077541
200 changed files with 6236 additions and 2311 deletions

View File

@@ -4,6 +4,8 @@
import { page } from '$app/state';
import { goto, preloadData, pushState } from '$app/navigation';
import AnimalDetails from '$lib/animaldex/AnimalDetails.svelte';
import { Button } from '$lib/components/ui/button/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import PawPrint from 'lucide-svelte/icons/paw-print';
import X from 'lucide-svelte/icons/x';
import type { Map as MaptilerMap, Marker as MaptilerMarker } from '@maptiler/sdk';
@@ -309,12 +311,7 @@
<h1 class="text-4xl font-semibold tracking-tight sm:text-5xl">animaldex</h1>
<p class="mt-2 text-base text-[var(--site-muted)]">neighborhood animals on the map</p>
</div>
<a
href={resolve('/projects/animaldex/register')}
class="rounded bg-[var(--site-text)] px-4 py-2.5 text-base font-medium text-[var(--site-bg)]"
>
add an animal
</a>
<Button href={resolve('/projects/animaldex/register')}>add an animal</Button>
</div>
{#if submitted}<p role="status" class="mb-5 border-y border-[var(--site-border)] py-3">
sighting submitted. it will appear after review.
@@ -328,20 +325,18 @@
: `${animalCount} ${animalCount === 1 ? 'animal' : 'animals'} in this area`}</span
>
<div class="flex items-center gap-5">
<button
type="button"
onclick={locate}
disabled={locating}
class="underline underline-offset-4">{locating ? 'locating…' : 'my location'}</button
<Button variant="outline" size="sm" type="button" onclick={locate} disabled={locating}
>{locating ? 'locating…' : 'my location'}</Button
>
<button
<Button
variant="outline"
size="sm"
type="button"
onclick={() => (showFilters = !showFilters)}
aria-expanded={showFilters}
class="underline underline-offset-4"
>
filters{activeFilterCount ? ` (${activeFilterCount})` : ''}
</button>
</Button>
</div>
</div>
@@ -349,58 +344,24 @@
<div class="mb-5 border-y border-[var(--site-border)] py-5">
<h2 class="mb-3 text-lg font-medium">filter sightings</h2>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<input
bind:value={filterSpecies}
placeholder="Species"
aria-label="Species"
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
/>
<input
bind:value={filterBreed}
placeholder="Breed"
aria-label="Breed"
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
/>
<input
bind:value={filterName}
placeholder="Animal name"
aria-label="Animal name"
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
/>
<input
bind:value={filterReporter}
placeholder="Reporter name"
aria-label="Reporter name"
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
/>
<Input bind:value={filterSpecies} placeholder="Species" aria-label="Species" />
<Input bind:value={filterBreed} placeholder="Breed" aria-label="Breed" />
<Input bind:value={filterName} placeholder="Animal name" aria-label="Animal name" />
<Input bind:value={filterReporter} placeholder="Reporter name" aria-label="Reporter name" />
<div class="grid grid-cols-2 gap-3 sm:col-span-2">
<div>
<label for="filter-from" class="mb-1 block text-base">from</label>
<input
id="filter-from"
type="date"
bind:value={filterFrom}
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
/>
<Input id="filter-from" type="date" bind:value={filterFrom} />
</div>
<div>
<label for="filter-to" class="mb-1 block text-base">to</label>
<input
id="filter-to"
type="date"
bind:value={filterTo}
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
/>
<Input id="filter-to" type="date" bind:value={filterTo} />
</div>
</div>
</div>
<div class="mt-4 flex gap-4">
<button
onclick={applyFilters}
class="rounded bg-[var(--site-text)] px-4 py-2 text-base text-[var(--site-bg)]"
>apply</button
>
<button onclick={clearFilters} class="text-base underline underline-offset-4">clear</button>
<Button onclick={applyFilters}>apply</Button>
<Button variant="ghost" onclick={clearFilters}>clear</Button>
</div>
</div>
{/if}

View File

@@ -2,7 +2,7 @@ import type { PageServerLoad } from './$types';
import { db } from '$lib/server/db';
import { animals, sightings } from '$lib/server/db/schema';
import { isAdmin } from '$lib/server/auth';
import { and, eq, isNull, sql } from 'drizzle-orm';
import { and, eq, sql } from 'drizzle-orm';
export const load: PageServerLoad = async (event) => {
const admin = isAdmin(event);

View File

@@ -8,6 +8,9 @@
import MapPin from 'lucide-svelte/icons/map-pin';
import PawPrint from 'lucide-svelte/icons/paw-print';
import X from 'lucide-svelte/icons/x';
import { Button } from '$lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js';
import { Badge } from '$lib/components/ui/badge/index.js';
let { data }: { data: PageData } = $props();
@@ -67,7 +70,7 @@
{#each queue as item (item.animalId)}
{@const status = item.acceptedAt ? 'accepted' : item.deniedAt ? 'denied' : 'pending'}
<div class="border-t border-[var(--site-border)] pt-4">
<Card.Root>
<div class="flex gap-4 p-4">
<div class="h-24 w-24 shrink-0 overflow-hidden rounded bg-[var(--site-surface)]">
{#if item.photoUrl}
@@ -86,9 +89,7 @@
{item.species}{item.breed ? ` · ${item.breed}` : ''}
</p>
</div>
<span class="shrink-0 text-base text-[var(--site-muted)]">
{status}
</span>
<Badge variant="secondary">{status}</Badge>
</div>
{#if item.aiBreedSuggestion}
<p class="mt-1 flex items-center gap-1 text-base text-[var(--site-muted)]">
@@ -114,28 +115,30 @@
</div>
</div>
<div class="flex gap-3 border-t border-[var(--site-border)] px-4 py-3">
<button
<Button
variant="outline"
onclick={() => moderate(item.animalId, 'accept')}
disabled={actionLoading !== null || status === 'accepted'}
class="inline-flex flex-1 items-center justify-center gap-1 rounded border border-[var(--site-border)] py-2 text-base disabled:opacity-50"
class="flex-1"
>
{#if actionLoading === `animals-${item.animalId}-accept`}…{:else}<Check
size={16}
aria-hidden="true"
/> Accept{/if}
</button>
<button
</Button>
<Button
variant="outline"
onclick={() => moderate(item.animalId, 'deny')}
disabled={actionLoading !== null || status === 'denied'}
class="inline-flex flex-1 items-center justify-center gap-1 rounded border border-[var(--site-border)] py-2 text-base disabled:opacity-50"
class="flex-1"
>
{#if actionLoading === `animals-${item.animalId}-deny`}…{:else}<X
size={16}
aria-hidden="true"
/> Deny{/if}
</button>
</Button>
</div>
</div>
</Card.Root>
{/each}
</div>
</section>

View File

@@ -3,7 +3,7 @@ import type { RequestHandler } from './$types';
import { db } from '$lib/server/db';
import { animals, sightings } from '$lib/server/db/schema';
import { moderationActionSchema } from '$lib/validation/sighting';
import { eq, isNull } from 'drizzle-orm';
import { eq } from 'drizzle-orm';
import { requireAdmin } from '$lib/server/auth';
export const POST: RequestHandler = async (event) => {

View File

@@ -1,4 +1,7 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Textarea } from '$lib/components/ui/textarea/index.js';
import { resolve } from '$app/paths';
import type { Map as MaptilerMap, Marker as MaptilerMarker } from '@maptiler/sdk';
import { onDestroy, onMount } from 'svelte';
@@ -257,8 +260,8 @@
<div role="alert" class="border-y border-[var(--site-border)] py-4 text-base">
<p class="font-medium">location required</p>
<p class="mt-1">{locationError}</p>
<button type="button" onclick={requestLocation} class="mt-3 underline underline-offset-4"
>try again</button
<Button type="button" variant="outline" onclick={requestLocation} class="mt-3"
>try again</Button
>
</div>
{/if}
@@ -276,12 +279,13 @@
{#each photoPreviews as preview, i (i)}
<div class="relative h-20 w-20 overflow-hidden rounded">
<img src={preview} alt="" class="h-full w-full object-cover" />
<button
<Button
variant="secondary"
size="icon-xs"
type="button"
onclick={() => removePhoto(i)}
aria-label={`Remove photo ${i + 1}`}
class="absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-xs text-white"
><X size={12} aria-hidden="true" /></button
class="absolute top-1 right-1 rounded-full"><X size={12} aria-hidden="true" /></Button
>
</div>
{/each}
@@ -320,30 +324,25 @@
</h2>
<div class="flex flex-wrap gap-2">
{#each COMMON_SPECIES as s (s)}
<button
<Button
variant={species === s ? 'secondary' : 'outline'}
size="sm"
onclick={() => {
species = s;
customSpecies = '';
}}
class="rounded border border-[var(--site-border)] px-3 py-1.5 text-base {species === s
? 'bg-[var(--site-text)] text-[var(--site-bg)]'
: 'bg-[var(--site-surface)] text-[var(--site-text)] hover:bg-[var(--site-hover)]'}"
>{s}</button
}}>{s}</Button
>
{/each}
<button
<Button
variant={species === '__custom__' ? 'secondary' : 'outline'}
size="sm"
onclick={() => {
species = '__custom__';
}}
class="rounded border border-[var(--site-border)] px-3 py-1.5 text-base {species ===
'__custom__'
? 'bg-[var(--site-text)] text-[var(--site-bg)]'
: 'bg-[var(--site-surface)] text-[var(--site-text)] hover:bg-[var(--site-hover)]'}"
>Other…</button
}}>Other…</Button
>
</div>
{#if species === '__custom__'}
<input
<Input
bind:value={customSpecies}
placeholder="Enter species"
class="mt-3 w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
@@ -355,7 +354,7 @@
<h2 class="mb-1 text-xl font-semibold">
Breed <span class="text-base font-normal text-[var(--site-muted)]">(optional)</span>
</h2>
<input
<Input
bind:value={breed}
placeholder="e.g. Golden Retriever"
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
@@ -369,7 +368,7 @@
>(optional)</span
>
</h2>
<input
<Input
bind:value={animalName}
placeholder="e.g. Fluffy"
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
@@ -381,12 +380,12 @@
>(optional)</span
>
</h2>
<textarea
<Textarea
bind:value={description}
placeholder="Behaviour, features, context…"
rows="3"
rows={3}
class="w-full resize-none rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
></textarea>
/>
</div>
</section>
{/if}
@@ -396,7 +395,7 @@
<h2 class="mb-1 text-xl font-semibold">
When did you see it? <span class="text-[var(--site-muted)]">*</span>
</h2>
<input
<Input
type="datetime-local"
bind:value={seenAt}
max={localDateTime()}
@@ -407,7 +406,7 @@
<h2 class="mb-1 text-xl font-semibold">
Your name <span class="text-base font-normal text-[var(--site-muted)]">(optional)</span>
</h2>
<input
<Input
bind:value={reporterName}
placeholder="Saved for next time"
class="w-full rounded border border-[var(--site-border)] bg-[var(--site-bg)] px-3 py-2 text-base"
@@ -443,13 +442,13 @@
</div>
{/if}
<button
<Button
onclick={submit}
disabled={submitting || !!locationError || lat === null}
class="w-full rounded bg-[var(--site-text)] py-3 font-medium text-[var(--site-bg)] disabled:cursor-not-allowed disabled:opacity-50"
class="h-11 w-full"
>
{submitting ? 'Submitting…' : isNewSighting ? 'Submit sighting' : 'Register animal'}
</button>
</Button>
<p class="pb-8 text-center text-base text-[var(--site-muted)]">
All submissions are reviewed before appearing on the map.