ui changes and cs prices
All checks were successful
Test and deploy staging / staging (push) Successful in 11m52s
All checks were successful
Test and deploy staging / staging (push) Successful in 11m52s
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user