From 857f5a239636a7be4f4bc1d74e35f05564179afd Mon Sep 17 00:00:00 2001 From: BizzleBot Date: Thu, 19 Feb 2026 18:11:37 +0000 Subject: [PATCH] Add sightings page + report form, fix stream API mapping, add nav link --- app/components/Navbar.tsx | 1 + app/sightings/ReportForm.tsx | 156 +++++++++++++++++++++++++++++++++++ app/sightings/page.tsx | 98 ++++++++++++++++++++++ lib/api.ts | 13 +++ 4 files changed, 268 insertions(+) create mode 100644 app/sightings/ReportForm.tsx create mode 100644 app/sightings/page.tsx diff --git a/app/components/Navbar.tsx b/app/components/Navbar.tsx index 10723c6..705b47c 100644 --- a/app/components/Navbar.tsx +++ b/app/components/Navbar.tsx @@ -7,6 +7,7 @@ import { Menu, X, Eye, Tv2, TreePine, Heart, Calendar, Info } from 'lucide-react const links = [ { href: '/streams', label: 'Live Cams', icon: Tv2 }, { href: '/wildlife', label: 'Wildlife', icon: TreePine }, + { href: '/sightings', label: 'Sightings', icon: Eye }, { href: '/events', label: 'Events', icon: Calendar }, { href: '/donate', label: 'Donate', icon: Heart }, { href: '/about', label: 'About', icon: Info }, diff --git a/app/sightings/ReportForm.tsx b/app/sightings/ReportForm.tsx new file mode 100644 index 0000000..8ebe006 --- /dev/null +++ b/app/sightings/ReportForm.tsx @@ -0,0 +1,156 @@ +'use client'; +import React, { useState } from 'react'; +import { Send, MapPin, Loader2 } from 'lucide-react'; + +const SPECIES_OPTIONS = [ + 'Burrowing Owl', + 'Gopher Tortoise', + 'Roseate Spoonbill', + 'Florida Scrub-Jay', + 'Manatee', + 'Red-shouldered Hawk', + 'Osprey', + 'Great Blue Heron', + 'Other', +]; + +export default function ReportForm() { + const [species, setSpecies] = useState(''); + const [description, setDescription] = useState(''); + const [lat, setLat] = useState(''); + const [lng, setLng] = useState(''); + const [locating, setLocating] = useState(false); + const [submitted, setSubmitted] = useState(false); + const [error, setError] = useState(''); + + const getLocation = () => { + if (!navigator.geolocation) { setError('Geolocation not supported'); return; } + setLocating(true); + navigator.geolocation.getCurrentPosition( + (pos) => { + setLat(pos.coords.latitude.toFixed(6)); + setLng(pos.coords.longitude.toFixed(6)); + setLocating(false); + }, + () => { setError('Could not get location'); setLocating(false); } + ); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!species || !lat || !lng) { setError('Species and location are required'); return; } + setError(''); + + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'}/api/sightings`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + species, + description, + gps_lat: parseFloat(lat), + gps_lng: parseFloat(lng), + }), + } + ); + if (!res.ok) throw new Error('Failed to submit'); + setSubmitted(true); + } catch { + setError('Submission failed — you may need to log in first'); + } + }; + + if (submitted) { + return ( +
+
+

Sighting Reported!

+

Thank you! An admin will verify your report.

+ +
+ ); + } + + return ( +
+

Report a Sighting

+ + {error && ( +
+ {error} +
+ )} + + {/* Species */} +
+ + +
+ + {/* Location */} +
+ +
+ setLat(e.target.value)} + placeholder="Latitude" + className="flex-1 bg-black/40 border border-white/10 rounded-lg px-4 py-2.5 text-sm text-white placeholder:text-stone-600 focus:outline-none focus:border-teal/50" + /> + setLng(e.target.value)} + placeholder="Longitude" + className="flex-1 bg-black/40 border border-white/10 rounded-lg px-4 py-2.5 text-sm text-white placeholder:text-stone-600 focus:outline-none focus:border-teal/50" + /> + +
+
+ + {/* Description */} +
+ +