From bd903cf95d185c9c8013d680a7acf9c6bbd86d0f Mon Sep 17 00:00:00 2001 From: Aytsuu Date: Tue, 19 May 2026 03:10:23 +0800 Subject: [PATCH 1/9] feat: add Mapbox MCP configuration and integration - Introduced Mapbox MCP server configuration in .codex/config.toml. - Updated .gemini/settings.json to include Mapbox MCP in allowed servers and added its configuration details. - Enhanced mcp-configs/mcp-servers.json with Mapbox MCP server information and description for geospatial services. --- .codex/config.toml | 4 ++++ .gemini/settings.json | 9 +++++++-- mcp-configs/mcp-servers.json | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.codex/config.toml b/.codex/config.toml index aaa379e..1dde217 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -37,6 +37,10 @@ url = "https://mcp.exa.ai/mcp" url = "https://mcp.supabase.com/mcp" startup_timeout_sec = 30 +[mcp_servers.mapbox-mcp] +url = "https://mcp.mapbox.com/mcp" +startup_timeout_sec = 30 + [mcp_servers.github] command = "npx" args = ["-y", "@modelcontextprotocol/server-github"] diff --git a/.gemini/settings.json b/.gemini/settings.json index 8c35f0c..8ba88b5 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -1,6 +1,6 @@ { "mcp": { - "allowed": ["magic", "token-optimizer", "playwright", "firecrawl" ,"memory", "context7", "github"], + "allowed": ["magic", "token-optimizer", "playwright", "firecrawl" ,"memory", "context7", "github", "mapbox-mcp"], "excluded": ["experimental-server"] }, "mcpServers": { @@ -46,6 +46,11 @@ "GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_PAT_HERE" }, "description": "GitHub operations - PRs, issues, repos" + }, + "mapbox-mcp": { + "type": "http", + "url": "https://mcp.mapbox.com/mcp", + "description": "Mapbox geospatial MCP via hosted endpoint with OAuth - geocoding, POI search, routing, isochrones, and static maps" } } -} \ No newline at end of file +} diff --git a/mcp-configs/mcp-servers.json b/mcp-configs/mcp-servers.json index b2a3b75..a6e89c0 100644 --- a/mcp-configs/mcp-servers.json +++ b/mcp-configs/mcp-servers.json @@ -81,6 +81,11 @@ "url": "https://mcp.clickhouse.cloud/mcp", "description": "ClickHouse analytics queries" }, + "mapbox-mcp": { + "type": "http", + "url": "https://mcp.mapbox.com/mcp", + "description": "Mapbox geospatial MCP via hosted endpoint with OAuth - geocoding, POI search, routing, isochrones, and static maps" + }, "exa-web-search": { "command": "npx", "args": ["-y", "exa-mcp-server"], From 9f157bafadb5dedfd6136ddc5b87334ae54ca539 Mon Sep 17 00:00:00 2001 From: Aytsuu Date: Tue, 19 May 2026 03:11:10 +0800 Subject: [PATCH 2/9] feat: integrate Mapbox and update global styles - Added Mapbox GL and related dependencies for enhanced mapping features. - Introduced a new Globe component for interactive map visualization. - Updated global styles to use Geist font family for improved UI consistency. - Refactored the Home page to utilize the new Globe component, replacing the previous layout. --- www/app/globals.css | 6 +- www/app/layout.tsx | 12 +- www/app/page.tsx | 71 +---- www/components/Globe.tsx | 191 ++++++++++++++ www/package-lock.json | 550 ++++++++++++++++++++++++++++++++++++++- www/package.json | 6 +- 6 files changed, 757 insertions(+), 79 deletions(-) create mode 100644 www/components/Globe.tsx diff --git a/www/app/globals.css b/www/app/globals.css index f2a2a99..c154da3 100644 --- a/www/app/globals.css +++ b/www/app/globals.css @@ -8,12 +8,12 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-fraunces); - --font-mono: var(--font-jetbrains-mono); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); } body { background: var(--background); color: var(--foreground); - font-family: var(--font-fraunces), serif; + font-family: var(--font-geist-sans), sans-serif; } diff --git a/www/app/layout.tsx b/www/app/layout.tsx index 1f201a3..7671e42 100644 --- a/www/app/layout.tsx +++ b/www/app/layout.tsx @@ -1,14 +1,14 @@ import type { Metadata } from "next"; -import { Fraunces, JetBrains_Mono } from "next/font/google"; +import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; -const fraunces = Fraunces({ - variable: "--font-fraunces", +const geistSans = Geist({ + variable: "--font-geist-sans", subsets: ["latin"], }); -const jetBrainsMono = JetBrains_Mono({ - variable: "--font-jetbrains-mono", +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", subsets: ["latin"], }); @@ -25,7 +25,7 @@ export default function RootLayout({ return ( {children} diff --git a/www/app/page.tsx b/www/app/page.tsx index 270e3b2..b778d41 100644 --- a/www/app/page.tsx +++ b/www/app/page.tsx @@ -1,72 +1,9 @@ -import Link from "next/link"; +import Globe from "@/components/Globe"; -import { createClient } from "@/lib/supabase/server"; - -export default async function Home() { - const supabase = await createClient(); - const { - data: { user }, - } = await supabase.auth.getUser(); +export default function Home() { return ( -
-
-
-
-

- LoopHarvest MVP -

-

- Supabase database and auth are now first-class citizens in the app shell. -

-

- The repo now includes a Supabase schema migration, row-level security - policies, SSR auth helpers for Next.js, and bearer-token validation hooks - for the FastAPI service. -

-
- - {user ? "Open dashboard" : "Open auth flow"} - - - Open Supabase dashboard - -
-
- -
- {[ - [ - "Database", - "Tracked in SQL migrations with users, listings, requests, transactions, notifications, RLS, and matching RPC.", - ], - [ - "Frontend auth", - "Supabase SSR clients, OAuth callback, route guard, and protected dashboard flow are wired into the App Router.", - ], - [ - "API auth", - "FastAPI verifies Supabase bearer tokens and stops trusting user ids sent in POST bodies.", - ], - ].map(([title, body]) => ( -
-

{title}

-

{body}

-
- ))} -
-
-
+
+
); } diff --git a/www/components/Globe.tsx b/www/components/Globe.tsx new file mode 100644 index 0000000..fbf02ad --- /dev/null +++ b/www/components/Globe.tsx @@ -0,0 +1,191 @@ +'use client'; + +import * as React from 'react'; +import Map, { Marker } from 'react-map-gl/mapbox'; +import { Leaf, HelpCircle, X, Clock, Globe as GlobeIcon, Map as MapIcon } from 'lucide-react'; +import 'mapbox-gl/dist/mapbox-gl.css'; + +interface UserPin { + id: string; + name: string; + latitude: number; + longitude: number; + avatar: string; + type: 'request' | 'listing'; +} + +const MOCK_USERS: UserPin[] = [ + { id: '1', name: 'Alice', latitude: 37.7749, longitude: -122.4194, avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Alice', type: 'listing' }, + { id: '2', name: 'Bob', latitude: 51.5074, longitude: -0.1276, avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Bob', type: 'request' }, + { id: '3', name: 'Charlie', latitude: 35.6762, longitude: 139.6503, avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Charlie', type: 'listing' }, + { id: '4', name: 'Diana', latitude: -33.8688, longitude: 151.2093, avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Diana', type: 'request' }, + { id: '5', name: 'Ethan', latitude: 40.7128, longitude: -74.0060, avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Ethan', type: 'listing' }, + { id: '6', name: 'Fiona', latitude: -23.5505, longitude: -46.6333, avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Fiona', type: 'request' }, + { id: '7', name: 'George', latitude: 48.8566, longitude: 2.3522, avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=George', type: 'listing' }, +]; + +interface GlobeProps { + accessToken?: string; +} + +export default function Globe({ accessToken }: GlobeProps) { + const [projection, setProjection] = React.useState<'globe' | 'mercator'>('globe'); + const [selectedUser, setSelectedUser] = React.useState(null); + const token = accessToken || process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN; + + if (!token) { + return ( +
+
+

Mapbox Token Missing

+

Please set NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN in your .env file.

+
+
+ ); + } + + return ( +
+ + {MOCK_USERS.map((user) => ( + { + e.originalEvent.stopPropagation(); + setSelectedUser(user); + }} + > +
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {user.name} +
+ + {/* Type Indicator Badge - Floating Outside */} +
+ {user.type === 'listing' ? ( + + ) : ( + + )} +
+
+
+ {user.name} +
+
+
+ + ))} + + + {/* Projection Toggle */} +
+ +
+ + {/* Event Modal */} + {selectedUser && ( +
setSelectedUser(null)}> +
e.stopPropagation()}> + + +
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {selectedUser.name} +
+
+ {selectedUser.type === 'listing' ? ( + + ) : ( + + )} +
+
+ +

{selectedUser.name}

+

+ {selectedUser.type === 'listing' ? 'Active Listing' : 'Open Request'} +

+ +
+
+

Item Details

+

+ {selectedUser.type === 'listing' + ? "Fresh garden produce: Organic tomatoes and basil available for pickup." + : "Looking for excess sourdough starter or bread flour."} +

+
+ +
+ + Expires in 4 hours +
+
+ + +
+
+
+ )} +
+ ); +} diff --git a/www/package-lock.json b/www/package-lock.json index fcce40d..2b7cfaa 100644 --- a/www/package-lock.json +++ b/www/package-lock.json @@ -10,12 +10,16 @@ "dependencies": { "@supabase/ssr": "^0.10.3", "@supabase/supabase-js": "^2.105.4", + "lucide-react": "^1.16.0", + "mapbox-gl": "^3.24.0", "next": "16.2.6", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "react-map-gl": "^8.1.1" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/mapbox-gl": "^3.4.1", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -1037,6 +1041,68 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz", + "integrity": "sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg==", + "license": "BSD-3-Clause" + }, + "node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz", + "integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.2.0.tgz", + "integrity": "sha512-LVL4wgI9YAum5V+LNVQO6QgFBPw7/MIIY4XJPNsPDMrjEwcE+JfKk1LuIl8GnF197ejVdC9QdPaxrx5gfgdGXg==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz", + "integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==", + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^4.0.1" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz", + "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1643,6 +1709,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/geojson-vt": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/geojson-vt/-/geojson-vt-3.2.5.tgz", + "integrity": "sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1657,6 +1738,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mapbox-gl": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz", + "integrity": "sha512-NsGKKtgW93B+UaLPti6B7NwlxYlES5DpV5Gzj9F75rK5ALKsqSk15CiEHbOnTr09RGbr6ZYiCdI+59NNNcAImg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "20.19.41", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", @@ -1667,6 +1758,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/pbf": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.5.tgz", + "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -1687,6 +1784,15 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/supercluster": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz", + "integrity": "sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.59.3", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", @@ -2251,6 +2357,41 @@ "win32" ] }, + "node_modules/@vis.gl/react-mapbox": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vis.gl/react-mapbox/-/react-mapbox-8.1.1.tgz", + "integrity": "sha512-KMDTjtWESXxHS4uqWxjsvgQUHvuL3Z6SdKe68o7Nxma2qUfuyH3x4TCkIqGn3FQTrFvZLWvTnSAbGvtm+Kd13A==", + "license": "MIT", + "peerDependencies": { + "mapbox-gl": ">=3.5.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + } + } + }, + "node_modules/@vis.gl/react-maplibre": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@vis.gl/react-maplibre/-/react-maplibre-8.1.1.tgz", + "integrity": "sha512-iUOfzJAhFAJwEZp1644tQb7LOTFgi5/GzdaztkhzNgFVuoF2Ez7guvwZjQAKB9CN2TlHTgNuYH8UW85kO7cVhw==", + "license": "MIT", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1" + }, + "peerDependencies": { + "maplibre-gl": ">=4.0.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "maplibre-gl": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2324,6 +2465,15 @@ "node": ">= 0.4" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -2484,6 +2634,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -2614,6 +2773,25 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", + "license": "MIT", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", + "license": "MIT", + "dependencies": { + "typewise-core": "^1.2" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -2711,6 +2889,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cheap-ruler": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cheap-ruler/-/cheap-ruler-4.0.0.tgz", + "integrity": "sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==", + "license": "ISC" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2779,6 +2963,12 @@ "node": ">= 8" } }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2946,6 +3136,12 @@ "node": ">= 0.4" } }, + "node_modules/earcut": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz", + "integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==", + "license": "ISC" + }, "node_modules/electron-to-chromium": { "version": "1.5.357", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.357.tgz", @@ -3580,6 +3776,18 @@ "node": ">=0.10.0" } }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3782,6 +3990,12 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-vt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-4.0.3.tgz", + "integrity": "sha512-jR1MwkLaZGa8Zftct9ZFruyWFrdl9ZyD2OliXNy9Qq5bBPeg5wHVpBQF9p5GjnicSDQqvBVpysxTPKmWdsfWMA==", + "license": "ISC" + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3852,6 +4066,21 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4245,6 +4474,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4357,6 +4595,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4516,6 +4766,15 @@ "dev": true, "license": "ISC" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -4598,6 +4857,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz", + "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4627,6 +4892,12 @@ "node": ">=4.0" } }, + "node_modules/kdbush": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", + "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", + "license": "ISC" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4978,6 +5249,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz", + "integrity": "sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -4988,6 +5268,54 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mapbox-gl": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.24.0.tgz", + "integrity": "sha512-R+FdFUB3DnoE5FYASV7lGSiRyMkSblcZ2UEy7b2pt7s5ZbCxFIUPXd0E6iAFd8OdvdA2VtbvZZVylzAZNaurjA==", + "license": "SEE LICENSE IN LICENSE.txt", + "workspaces": [ + "src/style-spec", + "plugins/mapbox-gl-pmtiles-provider", + "test/build/vite", + "test/build/webpack", + "test/build/typings" + ], + "dependencies": { + "@mapbox/mapbox-gl-supported": "^3.0.0", + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.0.6", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^2.0.4", + "@types/geojson": "^7946.0.16", + "@types/geojson-vt": "^3.2.5", + "@types/pbf": "^3.0.5", + "@types/supercluster": "^7.1.3", + "cheap-ruler": "^4.0.0", + "csscolorparser": "~1.0.3", + "earcut": "^3.0.1", + "geojson-vt": "^4.0.2", + "gl-matrix": "^3.4.4", + "kdbush": "^4.0.2", + "martinez-polygon-clipping": "^0.8.1", + "murmurhash-js": "^1.0.0", + "pbf": "^4.0.1", + "potpack": "^2.0.0", + "quickselect": "^3.0.0", + "supercluster": "^8.0.1", + "tinyqueue": "^3.0.0" + } + }, + "node_modules/martinez-polygon-clipping": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/martinez-polygon-clipping/-/martinez-polygon-clipping-0.8.1.tgz", + "integrity": "sha512-9PLLMzMPI6ihHox4Ns6LpVBLpRc7sbhULybZ/wyaY8sY3ECNe2+hxm1hA2/9bEEpRrdpjoeduBuZLg2aq1cSIQ==", + "license": "MIT", + "dependencies": { + "robust-predicates": "^2.0.4", + "splaytree": "^0.1.4", + "tinyqueue": "3.0.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5039,7 +5367,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5052,6 +5379,12 @@ "dev": true, "license": "MIT" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -5431,6 +5764,18 @@ "dev": true, "license": "MIT" }, + "node_modules/pbf": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5489,6 +5834,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/potpack": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.1.0.tgz", + "integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==", + "license": "ISC" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5511,6 +5862,12 @@ "react-is": "^16.13.1" } }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5542,6 +5899,12 @@ ], "license": "MIT" }, + "node_modules/quickselect": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "license": "ISC" + }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -5570,6 +5933,30 @@ "dev": true, "license": "MIT" }, + "node_modules/react-map-gl": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-8.1.1.tgz", + "integrity": "sha512-aSqFAFoxvY7wxbGI93Dz0E41171mkAb3GcNbnkFIotmu88OFw495os6mIDZSi7irYNT/PZEIOEHUxhun4ToGuQ==", + "license": "MIT", + "dependencies": { + "@vis.gl/react-mapbox": "8.1.1", + "@vis.gl/react-maplibre": "8.1.1" + }, + "peerDependencies": { + "mapbox-gl": ">=1.13.0", + "maplibre-gl": ">=1.13.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "mapbox-gl": { + "optional": true + }, + "maplibre-gl": { + "optional": true + } + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5658,6 +6045,15 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -5669,6 +6065,12 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==", + "license": "Unlicense" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5693,6 +6095,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", @@ -5813,6 +6221,21 @@ "node": ">= 0.4" } }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -5970,6 +6393,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sort-asc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", + "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", + "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", + "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", + "license": "MIT", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5979,6 +6437,49 @@ "node": ">=0.10.0" } }, + "node_modules/splaytree": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-0.1.4.tgz", + "integrity": "sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ==", + "license": "MIT" + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -6159,6 +6660,15 @@ } } }, + "node_modules/supercluster": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz", + "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==", + "license": "ISC", + "dependencies": { + "kdbush": "^4.0.2" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6254,6 +6764,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6441,6 +6957,21 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", + "license": "MIT", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==", + "license": "MIT" + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -6467,6 +6998,21 @@ "dev": true, "license": "MIT" }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", diff --git a/www/package.json b/www/package.json index 4508f02..61fec5c 100644 --- a/www/package.json +++ b/www/package.json @@ -11,12 +11,16 @@ "dependencies": { "@supabase/ssr": "^0.10.3", "@supabase/supabase-js": "^2.105.4", + "lucide-react": "^1.16.0", + "mapbox-gl": "^3.24.0", "next": "16.2.6", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "react-map-gl": "^8.1.1" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/mapbox-gl": "^3.4.1", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", From f8e9510547b932c3cf88988ab99ee8df2dd5b2d2 Mon Sep 17 00:00:00 2001 From: Aytsuu Date: Wed, 20 May 2026 00:17:07 +0800 Subject: [PATCH 3/9] Update --- .gemini/GEMINI.md | 6 ++++++ scripts.json | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md index 43bc69c..6ee3980 100644 --- a/.gemini/GEMINI.md +++ b/.gemini/GEMINI.md @@ -8,6 +8,12 @@ Everything Claude Code (ECC) is a cross-harness coding system with 36 specialize Gemini support is currently focused on a strong project-local instruction layer via `.gemini/GEMINI.md`, plus the shared MCP catalog and package-manager setup assets shipped by the installer. +## Operational Scope + +- **Default Scope:** You are mandated to work **exclusively on the frontend** codebase. +- **Backend Changes:** Do NOT modify any files in the `api/` directory unless the user explicitly instructs you to perform backend changes for a specific task. +- **Research:** You may read backend files for context or verification, but implementation must remain frontend-only by default. + ## Core Workflow 1. Plan before editing large features. diff --git a/scripts.json b/scripts.json index 14ece6e..a8ccbde 100644 --- a/scripts.json +++ b/scripts.json @@ -1,6 +1,9 @@ { "Obsidian": { - "description": "Obsidian vault ingestion on codex session", - "command": "codex --sandbox danger-full-access --add-dir D:/vault/personal/LoopHarvest" + "description": "Obsidian vault ingestion for ai sessions", + "command": { + "codex": "codex --sandbox danger-full-access --add-dir D:/vault/personal/LoopHarvest", + "gemini": "gemini --include-directories D:/vault/personal/LoopHarvest" + } } } \ No newline at end of file From 4ebda9db3e4de8b86bfbb0e6b7c38d7745619cb7 Mon Sep 17 00:00:00 2001 From: Aytsuu Date: Wed, 20 May 2026 12:11:39 +0800 Subject: [PATCH 4/9] chore: replace gemini command with antigravity and initialize MCP server settings --- .gemini/settings.json | 62 ++++++++++++++++++++++++++++++++++++------- scripts.json | 2 +- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/.gemini/settings.json b/.gemini/settings.json index 8ba88b5..1578450 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -1,29 +1,62 @@ { "mcp": { - "allowed": ["magic", "token-optimizer", "playwright", "firecrawl" ,"memory", "context7", "github", "mapbox-mcp"], - "excluded": ["experimental-server"] + "allowed": [ + "magic", + "token-optimizer", + "playwright", + "firecrawl", + "memory", + "context7", + "github", + "mapbox-mcp" + ], + "excluded": [ + "experimental-server" + ] }, "mcpServers": { "magic": { "command": "npx", - "args": ["-y", "@magicuidesign/mcp@latest"], + "args": [ + "-y", + "@magicuidesign/mcp@latest" + ], "description": "Magic UI components", "timeout": 30000, "trust": true }, + "mui-mcp": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@mui/mcp@latest" + ] + }, "token-optimizer": { "command": "npx", - "args": ["-y", "token-optimizer-mcp"], + "args": [ + "-y", + "token-optimizer-mcp" + ], "description": "Token optimization for 95%+ context reduction via content deduplication and compression" }, "playwright": { "command": "npx", - "args": ["-y", "@playwright/mcp", "--browser", "chrome"], + "args": [ + "-y", + "@playwright/mcp", + "--browser", + "chrome" + ], "description": "Browser automation and testing via Playwright" }, "firecrawl": { "command": "npx", - "args": ["-y", "firecrawl-mcp"], + "args": [ + "-y", + "firecrawl-mcp" + ], "env": { "FIRECRAWL_API_KEY": "YOUR_FIRECRAWL_KEY_HERE" }, @@ -31,17 +64,26 @@ }, "memory": { "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-memory"], + "args": [ + "-y", + "@modelcontextprotocol/server-memory" + ], "description": "Persistent memory across sessions" }, "context7": { "command": "npx", - "args": ["-y", "@upstash/context7-mcp@latest"], + "args": [ + "-y", + "@upstash/context7-mcp@latest" + ], "description": "Live documentation lookup — use with /docs command and documentation-lookup skill (resolve-library-id, query-docs)." }, "github": { "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-github"], + "args": [ + "-y", + "@modelcontextprotocol/server-github" + ], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_PAT_HERE" }, @@ -53,4 +95,4 @@ "description": "Mapbox geospatial MCP via hosted endpoint with OAuth - geocoding, POI search, routing, isochrones, and static maps" } } -} +} \ No newline at end of file diff --git a/scripts.json b/scripts.json index a8ccbde..3199878 100644 --- a/scripts.json +++ b/scripts.json @@ -3,7 +3,7 @@ "description": "Obsidian vault ingestion for ai sessions", "command": { "codex": "codex --sandbox danger-full-access --add-dir D:/vault/personal/LoopHarvest", - "gemini": "gemini --include-directories D:/vault/personal/LoopHarvest" + "antigravity": "agy --add-dir D:/vault/personal/LoopHarvest" } } } \ No newline at end of file From 0f6a5aafdecf037ba4c95747ba285f6c74fd3806 Mon Sep 17 00:00:00 2001 From: Aytsuu Date: Wed, 20 May 2026 13:00:12 +0800 Subject: [PATCH 5/9] feat: initialize MCP configuration files for antigravity cli --- .gemini/antigravity-cli/mcp_config.json | 84 +++++++++++++++++++++++++ .gemini/settings.json | 1 + 2 files changed, 85 insertions(+) create mode 100644 .gemini/antigravity-cli/mcp_config.json diff --git a/.gemini/antigravity-cli/mcp_config.json b/.gemini/antigravity-cli/mcp_config.json new file mode 100644 index 0000000..de28638 --- /dev/null +++ b/.gemini/antigravity-cli/mcp_config.json @@ -0,0 +1,84 @@ +{ + "mcp": { + "allowed": [ + "magic", + "mui-mcp", + "token-optimizer", + "playwright", + "firecrawl", + "memory", + "context7", + "github", + "mapbox-mcp" + ] + }, + "mcpServers": { + "magic": { + "command": "npx", + "args": [ + "-y", + "@magicuidesign/mcp@latest" + ] + }, + "mui-mcp": { + "command": "npx", + "args": [ + "-y", + "@mui/mcp@latest" + ] + }, + "token-optimizer": { + "command": "npx", + "args": [ + "-y", + "token-optimizer-mcp" + ] + }, + "playwright": { + "command": "npx", + "args": [ + "-y", + "@playwright/mcp", + "--browser", + "chrome" + ] + }, + "firecrawl": { + "command": "npx", + "args": [ + "-y", + "firecrawl-mcp" + ], + "env": { + "FIRECRAWL_API_KEY": "YOUR_FIRECRAWL_KEY_HERE" + } + }, + "memory": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-memory" + ] + }, + "context7": { + "command": "npx", + "args": [ + "-y", + "@upstash/context7-mcp@latest" + ] + }, + "github": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-github" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_PAT_HERE" + } + }, + "mapbox-mcp": { + "serverUrl": "https://mcp.mapbox.com/mcp" + } + } +} \ No newline at end of file diff --git a/.gemini/settings.json b/.gemini/settings.json index 1578450..3c798c7 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -2,6 +2,7 @@ "mcp": { "allowed": [ "magic", + "mui-mcp", "token-optimizer", "playwright", "firecrawl", From 1bbd279367fb0ad131b1e0bde7680bfad9bce8df Mon Sep 17 00:00:00 2001 From: Aytsuu Date: Wed, 20 May 2026 23:27:19 +0800 Subject: [PATCH 6/9] feat: optimize mobile/desktop layouts, themes preferences, and navigation hover tooltips - Implement premium, custom hover tooltips for collapsed NavigationRail - Remove standard HTML title attributes to avoid double overlaps - Design solid category contrasting badges and list card status footers - Build fully-functional ThemeSync system for local storage, accessibility contrast/motion preferences, and root density font scaling - Solve sticky action footer overlap with responsive NavigationRail offset - Refactor UN SDG cards to a high-fidelity responsive grid with SVG badges and dynamic hover glows - Extract top mobile browse headers and build categories smooth-scroll chevron navigations --- .gitignore | 2 +- antigravity-sessions.md | 1 + codex-session.md => codex-sessions.md | 0 www/app/(app)/browse/page.tsx | 403 +++++++ www/app/(app)/home/page.tsx | 404 +++++++ www/app/(app)/impact/page.tsx | 259 +++++ www/app/(app)/layout.tsx | 234 ++++ www/app/(app)/notifications/page.tsx | 144 +++ www/app/(app)/post/listing/page.tsx | 365 ++++++ www/app/(app)/post/request/page.tsx | 284 +++++ www/app/(app)/requests/[id]/page.tsx | 286 +++++ www/app/(app)/settings/page.tsx | 1036 +++++++++++++++++ www/app/(app)/users/[id]/page.tsx | 279 +++++ www/app/auth/callback/route.ts | 21 - www/app/dashboard/page.tsx | 62 - www/app/globals.css | 326 +++++- www/app/layout.tsx | 38 +- www/app/login/actions.ts | 78 +- www/app/login/page.tsx | 290 +++-- www/app/page.tsx | 271 ++++- www/app/signup/page.tsx | 201 ++++ www/components/Globe.tsx | 435 +++++-- www/components/cards/ListingCard.tsx | 90 ++ www/components/cards/RequestCard.tsx | 77 ++ www/components/common/CategoryChip.tsx | 70 ++ www/components/common/ThemeSync.tsx | 61 + www/components/navigation/BottomNav.tsx | 69 ++ www/components/navigation/NavigationRail.tsx | 206 ++++ www/lib/categories.ts | 14 + www/lib/mockStore.ts | 471 ++++++++ www/lib/supabase/client.ts | 14 + www/lib/supabase/env.ts | 16 + www/lib/supabase/server.ts | 25 + www/lib/theme.ts | 83 ++ www/list-tools.js | 67 ++ www/next.config.ts | 18 +- www/package-lock.json | 1084 +++++++++++++++++- www/package.json | 8 +- www/proxy.ts | 88 +- www/public/logo.png | Bin 0 -> 763360 bytes 40 files changed, 7429 insertions(+), 451 deletions(-) create mode 100644 antigravity-sessions.md rename codex-session.md => codex-sessions.md (100%) create mode 100644 www/app/(app)/browse/page.tsx create mode 100644 www/app/(app)/home/page.tsx create mode 100644 www/app/(app)/impact/page.tsx create mode 100644 www/app/(app)/layout.tsx create mode 100644 www/app/(app)/notifications/page.tsx create mode 100644 www/app/(app)/post/listing/page.tsx create mode 100644 www/app/(app)/post/request/page.tsx create mode 100644 www/app/(app)/requests/[id]/page.tsx create mode 100644 www/app/(app)/settings/page.tsx create mode 100644 www/app/(app)/users/[id]/page.tsx delete mode 100644 www/app/auth/callback/route.ts delete mode 100644 www/app/dashboard/page.tsx create mode 100644 www/app/signup/page.tsx create mode 100644 www/components/cards/ListingCard.tsx create mode 100644 www/components/cards/RequestCard.tsx create mode 100644 www/components/common/CategoryChip.tsx create mode 100644 www/components/common/ThemeSync.tsx create mode 100644 www/components/navigation/BottomNav.tsx create mode 100644 www/components/navigation/NavigationRail.tsx create mode 100644 www/lib/categories.ts create mode 100644 www/lib/mockStore.ts create mode 100644 www/lib/supabase/client.ts create mode 100644 www/lib/supabase/env.ts create mode 100644 www/lib/supabase/server.ts create mode 100644 www/lib/theme.ts create mode 100644 www/list-tools.js create mode 100644 www/public/logo.png diff --git a/.gitignore b/.gitignore index 83972fa..620150d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ dist/ downloads/ eggs/ .eggs/ -lib/ +api/lib/ lib64/ parts/ sdist/ diff --git a/antigravity-sessions.md b/antigravity-sessions.md new file mode 100644 index 0000000..dc87a4b --- /dev/null +++ b/antigravity-sessions.md @@ -0,0 +1 @@ +agy --conversation=FFix \ No newline at end of file diff --git a/codex-session.md b/codex-sessions.md similarity index 100% rename from codex-session.md rename to codex-sessions.md diff --git a/www/app/(app)/browse/page.tsx b/www/app/(app)/browse/page.tsx new file mode 100644 index 0000000..a8129d0 --- /dev/null +++ b/www/app/(app)/browse/page.tsx @@ -0,0 +1,403 @@ +'use client'; + +import * as React from 'react'; +import { Search, List as ListIcon, Map as MapIcon, Globe as GlobeIcon, ChevronLeft, ChevronRight, Apple, HeartHandshake } from 'lucide-react'; +import { mockStore, Listing, RequestItem } from '@/lib/mockStore'; +import { CATEGORIES, CategorySlug } from '@/lib/categories'; +import CategoryChip from '@/components/common/CategoryChip'; +import ListingCard from '@/components/cards/ListingCard'; +import RequestCard from '@/components/cards/RequestCard'; +import Globe from '@/components/Globe'; + +export default function BrowseMapPage() { + const [activeTab, setActiveTab] = React.useState<'listings' | 'requests'>('listings'); + const [searchQuery, setSearchQuery] = React.useState(''); + const [selectedCategory, setSelectedCategory] = React.useState(null); + const [listings, setListings] = React.useState(() => mockStore.getListings()); + const [requests, setRequests] = React.useState(() => mockStore.getRequests()); + const [selectedItem, setSelectedUserItem] = React.useState(null); + const [isLargeScreen, setIsLargeScreen] = React.useState(false); + const [showMapOnMobile, setShowMapOnMobile] = React.useState(false); + const [mapProjection, setMapProjection] = React.useState<'globe' | 'mercator'>('globe'); + + const categoriesRef = React.useRef(null); + const [showLeftArrow, setShowLeftArrow] = React.useState(false); + const [showRightArrow, setShowRightArrow] = React.useState(false); + + const checkScroll = React.useCallback(() => { + const el = categoriesRef.current; + if (!el) return; + const { scrollLeft, scrollWidth, clientWidth } = el; + setShowLeftArrow(scrollLeft > 2); + setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 2); + }, []); + + const setCategoriesRef = React.useCallback((node: HTMLDivElement | null) => { + categoriesRef.current = node; + if (node) { + setTimeout(() => { + const { scrollLeft, scrollWidth, clientWidth } = node; + setShowLeftArrow(scrollLeft > 2); + setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 2); + }, 100); + } + }, []); + + const scroll = React.useCallback((direction: 'left' | 'right') => { + const el = categoriesRef.current; + if (!el) return; + const scrollAmount = 200; + const target = el.scrollLeft + (direction === 'left' ? -scrollAmount : scrollAmount); + el.scrollTo({ + left: target, + behavior: 'smooth', + }); + }, []); + + React.useEffect(() => { + const handleResize = () => { + setIsLargeScreen(window.innerWidth >= 768); // md breakpoint is 768px + checkScroll(); + }; + handleResize(); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [checkScroll]); + + // Load store data + const refreshData = React.useCallback(() => { + setListings(mockStore.getListings()); + setRequests(mockStore.getRequests()); + }, []); + + // Filter lists based on inputs + const filteredListings = listings.filter((l) => { + const matchesSearch = l.title.toLowerCase().includes(searchQuery.toLowerCase()) || + l.description.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesCategory = selectedCategory ? l.category === selectedCategory : true; + return matchesSearch && matchesCategory; + }); + + const filteredRequests = requests.filter((r) => { + const matchesSearch = r.title.toLowerCase().includes(searchQuery.toLowerCase()) || + r.description.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesCategory = selectedCategory ? r.category === selectedCategory : true; + return matchesSearch && matchesCategory; + }); + + // Select item to fly to its position on the Globe + const flyToItem = (id: string, isListing: boolean) => { + const itemsList = isListing ? listings : requests; + const item = itemsList.find(i => i.id === id); + if (item) { + setSelectedUserItem(item); + setShowMapOnMobile(true); + } + }; + + const handleClaim = (id: string) => { + const success = mockStore.claimListing(id); + if (success) { + const event = new CustomEvent('post-created', { + detail: 'Listing claimed!' + }); + window.dispatchEvent(event); + refreshData(); + setSelectedUserItem(null); + } + }; + + const handleFulfill = (id: string) => { + const success = mockStore.fulfillRequest(id); + if (success) { + const event = new CustomEvent('post-created', { + detail: 'Offering to fulfill request!' + }); + window.dispatchEvent(event); + refreshData(); + setSelectedUserItem(null); + } + }; + + const renderHeaderFilters = () => { + const activeView = showMapOnMobile + ? (mapProjection === 'globe' ? 'globe' : 'flat') + : 'list'; + + return ( +
+ + {/* Search Input */} +
+
+ + + + setSearchQuery(e.target.value)} + className="w-full h-10 pl-10 pr-4 rounded-lg border border-white/10 bg-[#141414] text-sm text-[#E8EAD8] placeholder-[#5A5C50] focus:border-[#A8D97F] focus:outline-none" + /> +
+ + {/* Beautiful Segmented Selection Control with High-Fidelity Vector Icons */} +
+ + {/* List View Option (Mobile Only) */} + {!isLargeScreen && ( + + )} + + {/* Flat Map Option */} + + + {/* 3D Globe Option */} + +
+
+ + {/* Selector tabs */} +
+ + +
+ + {/* Horizontal Categories with Scroll Chevrons and Fade Masks */} +
+ {/* Left Fading Edge & Chevron */} +
+ +
+ + {/* Scrolling Categories List */} +
+ + {CATEGORIES.map((cat) => ( + setSelectedCategory(cat.slug)} + /> + ))} +
+ + {/* Right Fading Edge & Chevron */} +
+ +
+
+
+ ); + }; + + return ( +
+ {/* Mobile-only header (static, high z-index overlay, does not translate/slide) */} + {!isLargeScreen && ( +
+ {renderHeaderFilters()} +
+ )} + + {/* Main Grid: Split map/list on desktop, stacked sliding panel on mobile */} +
+ + {/* SIDEBAR: Search & Filters list */} +
+ + {/* Desktop-only header inside sidebar */} + {isLargeScreen && renderHeaderFilters()} + + {/* List Content */} +
+ {activeTab === 'listings' ? ( + filteredListings.length > 0 ? ( + filteredListings.map((listing, index) => ( +
flyToItem(listing.id, true)} + className="cursor-pointer animate-fade-in-up" + style={{ animationDelay: `${index * 40}ms`, animationFillMode: 'both' }} + > + +
+ )) + ) : ( +
+ No matching listings on the globe. +
+ ) + ) : ( + filteredRequests.length > 0 ? ( + filteredRequests.map((request, index) => ( +
flyToItem(request.id, false)} + className="cursor-pointer animate-fade-in-up" + style={{ animationDelay: `${index * 40}ms`, animationFillMode: 'both' }} + > + +
+ )) + ) : ( +
+ No matching appeals on the globe. +
+ ) + )} +
+
+ + {/* MAP PANEL: Interactive 3D Globe with dynamic flight and modal control */} +
+ {(isLargeScreen || showMapOnMobile) && ( + { + if (pin) { + const item = listings.find(l => l.id === pin.id) || requests.find(r => r.id === pin.id); + if (item) { + setSelectedUserItem(item); + } + } else { + setSelectedUserItem(null); + } + }} + /> + )} + {showMapOnMobile && ( + + )} +
+ +
+
+ ); +} diff --git a/www/app/(app)/home/page.tsx b/www/app/(app)/home/page.tsx new file mode 100644 index 0000000..89c3c35 --- /dev/null +++ b/www/app/(app)/home/page.tsx @@ -0,0 +1,404 @@ +"use client"; + +import * as React from "react"; +import { Search, Sparkles, Heart, ChevronLeft, ChevronRight } from "lucide-react"; +import { mockStore, Listing, RequestItem } from "@/lib/mockStore"; +import { CATEGORIES, CategorySlug } from "@/lib/categories"; +import CategoryChip from "@/components/common/CategoryChip"; +import ListingCard from "@/components/cards/ListingCard"; +import RequestCard from "@/components/cards/RequestCard"; + +export default function HomeFeed() { + const [activeTab, setActiveTab] = React.useState<"listings" | "requests">( + "listings", + ); + const [searchQuery, setSearchQuery] = React.useState(""); + const [selectedCategory, setSelectedCategory] = + React.useState(null); + const [listings, setListings] = React.useState(() => + mockStore.getListings(), + ); + const [requests, setRequests] = React.useState(() => + mockStore.getRequests(), + ); + const [stats, setStats] = React.useState(() => mockStore.getUserStats()); + + const categoriesRef = React.useRef(null); + const [showLeftArrow, setShowLeftArrow] = React.useState(false); + const [showRightArrow, setShowRightArrow] = React.useState(false); + + const checkScroll = React.useCallback(() => { + const el = categoriesRef.current; + if (!el) return; + const { scrollLeft, scrollWidth, clientWidth } = el; + setShowLeftArrow(scrollLeft > 2); + setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 2); + }, []); + + const setCategoriesRef = React.useCallback((node: HTMLDivElement | null) => { + categoriesRef.current = node; + if (node) { + setTimeout(() => { + const { scrollLeft, scrollWidth, clientWidth } = node; + setShowLeftArrow(scrollLeft > 2); + setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 2); + }, 100); + } + }, []); + + const scroll = React.useCallback((direction: "left" | "right") => { + const el = categoriesRef.current; + if (!el) return; + const scrollAmount = 200; + const target = + el.scrollLeft + (direction === "left" ? -scrollAmount : scrollAmount); + el.scrollTo({ + left: target, + behavior: "smooth", + }); + }, []); + + React.useEffect(() => { + const handleResize = () => { + checkScroll(); + }; + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, [checkScroll]); + + // Load initial store data + const refreshData = React.useCallback(() => { + setListings(mockStore.getListings()); + setRequests(mockStore.getRequests()); + setStats(mockStore.getUserStats()); + }, []); + + React.useEffect(() => { + // Add event listener to refresh data if a post is created elsewhere + const handleRefresh = () => { + refreshData(); + }; + window.addEventListener("post-created", handleRefresh); + return () => window.removeEventListener("post-created", handleRefresh); + }, [refreshData]); + + // Handle claiming a listing + const handleClaim = (id: string) => { + const success = mockStore.claimListing(id); + if (success) { + // Trigger toast through the CustomEvent that layout.tsx listens to + const event = new CustomEvent("post-created", { + detail: "Listing claimed! Points added to your profile.", + }); + window.dispatchEvent(event); + refreshData(); + } + }; + + // Handle fulfilling a request + const handleFulfill = (id: string) => { + const success = mockStore.fulfillRequest(id); + if (success) { + const event = new CustomEvent("post-created", { + detail: "Offering to fulfill request! Requester has been notified.", + }); + window.dispatchEvent(event); + refreshData(); + } + }; + + // Filter listings based on search and category selection + const filteredListings = listings.filter((l) => { + const matchesSearch = + l.title.toLowerCase().includes(searchQuery.toLowerCase()) || + l.description.toLowerCase().includes(searchQuery.toLowerCase()) || + l.city.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesCategory = selectedCategory + ? l.category === selectedCategory + : true; + return matchesSearch && matchesCategory; + }); + + // Filter requests based on search and category selection + const filteredRequests = requests.filter((r) => { + const matchesSearch = + r.title.toLowerCase().includes(searchQuery.toLowerCase()) || + r.description.toLowerCase().includes(searchQuery.toLowerCase()) || + r.city.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesCategory = selectedCategory + ? r.category === selectedCategory + : true; + return matchesSearch && matchesCategory; + }); + + // Matches carousel data (e.g. low-distance items or featured) + const matches = React.useMemo(() => { + if (activeTab === "listings") { + return listings.filter((l) => l.status === "open").slice(0, 3); + } else { + return requests.filter((r) => r.status === "open").slice(0, 3); + } + }, [listings, requests, activeTab]); + + return ( +
+
+ {/* Search & Filter Header */} +
+
+ + + + setSearchQuery(e.target.value)} + className="w-full h-12 pl-10 pr-4 rounded-xl border border-white/10 bg-[#141414] text-[#E8EAD8] placeholder-[#5A5C50] text-sm focus:border-[#A8D97F] focus:outline-none transition-all" + /> +
+ + {/* Quick Stats Bar */} +
+
+ + + Points: + + + {stats.loopPoints} XP + +
+
+
+ + + Diverted: + + + {stats.kgDiverted} kg + +
+
+
+ + {/* Categories Horizontal Scroll with Chevrons & Gradient Fade Masks */} +
+

