import React, { useEffect, useMemo, useState } from "react"; import { motion } from "framer-motion"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { MapPin, Mail, Copy, ExternalLink, HeartHandshake, Megaphone, Loader2 } from "lucide-react"; import * as turf from "@turf/turf"; const DISTRICT_EMAILS = { 1: { name: "Joe LaCava", email: "joelacava@sandiego.gov", ph([sandiego.gov](https://www.sandiego.gov/contact)): "https://www.sandiego.gov/citycouncil/cd1" }, 2: { name: "Jennifer Campbell", email: "jennifercampbell@sandiego.gov", phone: "619-236-6622", website: "https://www.sandiego.gov/citycouncil/cd2" }, 3: { name: "Stephen Whitburn", email: "stephenwhitburn@sandiego.gov", phone: "619-236-6633", website: "https://www.sandiego.gov/citycouncil/cd3" }, 4: { name: "Henry Foster III", email: "henryfoster@sandiego.gov", phone: "619-236-6644", website: "https://www.sandiego.gov/citycouncil/cd4" }, 5: { name: "Marni von Wilpert", email: "marnivonwilpert@sandiego.gov", phone: "619-236-6655", website: "https://www.sandiego.gov/citycouncil/cd5" }, 6: { name: "Kent Lee", email: "kentlee@sandiego.gov", phone: "619-236-6616", website: "https://www.sandiego.gov/citycouncil/cd6" }, 7: { name: "Raul Campillo", email: "raulcampillo@sandiego.gov", phone: "619-236-6677", website: "https://www.sandiego.gov/citycouncil/cd7" }, 8: { name: "Vivian Moreno", email: "vivianmoreno@sandiego.gov", phone: "619-236-6688", website: "https://www.sandiego.gov/citycouncil/cd8" }, 9: { name: "Sean Elo-Rivera", email: "seanelorivera@sandiego.gov", phone: "619-236-6699", website: "https://www.sandiego.gov/citycouncil/cd9" }, }; const DEFAULT_MESSAGE = `Dear Councilmember {{lastName}},\n\nI am a resident of your district and I am writing to urge you to protect City funding for arts and culture in San Diego. Arts organizations are not a luxury. They are a core part of our city’s identity, economy, education, tourism, and quality of life.\n\nCuts to arts funding would hurt local artists, students, families, neighborhood programs, and small businesses that benefit from a vibrant cultural sector. Public support for the arts helps keep San Diego creative, connected, and economically strong.\n\nPlease stand up for continued funding for arts organizations in the City budget.\n\nSincerely,\n{{name}}\n{{address}}`; function lastName(fullName) { const parts = fullName.split(" "); return parts[parts.length - 1].replace(/III$/, "Foster"); } function buildMessage(template, person, signerName, address) { return template .replace(/{{lastName}}/g, lastName(person.name)) .replace(/{{name}}/g, signerName || "[Your Name]") .replace(/{{address}}/g, address || "[Your Address]"); } export default function SanDiegoArtsAdvocacySite() { const [districtGeoJson, setDistrictGeoJson] = useState(null); const [loadingDistricts, setLoadingDistricts] = useState(true); const [lookupLoading, setLookupLoading] = useState(false); const [address, setAddress] = useState(""); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [district, setDistrict] = useState(null); const [error, setError] = useState(""); const [message, setMessage] = useState(DEFAULT_MESSAGE); const [copied, setCopied] = useState(false); useEffect(() => { async function loadDistricts() { try { const url = "https://webmaps.sandiego.gov/arcgis/rest/services/DoIT_Public/DoIT_Public/MapServer/5/query?where=1%3D1&outFields=DISTRICT,NAME,PHONE,WEBSITE&returnGeometry=true&f=geojson&outSR=4326"; const res = await fetch(url); if (!res.ok) throw new Error("Could not load district boundaries."); const data = await res.json(); setDistrictGeoJson(data); } catch (e) { setError("I couldn't load the district map right now. You can still choose your district manually below."); } finally { setLoadingDistricts(false); } } loadDistricts(); }, []); const matchedPerson = useMemo(() => { if (!district) return null; const info = DISTRICT_EMAILS[district]; if (!info) return null; return { district, ...info }; }, [district]); async function lookupDistrict() { setError(""); setCopied(false); if (!address.trim()) { setError("Please enter a San Diego address."); return; } setLookupLoading(true); try { const params = new URLSearchParams({ format: "jsonv2", q: `${address}, San Diego, CA`, limit: "5", }); const geoRes = await fetch(`https://nominatim.openstreetmap.org/search?${params.toString()}`, { headers: { Accept: "application/json" }, }); if (!geoRes.ok) throw new Error("Geocoding failed."); const results = await geoRes.json(); if (!results.length) throw new Error("Address not found."); const first = results.find((r) => (r.display_name || "").toLowerCase().includes("san diego")) || results[0]; const point = turf.point([Number(first.lon), Number(first.lat)]); if (!districtGeoJson?.features?.length) { throw new Error("District map unavailable."); } const match = districtGeoJson.features.find((feature) => turf.booleanPointInPolygon(point, feature)); if (!match) throw new Error("That address does not appear to be inside the City of San Diego council districts."); setDistrict(Number(match.properties.DISTRICT)); } catch (e) { setError(e.message || "I couldn't match that address to a district."); } finally { setLookupLoading(false); } } function openMailto() { if (!matchedPerson) return; const subject = encodeURIComponent("Please protect San Diego arts funding"); const body = encodeURIComponent(buildMessage(message, matchedPerson, name, address)); const cc = email ? `&cc=${encodeURIComponent(email)}` : ""; window.location.href = `mailto:${matchedPerson.email}?subject=${subject}&body=${body}${cc}`; } async function copyMessage() { if (!matchedPerson) return; await navigator.clipboard.writeText(buildMessage(message, matchedPerson, name, address)); setCopied(true); setTimeout(() => setCopied(false), 1800); } return (
Enter your address to identify your San Diego City Council district, review a ready-to-send message, and email your councilmember in support of public funding for arts organizations.
1. Find your district
Use your San Diego address or choose manually.
2. Review the message
Personalize it so it feels real and local.
3. Send your email
Open a prefilled email to your council office.
Your council office
{matchedPerson.email} · {matchedPerson.phone}
Enter your address to identify your San Diego City Council district, review a ready-to-send message, and email your councilmember in support of public funding for arts organizations.
1. Find your district
Use your San Diego address or choose manually.
2. Review the message
Personalize it so it feels real and local.
3. Send your email
Open a prefilled email to your council office.
For a fast launch, publish this as a simple one-page site on Vercel, Netlify, or Squarespace custom code.
If you want true one-click sending from the site instead of opening a user’s email app, connect a serverless function and an email service such as Resend, Postmark, or SendGrid, then add rate limits and CAPTCHA.
You can also add organizer notifications, a thank-you page, district-level conversion tracking, and optional social sharing.
FOR TICKET SERVICE E-MAIL
TICKETS@SANDIEGOSYMPHONY.ORG