Search and claim cards from the TCG vending machine inventory.
- 🔍 Real-time search powered by Algolia
- 🎴 Card claiming system with Supabase backend
- ⚡ Serverless API routes on Vercel
- ✅ Comprehensive test coverage
/
├── tcg-search/ # React frontend application
│ ├── src/
│ │ ├── components/ # React components (search, carousels, modals, chat)
│ │ ├── utilities/ # Algolia & Supabase client setup
│ │ └── assets/ # Logos and icons
│ ├── api/ # Vercel serverless API routes
│ └── public/ # Static assets
├── data/ # Algolia index management
│ ├── data-files/ # Card inventory data (XLSX per event)
│ └── data-utilities/ # Python scripts for indexing
├── vercel.json # Vercel deployment configuration
└── .vercelignore # Files excluded from deployment
- Algolia - Search service (free tier available)
- Supabase - Database backend (free tier available)
- Vercel - Hosting platform (free tier available)
- Node.js 20+ (required by @supabase/supabase-js)
node --version # Should be v20.0.0 or higher - Vercel CLI:
npm i -g vercel - Python 3.x + Poetry (only needed for updating Algolia index)
- React 18 with Algolia InstantSearch v7
- Supabase JS Client for real-time subscriptions
- Vercel Serverless Functions
Run frontend only (fast, no API):
npm run devStarts Vite dev server at http://localhost:5173.
Run full stack locally (frontend + API routes):
npm run serveStarts Vercel dev server at http://localhost:3000 with API routes available at http://localhost:3000/api/*.
Required for local development (.env in tcg-search/):
# Algolia
VITE_ALGOLIA_APP_ID=your_app_id
VITE_ALGOLIA_API_KEY=your_search_key
VITE_ALGOLIA_INDEX_NAME=your_index_name
VITE_USER_TOKEN=your_user_token
VITE_ALGOLIA_CHAT_AGENT_ID=your_chat_agent_id # optional
# Supabase
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=your_publishable_key
SUPABASE_SECRET_KEY=your_secret_key # server-side only, not exposed to browser
# Algolia (server-side only)
ALGOLIA_WRITE_API_KEY=your_write_key # server-side only, used by claims APICard inventory is managed via a per-event XLSX file placed in data/data-files/{event_id}/, ingested into Algolia via Python scripts in data/data-utilities/. See data/data-utilities/README.md for details on individual scripts, data format, and output schema.
Install Python dependencies and configure credentials:
cd data/data-utilities
poetry installCreate data/.env from data/.env.example:
ALGOLIA_APP_ID=your-app-id
ALGOLIA_API_KEY=your-admin-api-key
ALGOLIA_EVENTS_INDEX=tcg_events
ALGOLIA_EVENT_ID=my-event-2026 # only needed when running scripts directly
Run setup_event.sh from the repo root to bootstrap a new event end-to-end:
./setup_event.sh <event_id> <event_name> <booth> [venue] [--no-activate]
# Example:
./setup_event.sh etail-palm-springs-2026 "eTail Palm Springs 2026" 701 "JW Marriott Desert Springs Resort"The script runs six steps:
Step 1 — Create indices and event record (create_event.py)
Creates the primary card index tcg_cards_{event_id} and two virtual replica indices (_price_asc, _price_desc) in Algolia, applies settings from algolia-config.json to the primary (replicas inherit automatically), and inserts a record into tcg_events with current: false.
Step 2 — Clear the index (clear_index.py)
Wipes any existing card records from tcg_cards_{event_id}. Preserves index settings. (Useful when re-running setup after a partial failure.)
Step 3 — Ingest card data (ingest.py)
Reads card data from data/data-files/{event_id}/ — prefers a single XLSX file (one sheet per card set), falls back to individual CSVs. Enriches each card with image URLs and type data from the TCGdex API, and uploads records to tcg_cards_{event_id}.
If no data files are found, setup exits cleanly with instructions to add files and run
reset_and_ingest.shwhen ready.
Step 4 — Enrich chase cards (enrich_chase_cards.py)
Reads the master XLSX spreadsheet to identify chase cards, then applies partial updates to the correct records already in Algolia. Skipped if no XLSX is present.
Step 5 — Create Agent Studio agent (agent/agent.py)
Creates an Algolia Agent Studio agent configured for the new event.
Step 6 — Activate the event (set_active_event.py)
Sets the new event to current: true in tcg_events, clearing current from any previously active event. The frontend reads this to determine which event to display. Pass --no-activate to skip this step and activate manually later.
When card data changes (updated XLSX or CSVs) but the event already exists, use reset_and_ingest.sh to re-run Steps 2–4 without recreating indices or touching the events record:
data/data-utilities/reset_and_ingest.sh <event_id>
# Example:
data/data-utilities/reset_and_ingest.sh etail-palm-springs-2026To make a different (already set-up) event the active one:
cd data/data-utilities
poetry run python set_active_event.py list # see all events
poetry run python set_active_event.py set <event_id>This is a safe, non-destructive operation — it only updates the current field in tcg_events. The frontend will redirect to the new event on next load.
cd tcg-search
# Interactive watch mode
npm test
# One-time run with coverage
npm run test:ciSee tcg-search/README.md for more details on the frontend.
-
Link to Vercel project:
vercel link
-
Add environment variables in Vercel dashboard:
- Go to Project Settings → Environment Variables
- Add all variables from
.env(see above) - Make sure to add them for all environments (Production, Preview, Development)
Deploy from repository root (not tcg-search/):
# Preview deployment
npm run deploy
# or
vercel
# Production deployment
npm run deploy:prod
# or
vercel --prodBuild and root directory settings are configured in the Vercel dashboard (Root Directory: tcg-search).
- ✅ React frontend (built from
tcg-search/) - ✅ API routes (from
api/) - ✅ Serverless functions configuration
- ❌ Data scripts (excluded via
.vercelignore) - ❌ Tests and coverage (excluded)
- Framework: React 18 with Vite
- Search: Algolia InstantSearch v7
- Styling: Custom CSS
- Platform: Vercel Serverless Functions
- Database: Supabase (Postgres)
- API Routes:
POST /api/claims/create- Submit card claim
Required table in your Supabase project. Run data/supabase_claims_table.sql in the Supabase SQL editor to create the table and policies:
| Column | Type | Notes |
|---|---|---|
id |
bigint |
Primary key, auto-increment |
card_id |
text |
Algolia objectID (e.g., sv08-102) |
pokemon_name |
text |
|
card_number |
text |
|
set_name |
text |
|
card_value |
numeric |
Nullable |
image_url |
text |
Nullable |
claimer_first_name |
text |
|
claimer_last_name |
text |
|
claimer_name |
text |
Legacy field, nullable |
claimed_at |
timestamptz |
Default: now() |
Enable Row Level Security (RLS). The anon role is granted SELECT on all rows — the table contains no sensitive PII (no email). Inserts go through the server-side API using the service_role key.
- Row Level Security (RLS) enabled on Supabase
- Server-side validation for all inputs
SUPABASE_SECRET_KEYandALGOLIA_WRITE_API_KEYare server-side only, never exposed to the browser
- Create a feature branch:
git checkout -b feat/your-feature - Make changes and test locally with
npm run serve - Run tests:
cd tcg-search && npm run test:ci - Commit with semantic prefixes:
feat:,fix:,chore:, etc. - Push and create a PR
See .claude/CLAUDE.md for detailed contribution guidelines.
Private repository - see LICENSE file for details.