Some checks failed
Test and deploy staging / staging (push) Failing after 43s
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>
184 lines
5.7 KiB
Svelte
184 lines
5.7 KiB
Svelte
<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>
|