From 495e67f14d2ed1f8674802f09276bc8e29f37342 Mon Sep 17 00:00:00 2001 From: zias Date: Mon, 8 Dec 2025 18:15:41 +0100 Subject: [PATCH 1/3] feat:use locations&finds big overhaul! now we use locations that can have finds. multiple finds can be at the same location, so users can register the same place. --- drizzle/0008_location_refactor.sql | 47 + src/lib/components/button/button.svelte | 3 +- .../components/finds/CreateFindModal.svelte | 171 +-- .../locations/CreateLocationModal.svelte | 443 +++++++ .../components/locations/LocationCard.svelte | 236 ++++ .../locations/LocationFindsModal.svelte | 324 +++++ .../components/locations/LocationsList.svelte | 186 +++ .../locations/SelectLocationModal.svelte | 1078 +++++++++++++++++ src/lib/components/locations/index.ts | 5 + src/lib/components/map/Map.svelte | 117 +- .../notifications/NotificationManager.svelte | 4 +- src/lib/server/db/schema.ts | 20 +- src/lib/utils/distance.ts | 41 + src/routes/+page.server.ts | 10 +- src/routes/+page.svelte | 455 ++----- src/routes/api/finds/+server.ts | 76 +- src/routes/api/finds/[findId]/+server.ts | 34 +- src/routes/api/locations/+server.ts | 262 ++++ src/routes/finds/[findId]/+page.svelte | 36 - 19 files changed, 2909 insertions(+), 639 deletions(-) create mode 100644 drizzle/0008_location_refactor.sql create mode 100644 src/lib/components/locations/CreateLocationModal.svelte create mode 100644 src/lib/components/locations/LocationCard.svelte create mode 100644 src/lib/components/locations/LocationFindsModal.svelte create mode 100644 src/lib/components/locations/LocationsList.svelte create mode 100644 src/lib/components/locations/SelectLocationModal.svelte create mode 100644 src/lib/components/locations/index.ts create mode 100644 src/lib/utils/distance.ts create mode 100644 src/routes/api/locations/+server.ts diff --git a/drizzle/0008_location_refactor.sql b/drizzle/0008_location_refactor.sql new file mode 100644 index 0000000..2c950aa --- /dev/null +++ b/drizzle/0008_location_refactor.sql @@ -0,0 +1,47 @@ +-- Create location table +CREATE TABLE "location" ( + "id" text PRIMARY KEY NOT NULL, + "user_id" text NOT NULL, + "latitude" text NOT NULL, + "longitude" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint + +-- Add foreign key constraint for location table +ALTER TABLE "location" ADD CONSTRAINT "location_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +--> statement-breakpoint + +-- Migrate existing find data to location table and update find table +-- First, create locations from existing finds +INSERT INTO "location" ("id", "user_id", "latitude", "longitude", "created_at") +SELECT + 'loc_' || "id" as "id", + "user_id", + "latitude", + "longitude", + "created_at" +FROM "find"; +--> statement-breakpoint + +-- Add location_id column to find table +ALTER TABLE "find" ADD COLUMN "location_id" text; +--> statement-breakpoint + +-- Update find table to reference the new location entries +UPDATE "find" +SET "location_id" = 'loc_' || "id"; +--> statement-breakpoint + +-- Make location_id NOT NULL +ALTER TABLE "find" ALTER COLUMN "location_id" SET NOT NULL; +--> statement-breakpoint + +-- Add foreign key constraint +ALTER TABLE "find" ADD CONSTRAINT "find_location_id_location_id_fk" FOREIGN KEY ("location_id") REFERENCES "public"."location"("id") ON DELETE cascade ON UPDATE no action; +--> statement-breakpoint + +-- Drop the latitude and longitude columns from find table +ALTER TABLE "find" DROP COLUMN "latitude"; +--> statement-breakpoint +ALTER TABLE "find" DROP COLUMN "longitude"; diff --git a/src/lib/components/button/button.svelte b/src/lib/components/button/button.svelte index ca02421..16f1a3c 100644 --- a/src/lib/components/button/button.svelte +++ b/src/lib/components/button/button.svelte @@ -2,7 +2,6 @@ import { cn, type WithElementRef } from '$lib/utils.js'; import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements'; import { type VariantProps, tv } from 'tailwind-variants'; - import { resolveRoute } from '$app/paths'; export const buttonVariants = tv({ base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", @@ -59,7 +58,7 @@ bind:this={ref} data-slot="button" class={cn(buttonVariants({ variant, size }), className)} - href={disabled ? undefined : resolveRoute(href)} + href={disabled ? undefined : href} aria-disabled={disabled} role={disabled ? 'link' : undefined} tabindex={disabled ? -1 : undefined} diff --git a/src/lib/components/finds/CreateFindModal.svelte b/src/lib/components/finds/CreateFindModal.svelte index efa7c82..ca52678 100644 --- a/src/lib/components/finds/CreateFindModal.svelte +++ b/src/lib/components/finds/CreateFindModal.svelte @@ -2,29 +2,24 @@ import { Input } from '$lib/components/input'; import { Label } from '$lib/components/label'; import { Button } from '$lib/components/button'; - import { coordinates } from '$lib/stores/location'; - import POISearch from '../map/POISearch.svelte'; - import type { PlaceResult } from '$lib/utils/places'; interface Props { isOpen: boolean; + locationId: string; onClose: () => void; onFindCreated: (event: CustomEvent) => void; } - let { isOpen, onClose, onFindCreated }: Props = $props(); + let { isOpen, locationId, onClose, onFindCreated }: Props = $props(); let title = $state(''); let description = $state(''); - let latitude = $state(''); - let longitude = $state(''); let locationName = $state(''); let category = $state('cafe'); let isPublic = $state(true); let selectedFiles = $state(null); let isSubmitting = $state(false); let uploadedMedia = $state>([]); - let useManualLocation = $state(false); const categories = [ { value: 'cafe', label: 'Café' }, @@ -51,13 +46,6 @@ return () => window.removeEventListener('resize', checkIsMobile); }); - $effect(() => { - if (isOpen && $coordinates) { - latitude = $coordinates.latitude.toString(); - longitude = $coordinates.longitude.toString(); - } - }); - function handleFileChange(event: Event) { const target = event.target as HTMLInputElement; selectedFiles = target.files; @@ -85,10 +73,7 @@ } async function handleSubmit() { - const lat = parseFloat(latitude); - const lng = parseFloat(longitude); - - if (!title.trim() || isNaN(lat) || isNaN(lng)) { + if (!title.trim()) { return; } @@ -105,10 +90,9 @@ 'Content-Type': 'application/json' }, body: JSON.stringify({ + locationId, title: title.trim(), description: description.trim() || null, - latitude: lat, - longitude: lng, locationName: locationName.trim() || null, category, isPublic, @@ -131,31 +115,14 @@ } } - function handlePlaceSelected(place: PlaceResult) { - locationName = place.name; - latitude = place.latitude.toString(); - longitude = place.longitude.toString(); - } - - function toggleLocationMode() { - useManualLocation = !useManualLocation; - if (!useManualLocation && $coordinates) { - latitude = $coordinates.latitude.toString(); - longitude = $coordinates.longitude.toString(); - } - } - function resetForm() { title = ''; description = ''; locationName = ''; - latitude = ''; - longitude = ''; category = 'cafe'; isPublic = true; selectedFiles = null; uploadedMedia = []; - useManualLocation = false; const fileInput = document.querySelector('#media-files') as HTMLInputElement; if (fileInput) { @@ -210,31 +177,13 @@ > -
-
- - -
- - {#if useManualLocation} -
- - -
- {:else} - - {/if} +
+ +
@@ -307,34 +256,6 @@
{/if}
- - {#if useManualLocation || (!latitude && !longitude)} -
-
- - -
-
- - -
-
- {:else if latitude && longitude} -
- -
- Lat: {parseFloat(latitude).toFixed(6)} - Lng: {parseFloat(longitude).toFixed(6)} - -
-
- {/if}