A standalone Next.js app that displays crypto/tech conference side events with interactive map, list, table, and gallery views. Landing page at plan.wtf shows active conference cards with event counts, days-away badges, social links, and upcoming conferences with "Notify Me" email signups. Users can filter events by conference, date range, time range, tags, and more. Add events to your plan and build a personal itinerary with PNG export and shareable links. Social features include friend connections, check-ins, emoji reactions, and event comments. RSVP flow for Luma events with in-app registration overlay.
Live: https://plan.wtf
Repo: https://github.com/snackman/sheeets
Data Source: Google Sheet 1xWmIHyEyOmPHfkYuZkucPRlLGWbb9CF6Oqvfl8FUV6k (https://plan.wtf/data)
- Next.js 16 (App Router, Turbopack)
- TypeScript
- Tailwind CSS (CSS custom properties theme system with
data-themeattribute) - Mapbox GL JS via
react-map-gl(individual markers, zoom-aware labels) - Supabase (auth via email OTP, itinerary sync, friends, check-ins, reactions, comments, POIs, image caching, ad tracking, profile pictures via Storage, RSVPs)
- OpenAI (GPT-4o-mini for sponsor extraction in crawl script)
- @tanstack/react-virtual (list virtualization for scroll performance)
- Google Sheets API (service account auth for event submission)
- Google Analytics (GA4, custom event tracking)
- html-to-image for itinerary PNG export
- localStorage + Supabase for user state (plan items, itinerary, hidden events — dual storage with sync)
All changes on branch pineapple-22344-itinerary-overhaul (PR #123).
- New file:
src/app/api/image-proxy/route.ts— server-side image proxy to bypass CORS for html-to-image canvas rendering - ShareCardModal (
src/components/ShareCardModal.tsx):- Pre-fetches flyer images as data URLs: checks
imageCache→ batch POST/api/og→ proxy via/api/image-proxy→ FileReader → data URL map - Flyers/Text Only toggle (
ShareCardModetype) — skips image pre-fetching in text mode - Passes
flyerImagesmap andmodeprop to template
- Pre-fetches flyer images as data URLs: checks
- ShareCardTemplate (
src/components/ShareCardTemplate.tsx):- Gallery mode (default): 2-column grid of 486px square flyer tiles with event name (22px bold) and time (19px) above each image. Dark placeholder for events without flyers.
- Text mode: Classic layout with time + event name + address in a list
- End times now shown (e.g., "6:00p - 9:00p")
- Exported
ShareCardModetype
- Friend avatars: Added
useFriends()+useFriendsItineraries()hooks, computesfriendsByEventmap, passesfriendsGoingprop to EventCard (blue left border + avatar stack) - Google Calendar export: Added
GoogleCalendarButton(icon-only, 18px CalendarPlus) to header withconferenceTimezoneandexportableEventsmemos - View mode sync: /plan page reads
STORAGE_KEYS.VIEW_MODEfrom localStorage on mount via useEffect +viewModeRestoredgate to prevent flash - Header cleanup:
- Removed "My Plan (N events)" heading
- Conference dropdown moved next to back arrow (same flex group)
- Back button navigates to
/<conference-slug>instead of/ - Logo replaces calendar emoji, heading says "My Plan" not "plan.wtf"
- CalendarX icon replaced with plan.wtf logo in empty state
- Schedule conflicts removed: Removed conflict banner, per-card indicators, border styling,
detectConflictsimport - Larger link/copy icons: Bumped from
w-3.5 h-3.5tow-4 h-4in EventCard to match StarButton
| PR | Branch | Description | Preview |
|---|---|---|---|
| #126 | calzone-34833-gallery-view |
Gallery view (flyer grid) | https://sheeets-git-calzone-34833-gallery-view-pizza-dao.vercel.app |
| #125 | onion-98806-admin-submissions |
Admin submissions approval queue | https://sheeets-git-onion-98806-admin-submissions-pizza-dao.vercel.app |
| #123 | pineapple-22344-itinerary-overhaul |
Itinerary page overhaul + share card flyer gallery | https://sheeets-git-pineapple-22344-itinerary-overhaul-pizza-dao.vercel.app |
| #122 | worktree-agent-ab7637fb |
Luma RSVP flow + profile fields | https://sheeets-git-worktree-agent-ab7637fb-pizza-dao.vercel.app |
| #89 | image-ad-column |
Image ad column (stale) | — |
| #78 | luma-gmail-importer |
Gmail Luma importer (stale) | — |
- Blue header theme changes — on local
calzone-34833-luma-rsvpbranch (globals.css, Header.tsx, ViewToggle.tsx, AuthModal.tsx)
- Updated plan at
plans/flyer-gallery-view.md— overlay bar on image instead of text below, StarButton (not stars) - Not yet implemented
supabase/migrations/20260502_add_profile_fields.sql—ALTER TABLE profiles ADD COLUMN telegram_handle text; ADD COLUMN company text;- Must be applied to Supabase production before merging PR #122
- Luma embed iframe does NOT support pre-filling name/email via URL params or postMessage
- Only supported params:
coupon,utm_source - Server-side API registration possible but requires organizer Luma Plus API keys (not viable for arbitrary events)
- Current copy-fields approach is the best available UX
plans/pineapple-22344-itinerary-overhaul.md— Itinerary overhaul + share card flyer gallery (PR #123, ready to merge)plans/flyer-gallery-view.md— Gallery view (implemented, card redesign pending)
plans/admin-conference-management.mdplans/automated-testing.mdplans/calendar-export.mdplans/event-click-tracking.mdplans/featured-events.mdplans/google-calendar-export.mdplans/image-ad-column.mdplans/instant-geocoding.mdplans/itinerary-privacy.mdplans/luma-gmail-importer.mdplans/pineapple-49198-ai-sponsor-extraction.mdplans/profile-picture.mdplans/salami-77082-landing-page.mdplans/sponsor-admin-ui.mdplans/sponsor-crawling.md
plans/done/ad-placements.mdplans/done/olive-29895-checkin-buttons.mdplans/done/per-conference-ad-inventory.mdplans/done/submit-form-improvements.mdplans/done/sxsw-theme.mdplans/done/sxsw-theme-v2.mdplans/done/tomato-46571-theme-toggle.md
- Image proxy:
/api/image-proxy?url=<encoded>— server-side fetch bypasses CORS for html-to-image canvas rendering - Pre-fetch flow:
imageCache(OGImage.tsx) → batch POST/api/og→ proxy fetch → FileReader → data URL map - Two modes:
ShareCardMode='gallery'(flyer grid) or'text'(classic list). Text mode skips image fetching entirely. - Rendering: html-to-image
toPng()at 2x pixel ratio, 1080px card width. Uses explicit pixel sizes (not CSS grid/aspectRatio) for html-to-image compatibility.
isLumaUrl()/getLumaSlug()insrc/lib/luma.ts— matcheslu.ma,luma.com,www.luma.comuseRsvphook — loads confirmed RSVPs fromrsvpstable, manages overlay stateRsvpButton— only renders for Luma events (inlineisLumaUrlcheck)RsvpOverlay— self-contained portal with inlinegetLumaSlug, copy fields, Luma iframe- Embed URL:
https://lu.ma/embed/event/{slug}/simple(also works withluma.com) - DB table:
rsvps(id, user_id, event_id, luma_api_id, status, method, created_at)
- Edge caching:
/api/events(5min),/api/geocoded-addresses(1hr) viaCache-Controlheaders - Server cache:
fetchEventsCached()usesunstable_cachewith 5min revalidation - SSR hydration:
[slug]/page.tsxpassesinitialEventstoEventApp— zero loading spinner - Client cache: sessionStorage with 5min staleness in
useEventshook - OG image cache: Module-level
imageCacheMap inOGImage.tsx, batch POST/api/ogfor gallery
Friendtype (src/lib/types.ts) — raw Supabase profile fields (user_id,avatar_url,x_handle, etc.)FriendInfotype — lightweight view type (userId,displayName,avatarUrl,xHandle)useFriendsItinerarieshook — fetches friend itineraries, merges display names + avatarsuseConferenceDatahook — buildsfriendsByEventandcheckedInFriendsByEventmaps (bothMap<string, FriendInfo[]>)UserAvatarcomponent — fallback chain: uploaded avatar → X/Twitter photo via unavatar.io → deterministic color initials
- Themes defined as CSS custom properties in
src/app/globals.cssusing[data-theme="name"]selectors - Theme metadata in
src/lib/themes.ts—ThemeIdunion type +THEME_OPTIONSarray - Header variables:
--theme-header-bg,--theme-header-border,--theme-header-logo-filter,--theme-header-text,--theme-header-control-*,--theme-header-accent-* - Per-conference theme selection via admin config (
theme:{conference}key in Supabase) - 8 themes: dark, paper, light, light-blue, sxsw, sxsw2, gdc, ethcc
- Most icons from
lucide-react - Custom planwtf calendar SVG at
src/components/icons/CalendarIcon.tsx - Event add-to-plan:
Plus/Checkin circular container (StarButton.tsx) - View toggle: Map, List, Table, LayoutGrid (gallery)