Files
website/src/lib/game-tracker/CatchEditor.svelte
Stan Runge 54924b39f0
Some checks failed
Test and deploy staging / staging (push) Failing after 43s
feat: upgrade to SvelteKit 3 and adapter-bun
Run the sveltekit-3 migration and finish its manual tasks:

- Move kit config into the sveltekit() Vite plugin; drop svelte.config.js
- Replace $lib with #lib subpath imports
- Declare env vars in src/env.ts and import them from $app/env/{private,public}
- Consolidate param matchers into src/params.ts
- Use error(status, message), Response.json, refreshAll and goto reset/shallow
- Fall back to a full page load when goto rejects a login return path

Switch from adapter-node to adapter-bun: the build script runs Vite under
Bun (the adapter requires it) and the image starts with `bun ./build`.
Runtime env names (PORT, BODY_SIZE_LIMIT, ADDRESS_HEADER, ...) are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:17:30 +00:00

184 lines
5.7 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import { pokemonImage } from '#lib/game-tracker/game-art.js';
import { enhance } from '$app/forms';
import * as Dialog from '#lib/components/ui/dialog/index.js';
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 { NativeSelect } from '#lib/components/ui/native-select/index.js';
import { Checkbox } from '#lib/components/ui/checkbox/index.js';
import { stages, obtainedMethods, type Stage } from './journal';
import type { PokemonCatch } from '#lib/server/db/schema/tracker-journal.js';
let {
open = $bindable(false),
species,
gameId,
record = null,
stage = 'story',
location = '',
signedIn,
ondelete,
ondetails
}: {
open?: boolean;
species: { id: number; number: number; name: string };
gameId: number;
record?: PokemonCatch | null;
stage?: Stage;
location?: string;
signedIn: boolean;
ondelete?: () => void;
ondetails?: () => void;
} = $props();
let message = $state('');
let saving = $state(false);
</script>
<Dialog.Root bind:open>
<Dialog.Content class="max-h-[90dvh] overflow-y-auto sm:max-w-2xl">
<Dialog.Header
><Dialog.Title>{record ? 'Meet' : 'Register'} {record?.nickname || species.name}</Dialog.Title
><Dialog.Description
>#{species.number} · {species.name}. Keep this Pokémon’s story, moves and party position
together.</Dialog.Description
></Dialog.Header
>
{#if ondetails}<Button variant="link" class="justify-self-start px-0" onclick={ondetails}
>All catches, moves and species research →</Button
>{/if}
{#if message}<p role="alert" class="text-destructive">{message}</p>{/if}
<form
method="POST"
action="?/saveCatch"
class="space-y-5"
use:enhance={() => {
saving = true;
message = '';
return async ({ result, update }) => {
await update({ reset: false });
saving = false;
if (result.type === 'success') open = false;
else
message =
result.type === 'failure'
? String(result.data?.message ?? 'Could not save.')
: 'Could not save. Try again.';
};
}}
>
<input type="hidden" name="gameId" value={gameId} /><input
type="hidden"
name="pokemonId"
value={species.id}
/>{#if record}<input type="hidden" name="id" value={record.id} />{/if}
<div class="flex items-center gap-4 rounded-xl bg-muted/40 p-3">
<img
src={pokemonImage(species.number, record?.shiny ? 'shiny' : 'sprite')}
alt={species.name}
width="80"
height="80"
/>
<div class="grid flex-1 grid-cols-[1fr_5rem] gap-3">
<label
>Nickname<Input
name="nickname"
value={record?.nickname ?? ''}
maxlength={50}
placeholder={species.name}
/></label
><label
>Level<Input
type="number"
name="level"
min={1}
max={100}
required
value={record?.level ?? 5}
/></label
>
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<label
>Party position<NativeSelect
name="partySlot"
value={record?.partySlot ?? ''}
class="mt-1 w-full"
><option value="">In the PC</option>{#each [1, 2, 3, 4, 5, 6] as slot (slot)}<option
value={slot}>Team slot {slot}</option
>{/each}</NativeSelect
><span class="text-xs text-muted-foreground"
>An occupied slot moves its current Pokémon to the PC.</span
></label
><label
>Obtained by<NativeSelect
name="obtained"
value={record?.obtained ?? 'caught'}
class="mt-1 w-full"
>{#each obtainedMethods as method (method)}<option value={method}>{method}</option
>{/each}</NativeSelect
></label
>
</div>
<fieldset>
<legend class="mb-2 font-medium">Current moves</legend>
<div class="grid grid-cols-2 gap-3">
{#each [0, 1, 2, 3] as slot (slot)}<Input
name="moves"
aria-label={`Move ${slot + 1}`}
placeholder={`Move ${slot + 1}`}
value={record?.moves[slot] ?? ''}
maxlength={60}
/>{/each}
</div>
</fieldset>
<div class="grid gap-4 sm:grid-cols-2">
<label>Nature<Input name="nature" value={record?.nature ?? ''} maxlength={150} /></label
><label>Ability<Input name="ability" value={record?.ability ?? ''} maxlength={150} /></label
><label>Poké Ball<Input name="ball" value={record?.ball ?? ''} maxlength={150} /></label
><label
>Caught on<Input
type="date"
name="caughtOn"
value={record?.caughtOn ?? new Date().toISOString().slice(0, 10)}
/></label
><label
>Location<Input
name="location"
value={record?.location ?? location}
maxlength={150}
/></label
><label
>Collection stage<NativeSelect name="stage" value={record?.stage ?? stage} class="w-full"
>{#each stages as s (s.value)}<option value={s.value}>{s.label}</option
>{/each}</NativeSelect
></label
>
</div>
<label class="flex items-center gap-2"
><Checkbox name="shiny" value="on" checked={record?.shiny ?? false} />Shiny Pokémon</label
>
<label class="block"
>Notes<Textarea
name="notes"
value={record?.notes ?? ''}
maxlength={2000}
placeholder="A lucky encounter, a trade partner, a plan for evolution…"
/></label
>
<Dialog.Footer
><Button variant="outline" onclick={() => (open = false)}>Cancel</Button><Button
type="submit"
disabled={!signedIn || saving}
>{saving ? 'Saving…' : record ? 'Save Pokémon' : 'Add to my collection'}</Button
></Dialog.Footer
>
</form>
{#if record && ondelete}<Button
variant="ghost"
class="justify-self-start text-destructive"
onclick={ondelete}>Remove this catch record</Button
>{/if}
</Dialog.Content>
</Dialog.Root>