Elsewhere is a location-aware web app for finding places to work outside the house: cafes, libraries, tea shops, and other third places with useful workability signals.
It is currently focused on Northern Virginia, with Annandale as the fallback center when a user has not shared location or is outside the active service area.
- Nearby feed with search, place-type filters, location status messaging, and personalized match scores
- Full-screen map and desktop split-pane map/list layout
- Custom Mapbox markers for scored/unscored places, selected places, and user location
- Place detail sheets/panels with photos, hours, rating aggregates, notes, save/rate CTAs, and share actions
- Rating flow for noise, vibe, tables, outlets, overall workability, tips, and user-uploaded photos
- Saved places
- User onboarding/tutorial and gated auth return flows
- Admin-only vibe photo selection tooling
/
├── elsewhere/ # Next.js application
│ ├── app/ # App Router pages, layouts, route handlers
│ ├── components/ # UI, feed, map, places, rating components
│ ├── hooks/ # Client hooks
│ ├── lib/ # Supabase clients, domain logic, helpers
│ ├── scripts/ # Maintainer data/backfill scripts
│ ├── store/ # Zustand client state
│ └── types/ # Shared TypeScript types
├── supabase/ # Supabase migrations and schema snapshots
├── backend-plan.md # Backend/product implementation notes
└── frontend-plan.md # Frontend/design implementation notes
- Framework: Next.js App Router (
next@16) - Runtime/UI: React 19, TypeScript
- Styling: Tailwind CSS v3 with project tokens in
elsewhere/tailwind.config.js - Fonts: Lora for headings/display, DM Sans for body/UI via
next/font - Client state: Zustand for selected/hovered place state
- Server/client async state: TanStack Query v5
- Database/Auth/Storage: Supabase
- Postgres for places, ratings, saves, preferences, stats, and submissions
- Supabase Auth for user sessions and Google OAuth
- Supabase Storage for user-uploaded rating photos
- Maps: Mapbox GL JS for the interactive map surface
- Place enrichment: Google Places API through server-side route handlers
- Analytics: PostHog, enabled only when public env vars are configured
- Hosting: Vercel, configured in
elsewhere/vercel.json
- The app is primarily client-rendered inside the App Router shell. Server route handlers provide mutations and protected data access.
- Supabase browser clients use anon credentials and RLS. Service-role access is restricted to server-only modules and route handlers.
GET /api/feedcalls a Supabase RPC (get_feed_places) through the service role, then shapes rows intoFeedItemobjects inelsewhere/lib/feedItemsFromPlaces.ts.- Place photos from Google are proxied through server routes instead of storing permanent Google photo URLs.
- User-uploaded rating photos are normalized client-side, uploaded through
POST /api/places/[id]/upload-photo, then attached to the rating withPATCH /api/places/[id]/rate. - The map/list/detail selection state is shared through
elsewhere/store/usePlaceStore.ts. - Admin vibe-photo updates require an authenticated admin user (
app_metadata.role === "admin",app_metadata.rolescontaining"admin", or an email listed inELSEWHERE_ADMIN_EMAILS).
cd elsewhere
npm install
cp .env.example .env.local
npm run devOpen http://localhost:3000.
From elsewhere/:
npm run dev # Start Next dev server
npm run build # Production build
npm run start # Serve production build
npm run lint # ESLint
npm run seed-places # Maintainer-only Google Places/Supabase seed helperUse elsewhere/.env.example as the source of truth for local setup. Do not commit real values.
Required for normal app behavior:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYNEXT_PUBLIC_MAPBOX_ACCESS_TOKENNEXT_PUBLIC_GOOGLE_MAPS_API_KEYGOOGLE_PLACES_API_KEYNEXTAUTH_SECRETorAUTH_SECRET
Optional:
NEXT_PUBLIC_MAPBOX_STYLENEXT_PUBLIC_POSTHOG_KEYNEXT_PUBLIC_POSTHOG_HOSTELSEWHERE_ADMIN_EMAILSDEV_AUTH_EMAILandDEV_AUTH_PASSWORDfor local-only development login- database helper variables for one-off scripts
Supabase migrations and schema snapshots live under supabase/. The app expects:
placesandplace_statsfor place data and cached rating aggregatesratingsfor user workability ratings and photo pathssavedfor saved placesprofilesanduser_preferencesfor user identity/preferencesplace_submissionsfor user-submitted missing places
The feed path depends on a get_feed_places RPC and rating aggregate triggers in the Supabase schema. Review the migrations before bootstrapping a new database.
Scripts under elsewhere/scripts/ are operational helpers for seeding, backfilling, and one-off data cleanup. They can use service-role credentials, Google Places credentials, and direct database URLs. Treat them as maintainer-only tools and run them only with intentional environment configuration.
This repository intentionally ignores local environment files, Supabase CLI temp state, Supabase branch state, and one-off script outputs. Real secrets should live in .env.local for local development and in Vercel/Supabase project settings for deployed environments.
If the repository has ever contained credentials or local Supabase project state in Git history, rotate affected credentials and rewrite history before making the repository public.