+ Browse by Category +

+
+ {/* Left Fading Edge & Chevron */} +
+ +
+ + {/* Scrolling Categories List */} +
+ + {CATEGORIES.map((cat) => ( + setSelectedCategory(cat.slug)} + /> + ))} +
+ + {/* Right Fading Edge & Chevron */} +
+ +
+
+
+ + {/* Matched for You (Carousel) */} + {matches.length > 0 && !searchQuery && !selectedCategory && ( +
+
+
+ +

+ Highly Compatible Matches +

+
+ + Based on distance + +
+ +
+ {activeTab === "listings" + ? (matches as Listing[]).map((listing, index) => ( +
+ +
+ )) + : (matches as RequestItem[]).map((request, index) => ( +
+ +
+ ))} +
+
+ )} + + {/* Listings vs Requests Selector */} +
+
+ {/* Tab switch */} +
+ + +
+ + + Showing{" "} + {activeTab === "listings" + ? filteredListings.length + : filteredRequests.length}{" "} + items + +
+ + {/* Grid Layout of Items */} + {activeTab === "listings" ? ( + filteredListings.length > 0 ? ( +
+ {filteredListings.map((listing, index) => ( +
+ +
+ ))} +
+ ) : ( +
+ 🍂 +

+ No available waste found +

+

+ Try adjusting your search queries or selecting a different + material category. +

+
+ ) + ) : filteredRequests.length > 0 ? ( +
+ {filteredRequests.map((request, index) => ( +
+ +
+ ))} +
+ ) : ( +
+ 🙏 +

+ No active appeals found +

+

+ Nobody is asking for materials matching your criteria right now. + Check back later! +

+
+ )} +
+
+
+ ); +} diff --git a/www/app/(app)/impact/page.tsx b/www/app/(app)/impact/page.tsx new file mode 100644 index 0000000..b2d017d --- /dev/null +++ b/www/app/(app)/impact/page.tsx @@ -0,0 +1,259 @@ +'use client'; + +import * as React from 'react'; +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; +import { Leaf, BarChart2, TrendingUp, Sparkles, Droplet, CloudLightning, Utensils, Building2, Recycle, Globe, type LucideIcon } from 'lucide-react'; +import { mockStore } from '@/lib/mockStore'; + +// Mock charts dataset representing daily carbon offset +const WEEKLY_DATA = [ + { day: 'Mon', kg: 4.2, co2: 2.1, water: 210 }, + { day: 'Tue', kg: 5.8, co2: 2.9, water: 290 }, + { day: 'Wed', kg: 8.5, co2: 4.2, water: 425 }, + { day: 'Thu', kg: 10.1, co2: 5.0, water: 505 }, + { day: 'Fri', kg: 12.4, co2: 6.2, water: 620 }, + { day: 'Sat', kg: 15.0, co2: 7.5, water: 750 }, + { day: 'Sun', kg: 17.4, co2: 8.7, water: 870 }, +]; + +interface SDGCard { + number: number; + title: string; + emoji: string; + icon: LucideIcon; + color: string; + description: string; +} + +const SDG_GOALS: SDGCard[] = [ + { + number: 2, + title: "Zero Hunger", + emoji: "🍲", + icon: Utensils, + color: "#E5A823", + description: "Routing surplus bakery items and fresh crops to community larders, preventing nutrition loss." + }, + { + number: 11, + title: "Sustainable Cities", + emoji: "🏙️", + icon: Building2, + color: "#F26E22", + description: "Forming localized organic recycling loops to reduce solid municipal waste entering city landfills." + }, + { + number: 12, + title: "Responsible Consumption", + emoji: "🔄", + icon: Recycle, + color: "#C98F1D", + description: "Fostering standard circular systems that prioritize material re-utilization over waste generation." + }, + { + number: 13, + title: "Climate Action", + emoji: "🍀", + icon: Globe, + color: "#3F7E44", + description: "Halting methane emissions by keeping organic matter out of anaerobic, compressed landfill zones." + } +]; + +export default function ImpactAnalyticsPage() { + const [stats] = React.useState(() => mockStore.getUserStats()); + const [hoveredCard, setHoveredCard] = React.useState(null); + + return ( +
+
+ + {/* Analytics Summary Banner */} +
+
+ +
+
+ + ESG Validation Verified +
+

