feat: upgrade to SvelteKit 3 and adapter-bun
Some checks failed
Test and deploy staging / staging (push) Failing after 43s
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>
This commit is contained in:
@@ -2,12 +2,12 @@
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
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 { goto, preloadData } 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 * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
import * as Dialog from '#lib/components/ui/dialog/index.js';
|
||||
import type { Map as MaptilerMap, Marker as MaptilerMarker } from '@maptiler/sdk';
|
||||
|
||||
interface TrailPoint {
|
||||
@@ -64,8 +64,9 @@
|
||||
const id = String(animalId);
|
||||
const result = await preloadData(resolve('/projects/animaldex/[id=integer]', { id }));
|
||||
if (result.type === 'loaded' && result.status === 200) {
|
||||
pushState(resolve('/projects/animaldex/[id=integer]', { id }), {
|
||||
animal: result.data.animal
|
||||
goto(resolve('/projects/animaldex/[id=integer]', { id }), {
|
||||
shallow: true,
|
||||
state: { animal: result.data.animal }
|
||||
});
|
||||
} else {
|
||||
goto(resolve('/projects/animaldex/[id=integer]', { id }));
|
||||
@@ -82,22 +83,21 @@
|
||||
(async () => {
|
||||
try {
|
||||
const { Map, Marker, config } = await import('@maptiler/sdk');
|
||||
const { env } = await import('$env/dynamic/public');
|
||||
const { PUBLIC_MAPTILER_API_KEY } = await import('$app/env/public');
|
||||
if (destroyed) return;
|
||||
if (!env.PUBLIC_MAPTILER_API_KEY) {
|
||||
if (!PUBLIC_MAPTILER_API_KEY) {
|
||||
mapError = 'The map is not configured yet.';
|
||||
return;
|
||||
}
|
||||
|
||||
config.apiKey = env.PUBLIC_MAPTILER_API_KEY;
|
||||
config.apiKey = PUBLIC_MAPTILER_API_KEY;
|
||||
MapMarker = Marker;
|
||||
|
||||
const initMap = (center: [number, number], zoom: number) => {
|
||||
map = new Map({
|
||||
container: mapContainer,
|
||||
style:
|
||||
'https://api.maptiler.com/maps/satellite/style.json?key=' +
|
||||
env.PUBLIC_MAPTILER_API_KEY,
|
||||
'https://api.maptiler.com/maps/satellite/style.json?key=' + PUBLIC_MAPTILER_API_KEY,
|
||||
center,
|
||||
zoom,
|
||||
scrollZoom: false,
|
||||
@@ -176,7 +176,7 @@
|
||||
request = new AbortController();
|
||||
let geojson: { features: GeoJSONFeature[] };
|
||||
try {
|
||||
const res = await fetch(`${resolve('/projects/animaldex/api/map')}?${params}`, {
|
||||
const res = await fetch(`${resolve('projects/animaldex/api/map')}?${params}`, {
|
||||
signal: request.signal
|
||||
});
|
||||
if (!res.ok) throw new Error('Map request failed');
|
||||
@@ -298,7 +298,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>
|
||||
<Button href={resolve('/projects/animaldex/register')}>add an animal</Button>
|
||||
<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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, photos, sightings } from '$lib/server/db/schema';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, photos, sightings } from '#lib/server/db/schema/index.js';
|
||||
import { and, asc, desc, eq, inArray, isNotNull, isNull } from 'drizzle-orm';
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
@@ -23,7 +23,7 @@ export const load: PageServerLoad = async ({ params }) => {
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!animal) throw error(404, { message: 'Animal not found' });
|
||||
if (!animal) throw error(404, 'Animal not found');
|
||||
|
||||
const sightingRows = await db
|
||||
.select({
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import AnimalDetails from '$lib/animaldex/AnimalDetails.svelte';
|
||||
import AnimalDetails from '#lib/animaldex/AnimalDetails.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<section class="mx-auto max-w-6xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<a
|
||||
href={resolve('/projects/animaldex')}
|
||||
href={resolve('projects/animaldex')}
|
||||
class="mb-6 inline-flex items-center gap-1 text-base underline underline-offset-4"
|
||||
>
|
||||
<ArrowLeft size={16} aria-hidden="true" /> back to map
|
||||
|
||||
@@ -1,7 +1,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 { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings } from '#lib/server/db/schema/index.js';
|
||||
import { isAdmin } from '#lib/server/auth.js';
|
||||
import { and, eq, sql } from 'drizzle-orm';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { refreshAll } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import Bot from 'lucide-svelte/icons/bot';
|
||||
@@ -8,9 +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';
|
||||
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();
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
actionLoading = `animals-${id}-${action}`;
|
||||
actionError = '';
|
||||
try {
|
||||
const response = await fetch(resolve(`/projects/animaldex/api/animals/${id}/moderate`), {
|
||||
const response = await fetch(resolve(`projects/animaldex/api/animals/${id}/moderate`), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action })
|
||||
});
|
||||
if (!response.ok) throw new Error();
|
||||
await invalidateAll();
|
||||
await refreshAll();
|
||||
} catch {
|
||||
actionError = 'Could not update this animal. Please try again.';
|
||||
} finally {
|
||||
@@ -43,14 +43,14 @@
|
||||
<h1 class="text-xl font-bold">Administrator access required</h1>
|
||||
{#if !data.user}<a
|
||||
class="mt-4 inline-block underline"
|
||||
href={resolve('/login?returnTo=/projects/animaldex/admin')}>Log in with your account</a
|
||||
href={resolve('login?returnTo=/projects/animaldex/admin')}>Log in with your account</a
|
||||
>
|
||||
{:else}<p class="mt-4">Your account does not have administrator access.</p>{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<section class="mx-auto max-w-4xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<a
|
||||
href={resolve('/projects/animaldex')}
|
||||
href={resolve('projects/animaldex')}
|
||||
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)]"
|
||||
><ArrowLeft size={16} aria-hidden="true" /> animaldex</a
|
||||
>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, sightings, photos } from '$lib/server/db/schema';
|
||||
import { registerAnimalSchema } from '$lib/validation/sighting';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { distanceMeters } from '$lib/geo';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings, photos } from '#lib/server/db/schema/index.js';
|
||||
import { registerAnimalSchema } from '#lib/validation/sighting.js';
|
||||
import { R2_PUBLIC_URL } from '$app/env/private';
|
||||
import { distanceMeters } from '#lib/geo.js';
|
||||
|
||||
const MAX_DISTANCE_METERS = 30_000;
|
||||
|
||||
@@ -13,16 +13,14 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
const parsed = registerAnimalSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: parsed.error.issues[0]?.message ?? 'Invalid request' });
|
||||
throw error(400, parsed.error.issues[0]?.message ?? 'Invalid request');
|
||||
}
|
||||
|
||||
const data = parsed.data;
|
||||
|
||||
const dist = distanceMeters(data.deviceLat, data.deviceLng, data.lat, data.lng);
|
||||
if (dist > MAX_DISTANCE_METERS) {
|
||||
throw error(422, {
|
||||
message: `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`
|
||||
});
|
||||
throw error(422, `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`);
|
||||
}
|
||||
|
||||
const result = await db.transaction(async (tx) => {
|
||||
@@ -53,7 +51,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
data.photoKeys.map((key, i) => ({
|
||||
sightingId: sighting.id,
|
||||
r2Key: key,
|
||||
url: `${env.R2_PUBLIC_URL}/${key}`,
|
||||
url: `${R2_PUBLIC_URL}/${key}`,
|
||||
sortOrder: i
|
||||
}))
|
||||
);
|
||||
@@ -62,5 +60,5 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
});
|
||||
|
||||
console.log(`[animals] created animal ${result.animalId}, sighting ${result.sightingId}`);
|
||||
return json(result, { status: 201 });
|
||||
return Response.json(result, { status: 201 });
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
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 { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings } from '#lib/server/db/schema/index.js';
|
||||
import { moderationActionSchema } from '#lib/validation/sighting.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { requireAdmin } from '$lib/server/auth';
|
||||
import { requireAdmin } from '#lib/server/auth.js';
|
||||
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
requireAdmin(event);
|
||||
|
||||
const id = Number(event.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) throw error(404, { message: 'Not found' });
|
||||
if (!Number.isInteger(id) || id <= 0) throw error(404, 'Not found');
|
||||
|
||||
const body = await event.request.json().catch(() => null);
|
||||
const parsed = moderationActionSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: 'action must be "accept" or "deny"' });
|
||||
throw error(400, 'action must be "accept" or "deny"');
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
@@ -43,12 +43,12 @@ export const POST: RequestHandler = async (event) => {
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
throw error(404, { message: 'Animal not found' });
|
||||
throw error(404, 'Animal not found');
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[moderate] animal ${event.params.id} → ${parsed.data.action}, updated ${updated?.id}`
|
||||
);
|
||||
|
||||
return json({ ok: true, action: parsed.data.action });
|
||||
return Response.json({ ok: true, action: parsed.data.action });
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, sightings, photos } from '$lib/server/db/schema';
|
||||
import { addSightingSchema } from '$lib/validation/sighting';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings, photos } from '#lib/server/db/schema/index.js';
|
||||
import { addSightingSchema } from '#lib/validation/sighting.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { distanceMeters } from '$lib/geo';
|
||||
import { R2_PUBLIC_URL } from '$app/env/private';
|
||||
import { distanceMeters } from '#lib/geo.js';
|
||||
|
||||
const MAX_DISTANCE_METERS = 30_000;
|
||||
|
||||
@@ -14,7 +14,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
const parsed = addSightingSchema.safeParse({ ...body, animalId: params.id });
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: parsed.error.issues[0]?.message ?? 'Invalid request' });
|
||||
throw error(400, parsed.error.issues[0]?.message ?? 'Invalid request');
|
||||
}
|
||||
|
||||
const data = parsed.data;
|
||||
@@ -26,14 +26,12 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
.limit(1);
|
||||
|
||||
if (!animal) {
|
||||
throw error(404, { message: 'Animal not found' });
|
||||
throw error(404, 'Animal not found');
|
||||
}
|
||||
|
||||
const dist = distanceMeters(data.deviceLat, data.deviceLng, data.lat, data.lng);
|
||||
if (dist > MAX_DISTANCE_METERS) {
|
||||
throw error(422, {
|
||||
message: `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`
|
||||
});
|
||||
throw error(422, `Location is ${Math.round(dist / 1000)}km from your device. Maximum is 30km.`);
|
||||
}
|
||||
|
||||
const animalAlreadyAccepted = animal.acceptedAt !== null;
|
||||
@@ -56,7 +54,7 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
data.photoKeys.map((key, i) => ({
|
||||
sightingId: sighting.id,
|
||||
r2Key: key,
|
||||
url: `${env.R2_PUBLIC_URL}/${key}`,
|
||||
url: `${R2_PUBLIC_URL}/${key}`,
|
||||
sortOrder: i
|
||||
}))
|
||||
);
|
||||
@@ -68,5 +66,5 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
||||
console.log(
|
||||
`[sightings] created sighting ${result.sightingId} for animal ${params.id} (auto-accepted: ${animalAlreadyAccepted})`
|
||||
);
|
||||
return json(result, { status: 201 });
|
||||
return Response.json(result, { status: 201 });
|
||||
};
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { sightings } from '$lib/server/db/schema';
|
||||
import { moderationActionSchema } from '$lib/validation/sighting';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { sightings } from '#lib/server/db/schema/index.js';
|
||||
import { moderationActionSchema } from '#lib/validation/sighting.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { requireAdmin } from '$lib/server/auth';
|
||||
import { requireAdmin } from '#lib/server/auth.js';
|
||||
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
requireAdmin(event);
|
||||
|
||||
const id = Number(event.params.id);
|
||||
if (!Number.isInteger(id) || id <= 0) throw error(404, { message: 'Not found' });
|
||||
if (!Number.isInteger(id) || id <= 0) throw error(404, 'Not found');
|
||||
|
||||
const body = await event.request.json().catch(() => null);
|
||||
const parsed = moderationActionSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: 'action must be "accept" or "deny"' });
|
||||
throw error(400, 'action must be "accept" or "deny"');
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
@@ -32,8 +32,8 @@ export const POST: RequestHandler = async (event) => {
|
||||
.returning({ id: sightings.id });
|
||||
|
||||
if (!updated) {
|
||||
throw error(404, { message: 'Sighting not found' });
|
||||
throw error(404, 'Sighting not found');
|
||||
}
|
||||
|
||||
return json({ ok: true, action: parsed.data.action });
|
||||
return Response.json({ ok: true, action: parsed.data.action });
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import * as z from 'zod';
|
||||
|
||||
@@ -9,9 +9,9 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
const parsed = schema.safeParse(body);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: 'imageUrl is required' });
|
||||
throw error(400, 'imageUrl is required');
|
||||
}
|
||||
|
||||
// Breed detection is not configured for this site yet.
|
||||
return json({ breed: null, confidence: 0 });
|
||||
return Response.json({ breed: null, confidence: 0 });
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { animals, sightings } from '$lib/server/db/schema';
|
||||
import { mapQuerySchema } from '$lib/validation/sighting';
|
||||
import { db } from '#lib/server/db/index.js';
|
||||
import { animals, sightings } from '#lib/server/db/schema/index.js';
|
||||
import { mapQuerySchema } from '#lib/validation/sighting.js';
|
||||
import { and, eq, gte, ilike, isNotNull, isNull, lte, sql } from 'drizzle-orm';
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
@@ -10,7 +10,7 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
const parsed = mapQuerySchema.safeParse(params);
|
||||
|
||||
if (!parsed.success) {
|
||||
throw error(400, { message: 'Invalid query parameters' });
|
||||
throw error(400, 'Invalid query parameters');
|
||||
}
|
||||
|
||||
const q = parsed.data;
|
||||
@@ -118,5 +118,5 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
};
|
||||
});
|
||||
|
||||
return json({ type: 'FeatureCollection', features });
|
||||
return Response.json({ type: 'FeatureCollection', features });
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { uploadImage, isAllowedMimeType } from '$lib/server/r2';
|
||||
import { uploadImage, isAllowedMimeType } from '#lib/server/r2.js';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const form = await request.formData().catch(() => null);
|
||||
@@ -10,7 +10,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
if (!photo.size || photo.size > 5 * 1024 * 1024)
|
||||
throw error(400, 'Photos must be 5 MB or smaller.');
|
||||
try {
|
||||
return json(await uploadImage(photo));
|
||||
return Response.json(await uploadImage(photo));
|
||||
} catch (cause) {
|
||||
console.error('Animaldex image upload failed', cause);
|
||||
throw error(503, 'Image storage is unavailable.');
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<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 { 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';
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import X from 'lucide-svelte/icons/x';
|
||||
|
||||
const animalId = $derived($page.url.searchParams.get('animalId'));
|
||||
const animalId = $derived(page.url.searchParams.get('animalId'));
|
||||
const isNewSighting = $derived(!!animalId);
|
||||
|
||||
let species = $state('');
|
||||
@@ -95,16 +95,16 @@
|
||||
if (!mapContainer || lat === null || lng === null) return;
|
||||
try {
|
||||
const { Map, Marker, config } = await import('@maptiler/sdk');
|
||||
const { env } = await import('$env/dynamic/public');
|
||||
const { PUBLIC_MAPTILER_API_KEY } = await import('$app/env/public');
|
||||
if (destroyed) return;
|
||||
if (!env.PUBLIC_MAPTILER_API_KEY) {
|
||||
if (!PUBLIC_MAPTILER_API_KEY) {
|
||||
mapError = 'The map is not configured yet. Your GPS location is still available.';
|
||||
return;
|
||||
}
|
||||
config.apiKey = env.PUBLIC_MAPTILER_API_KEY;
|
||||
config.apiKey = PUBLIC_MAPTILER_API_KEY;
|
||||
map = new Map({
|
||||
container: mapContainer,
|
||||
style: `https://api.maptiler.com/maps/satellite/style.json?key=${env.PUBLIC_MAPTILER_API_KEY}`,
|
||||
style: `https://api.maptiler.com/maps/satellite/style.json?key=${PUBLIC_MAPTILER_API_KEY}`,
|
||||
center: [lng!, lat!],
|
||||
zoom: 15
|
||||
});
|
||||
@@ -181,7 +181,7 @@
|
||||
for (const file of photoFiles) {
|
||||
const uploadForm = new FormData();
|
||||
uploadForm.set('photo', file);
|
||||
const uploadRes = await fetch(resolve('/projects/animaldex/api/upload'), {
|
||||
const uploadRes = await fetch(resolve('projects/animaldex/api/upload'), {
|
||||
method: 'POST',
|
||||
body: uploadForm
|
||||
});
|
||||
@@ -193,8 +193,8 @@
|
||||
if (reporterName) localStorage.setItem('reporterName', reporterName);
|
||||
|
||||
const endpoint = isNewSighting
|
||||
? resolve(`/projects/animaldex/api/animals/${animalId}/sightings`)
|
||||
: resolve('/projects/animaldex/api/animals');
|
||||
? resolve(`projects/animaldex/api/animals/${animalId}/sightings`)
|
||||
: resolve('projects/animaldex/api/animals');
|
||||
const body = isNewSighting
|
||||
? {
|
||||
animalId,
|
||||
@@ -229,7 +229,7 @@
|
||||
const err = await res.json();
|
||||
throw new Error(err.message ?? 'Submission failed');
|
||||
}
|
||||
goto(resolve('/projects/animaldex?submitted=1'));
|
||||
goto(resolve('projects/animaldex?submitted=1'));
|
||||
} catch (e: unknown) {
|
||||
submitError = e instanceof Error ? e.message : 'Something went wrong.';
|
||||
} finally {
|
||||
@@ -244,7 +244,7 @@
|
||||
|
||||
<section class="mx-auto max-w-3xl px-4 py-10 sm:px-6 sm:py-14">
|
||||
<a
|
||||
href={resolve('/projects/animaldex')}
|
||||
href={resolve('projects/animaldex')}
|
||||
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)] hover:text-[var(--site-text)]"
|
||||
><ArrowLeft size={16} aria-hidden="true" /> animaldex</a
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user