PropEdge AI is a sports props analytics web app that lets users research multi-sport player props (PrizePicks/Underdog style) and apply custom weighted models to get an edge score on each prop.
- Frontend: Next.js 16, Tailwind CSS v4, Radix UI (shadcn-style components)
- Backend: Supabase (Auth, Postgres, RLS)
- AI: OpenAI-compatible API (Grok, Claude, GPT-4o-mini, etc.)
- Payments: Stripe (Checkout + Customer Portal)
src/
├── app/
│ ├── (auth)/login, signup
│ ├── api/
│ │ ├── ai-insight/ # LLM insight generation
│ │ └── stripe/ # checkout, webhook, portal
│ ├── dashboard/ # Main props table + pick builder
│ ├── models/ # Model builder
│ └── pricing/
├── components/
├── context/ # AuthProvider
├── data/ # Mock props
├── lib/ # Supabase, model scoring, utils
└── types/
id(UUID, FK → auth.users)emailfirst_name,last_name,birthday(optional)is_premium(boolean)ai_insights_used_today,ai_insights_date(for free tier limit)created_at,updated_at
id,user_id,name,descriptionfactors(JSONB):[{ id, name, weight }]performance_score,is_activecreated_at,updated_at
user_id,stripe_customer_id
Run migrations in order: 001_initial_schema.sql, then 002_profiles_name_birthday.sql (adds first_name, last_name, birthday to profiles).
The Model Edge % is computed via a weighted formula:
edge = Σ (factor_score × factor_weight) / Σ factor_weight
Each factor (recent_form, matchup_difficulty, pace, etc.) returns a 0–100 score based on prop data. Weights are 0–100% and must sum to 100 for normalization.
Factors:
recent_form: hit rate × 100matchup_difficulty: 50 + trend × 5pace,usage_minutes,home_away,rest_days,sample_size: derived from prop stats
See src/lib/model-scoring.ts.
Props and time-series use mocker-data-generator + @faker-js/faker for scalable mock data:
- Props:
generateMockProps(count, seed)insrc/data/mock-generator.ts— generates 200 props by default (deterministic via seed for SSR hydration). - Time-series:
src/data/mock-timeseries.ts— 90-day chart data.
To change volume: edit MOCK_PROPS in src/data/mock-props.ts, e.g. generateMockProps(500, 42).
To plug in a real odds/props API (e.g. OpticOdds):
- Data shape: Map API response to
Proptype insrc/types/index.ts. - Fetch: Create
src/lib/props-api.tswithfetchProps(sport, date). - Replace mock: In
src/app/dashboard/page.tsx, replaceMOCK_PROPSwith a server fetch or client-sideuseEffectcalling your API. - Caching: Add React Query or SWR for caching; consider server-side fetch for SEO.
Copy .env.example to .env.local and fill in:
- Supabase URL and keys
- Stripe secret, webhook secret, price ID
- AI API key and optional URL/model override
- Create a Product + recurring Price ($19.99/mo) in Stripe Dashboard.
- Set
STRIPE_PRICE_IDto the price ID. - Add webhook endpoint:
https://your-domain.com/api/stripe/webhook - Events:
customer.subscription.created,customer.subscription.updated,customer.subscription.deleted - Copy webhook signing secret to
STRIPE_WEBHOOK_SECRET.
When Supabase has Confirm email enabled (Auth → Providers → Email):
Magic Link (default): User gets a link, clicks it, redirects to /auth/callback.
Email OTP (6-digit code): To enable, edit the Magic Link email template in Supabase Dashboard → Auth → Email Templates. Replace the template body to include {{ .Token }}:
<h2>One time login code</h2>
<p>Please enter this code: {{ .Token }}</p>
Then both signup and login can use the 6-digit code flow.
Signup flow: Email → OTP verify → Set password. Optional: skip OTP and use magic link. Profile fields (first name, last name, birthday) are collected at signup and stored in profiles.
Login flow: Users can sign in with password or choose "Sign in with email code instead" to receive a 6-digit OTP.
- User receives email with code or link.
- User enters code or clicks link → Supabase redirects to
/auth/callback?code=... - The callback route exchanges the code for a session and redirects to
/dashboard. - Add
http://localhost:3000/auth/callbackandhttps://your-domain.com/auth/callbackto Supabase Authentication → URL Configuration → Redirect URLs.
- Vercel: Connect repo, add env vars, deploy.
- Supabase: Run migrations, enable Auth (email/password).
- Stripe: Use live keys and update webhook URL for production.
| Feature | Free | Premium |
|---|---|---|
| AI Insights | 5/day | Unlimited |
| Custom Models | 1 | 10 |
| Backtesting | Basic | Full |