Your Cumulative Offset

+

+ Your circular resource contributions save equivalent atmospheric carbon release and virtual water overheads. +

+
+ +
+ Loop Level Score + {stats.loopPoints} XP +
+
+ + {/* 3 Columns Metrics row */} +
+ + {/* Scraps Card */} +
+
+ +
+
+ Scraps Diverted + {stats.kgDiverted} kg +
+
+ + {/* Water Saved Card */} +
+
+ +
+
+ Water Conserved + {stats.waterSaved} L +
+
+ + {/* CO2 Mitigated Card */} +
+
+ +
+
+ CO2 Prevented + {stats.co2Saved} kg +
+
+ +
+ + {/* Recharts Graphical Line Area Chart */} +
+
+
+ +

Resource Routing Progress

+
+ Past 7 Days +
+ + {/* Recharts wrapper */} +
+ + + + + + + + + + + + + + + +
+
+ + {/* UN SDG Goals Section */} +
+
+
+ +

+ UN Sustainable Development Goals (SDG) Mapping +

+
+

+ Connecting localized actions to global sustainability standards +

+
+ +
+ {SDG_GOALS.map((sdg) => { + const isHovered = hoveredCard === sdg.number; + return ( +
setHoveredCard(sdg.number)} + onMouseLeave={() => setHoveredCard(null)} + style={{ + borderColor: isHovered ? sdg.color : `${sdg.color}25`, + backgroundColor: isHovered ? '#1B1B1B' : '#141414', + boxShadow: isHovered + ? `0 10px 25px -5px ${sdg.color}1A, 0 8px 10px -6px ${sdg.color}12` + : '0 4px 20px -2px rgba(0, 0, 0, 0.4)', + transform: isHovered ? 'translateY(-4px)' : 'none', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' + }} + className="group rounded-2xl border p-5 flex flex-col justify-between min-h-[11rem] cursor-default" + > +
+
+ +
+ + Goal {sdg.number} + +
+ +
+

+ {sdg.title} +

+

+ {sdg.description} +

+
+
+ ); + })} +
+
+ +
+
+ ); +} diff --git a/www/app/(app)/layout.tsx b/www/app/(app)/layout.tsx new file mode 100644 index 0000000..64dcfca --- /dev/null +++ b/www/app/(app)/layout.tsx @@ -0,0 +1,234 @@ +'use client'; + +import * as React from 'react'; +import { useRouter } from 'next/navigation'; +import { X, Apple, MessageSquare, Check, LogOut } from 'lucide-react'; +import BottomNav from '@/components/navigation/BottomNav'; +import NavigationRail from '@/components/navigation/NavigationRail'; +import { logout } from '@/app/login/actions'; + +export default function AppLayout({ children }: { children: React.ReactNode }) { + const router = useRouter(); + const [mounted, setMounted] = React.useState(false); + const [isPostSheetOpen, setIsPostSheetOpen] = React.useState(false); + const [isSignOutConfirmOpen, setIsSignOutConfirmOpen] = React.useState(false); + const [toast, setToast] = React.useState<{ message: string; show: boolean }>({ message: '', show: false }); + + const triggerPostSheet = () => { + setIsPostSheetOpen(prev => !prev); + }; + + const navigateTo = (path: string) => { + setIsPostSheetOpen(false); + router.push(path); + }; + + const showToastNotification = (msg: string) => { + setToast({ message: msg, show: true }); + setTimeout(() => { + setToast(prev => ({ ...prev, show: false })); + }, 4000); + }; + + const [isSidebarCollapsed, setIsSidebarCollapsed] = React.useState(false); + + React.useEffect(() => { + setTimeout(() => { + setMounted(true); + }, 0); + try { + const stored = localStorage.getItem('fl_sidebar_collapsed'); + if (stored !== null) { + setTimeout(() => { + setIsSidebarCollapsed(stored === 'true'); + }, 0); + } + } catch (e) { + console.warn("Storage access failed (private browsing):", e); + } + }, []); + + const handleToggleCollapse = () => { + setIsSidebarCollapsed(prev => { + const next = !prev; + try { + localStorage.setItem('fl_sidebar_collapsed', String(next)); + window.dispatchEvent(new CustomEvent('sidebar-collapsed-change', { detail: next })); + } catch (e) { + console.warn("Writing to storage failed:", e); + } + return next; + }); + }; + + // Listen to custom posting events for feedback + React.useEffect(() => { + const handlePostCreated = (e: Event) => { + const customEvent = e as CustomEvent; + showToastNotification(customEvent.detail || 'Post created successfully!'); + }; + window.addEventListener('post-created', handlePostCreated); + return () => window.removeEventListener('post-created', handlePostCreated); + }, []); + + // Listen to custom signout events (for mobile settings triggers) + React.useEffect(() => { + const handleTriggerSignOut = () => { + setIsSignOutConfirmOpen(true); + }; + window.addEventListener('trigger-signout', handleTriggerSignOut); + return () => window.removeEventListener('trigger-signout', handleTriggerSignOut); + }, []); + + return ( +
+ {/* Navigation Rails & Drawers */} + setIsSignOutConfirmOpen(true)} + isCollapsed={isSidebarCollapsed} + onToggleCollapse={handleToggleCollapse} + /> + + {/* Main Content Area */} + {/* padding bottom is 80px on mobile to prevent navbar covering content, padding left adjusts dynamically with transition */} +
+ {mounted ? children : ( +
+
+
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + LoopHarvest +
+
+

LoopHarvest

+

Syncing Organic Cycles...

+
+
+
+ )} +
+ + {/* Bottom Nav (Mobile only) */} + + + {/* Post Bottom Sheet (M3 Modal overlay) */} + {isPostSheetOpen && ( +
setIsPostSheetOpen(false)} + > +
e.stopPropagation()} + > + {/* Drag Handle */} +
+ +
+

+ What do you want to post? +

+ +
+ +
+ + + +
+
+
+ )} + + {/* Sign Out Confirmation Modal (Premium Glassmorphic Dialog) */} + {isSignOutConfirmOpen && ( +
setIsSignOutConfirmOpen(false)} + > +
e.stopPropagation()} + > + {/* Warning Icon Badge */} +
+ +
+ +
+

+ Sign Out? +

+

+ Are you sure you want to sign out? You will need to log back in to manage your posts and active claims. +

+
+ +
+ + +
+
+
+ )} + + {/* Snackbar / Toast Notifications */} + {toast.show && ( +
+
+
+ +
+

{toast.message}

+
+
+ )} +
+ ); +} diff --git a/www/app/(app)/notifications/page.tsx b/www/app/(app)/notifications/page.tsx new file mode 100644 index 0000000..39272ff --- /dev/null +++ b/www/app/(app)/notifications/page.tsx @@ -0,0 +1,144 @@ +'use client'; + +import * as React from 'react'; +import { Bell, Trash2, CheckCircle2, Inbox, Calendar, MessageSquare, ArrowRight } from 'lucide-react'; +import { mockStore, NotificationItem } from '@/lib/mockStore'; +import { catMap } from '@/lib/categories'; + +export default function NotificationsPage() { + const [notifications, setNotifications] = React.useState(() => mockStore.getNotifications()); + + const refreshData = React.useCallback(() => { + setNotifications(mockStore.getNotifications()); + }, []); + + const handleMarkAllRead = () => { + mockStore.markAllNotificationsAsRead(); + refreshData(); + // Dispatch refresh event + window.dispatchEvent(new CustomEvent('post-created', { detail: 'All notifications marked as read!' })); + }; + + const handleDelete = (id: string) => { + mockStore.deleteNotification(id); + refreshData(); + }; + + const getIconForType = (type: NotificationItem['type']) => { + switch (type) { + case 'request_matched': + return ; + case 'listing_claimed': + return ; + case 'pickup_confirmed': + return ; + case 'match_found': + return ; + case 'review_received': + return ; + default: + return ; + } + }; + + return ( +
+
+
+

+ Notification Center +

+ {notifications.some(n => n.status === 'unread') ? ( + + ) : null} +
+ +
+

+ Recent Alerts +

+ + {notifications.filter(n => n.status === 'unread').length} unread + +
+ + {notifications.length > 0 ? ( +
+ {notifications.map((notif, idx) => { + const catColor = notif.category ? (catMap[notif.category]?.color || '#A8D97F') : '#A8AA98'; + const isUnread = notif.status === 'unread'; + + return ( +
+ {/* Category-Colored/Type Indicator Icon Container */} +
+ {getIconForType(notif.type)} +
+ + {/* Copy details */} +
+
+

+ {notif.title} +

+ + {notif.time} + +
+

+ {notif.body} +

+
+ + {/* Action delete floating button */} + + + {/* Unread dot indicator */} + {isUnread && ( + + )} +
+ ); + })} +
+ ) : ( +
+ +

Inbox is completely clean!

+

+ When neighbours post compatible food waste matches, list resources, or coordinate claims near you, alerts will populate here. +

+
+ )} + +
+
+ ); +} diff --git a/www/app/(app)/post/listing/page.tsx b/www/app/(app)/post/listing/page.tsx new file mode 100644 index 0000000..07ebb7d --- /dev/null +++ b/www/app/(app)/post/listing/page.tsx @@ -0,0 +1,365 @@ +'use client'; + +import * as React from 'react'; +import { useRouter } from 'next/navigation'; +import { ChevronRight, ChevronLeft, Send, Sparkles, Scale, Info } from 'lucide-react'; +import { mockStore } from '@/lib/mockStore'; +import { CATEGORIES, CategorySlug, catMap } from '@/lib/categories'; + +const PHOTO_PRESETS: Record = { + 'vegetable-scraps': [ + 'https://images.unsplash.com/photo-1598170845058-32b9d6a5da37?q=80&w=600&auto=format&fit=crop', + 'https://images.unsplash.com/photo-1557844352-761f2565b576?q=80&w=600&auto=format&fit=crop' + ], + 'coffee-grounds': [ + 'https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?q=80&w=600&auto=format&fit=crop', + 'https://images.unsplash.com/photo-1509042239860-f550ce710b93?q=80&w=600&auto=format&fit=crop' + ], + 'fruit-waste': [ + 'https://images.unsplash.com/photo-1619546813926-a78fa6372cd2?q=80&w=600&auto=format&fit=crop', + 'https://images.unsplash.com/photo-1550258987-190a2d41a8ba?q=80&w=600&auto=format&fit=crop' + ], + 'spent-grain': [ + 'https://images.unsplash.com/photo-1574316071802-0d68497b05f5?q=80&w=600&auto=format&fit=crop', + 'https://images.unsplash.com/photo-1532634922-8fe0b757fb13?q=80&w=600&auto=format&fit=crop' + ], + 'bread-stale': [ + 'https://images.unsplash.com/photo-1549931319-a545dcf3bc73?q=80&w=600&auto=format&fit=crop', + 'https://images.unsplash.com/photo-1509440159596-0249088772ff?q=80&w=600&auto=format&fit=crop' + ], + 'fish-scraps': [ + 'https://images.unsplash.com/photo-1534604973900-c43ab4c2e0ab?q=80&w=600&auto=format&fit=crop' + ], + 'fruit-peels': [ + 'https://images.unsplash.com/photo-1611080626919-7cf5a9dbab5b?q=80&w=600&auto=format&fit=crop', + 'https://images.unsplash.com/photo-1590005354167-6da97870c913?q=80&w=600&auto=format&fit=crop' + ], + 'cooking-oil': [ + 'https://images.unsplash.com/photo-1474979266404-7eaacbcd87c5?q=80&w=600&auto=format&fit=crop' + ], + 'other': [ + 'https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?q=80&w=600&auto=format&fit=crop' + ] +}; + +export default function PostListingPage() { + const router = useRouter(); + const [step, setStep] = React.useState(1); + + // Form Fields State + const [title, setTitle] = React.useState(''); + const [category, setCategory] = React.useState('vegetable-scraps'); + const [quantity, setQuantity] = React.useState(1.0); + const [unit, setUnit] = React.useState('kg'); + const [description, setDescription] = React.useState(''); + const [selectedPhoto, setSelectedPhoto] = React.useState(() => { + const presets = PHOTO_PRESETS['vegetable-scraps'] || PHOTO_PRESETS['other']; + return presets[0] || ''; + }); + + const activeCatPresets = PHOTO_PRESETS[category] || PHOTO_PRESETS['other']; + + const handleNext = () => { + if (step === 1 && !title.trim()) { + alert('Please fill in a descriptive title.'); + return; + } + if (step === 2 && quantity <= 0) { + alert('Quantity must be greater than zero.'); + return; + } + if (step === 3 && !description.trim()) { + alert('Please fill in a short details description.'); + return; + } + setStep(prev => prev + 1); + }; + + const handlePrev = () => { + setStep(prev => prev - 1); + }; + + const handlePublish = () => { + mockStore.addListing( + title, + category, + quantity, + unit, + description, + selectedPhoto + ); + + // Dispatch custom post created event to trigger Layout toasts + const event = new CustomEvent('post-created', { + detail: `Donation posted! "${title}" is now listed near you.` + }); + window.dispatchEvent(event); + + router.push('/home'); + }; + + return ( +
+
+ + Post Food Waste + +
+ + {/* Progress Stepper Bar */} +
+
+ {[1, 2, 3, 4].map((num) => ( +
+
num + ? 'bg-[#2A4A10] text-[#A8D97F]' + : 'bg-[#1B1B1B] text-[#5A5C50] border border-white/6' + }`} + > + {num} +
+ {num < 4 && ( +
num ? 'bg-[#2A4A10]' : 'bg-[#1B1B1B]' + }`} + /> + )} +
+ ))} +
+
+ + {/* Inner Form content container */} +
+ + {/* STEP 1: TITLE & CATEGORY */} + {step === 1 && ( +
+
+

Describe your organic material

+

Choose a precise title so composters can identify quality.

+
+ +
+
+ + setTitle(e.target.value)} + className="w-full h-12 px-4 rounded-xl border border-white/10 bg-[#141414] text-sm focus:border-[#A8D97F] focus:outline-none transition-all" + /> +
+ +
+ +
+ {CATEGORIES.map((cat) => ( + + ))} +
+
+
+
+ )} + + {/* STEP 2: QUANTITY & WEIGHT */} + {step === 2 && ( +
+
+

How much are you donating?

+

Accurate metrics enable carbon reduction claims validation.

+
+ +
+
+ + Handoff Weight +
+ +
+
+ + setQuantity(Math.max(0.1, parseFloat(e.target.value) || 0.1))} + className="w-full h-12 px-4 rounded-xl border border-white/10 bg-[#0A0A0A] text-center font-mono font-bold text-base text-[#E8EAD8] focus:border-[#A8D97F] focus:outline-none" + /> +
+ +
+ + +
+
+
+
+ )} + + {/* STEP 3: PHOTO & DETAILS */} + {step === 3 && ( +
+
+

Add Details & Visual Verification

+

Select preset stock images recommended for your category slug.

+
+ +
+
+ +