Arena is a production-ready full-stack SaaS web app for dealership leaderboard podium tracking.
- Next.js 14 (App Router)
- TypeScript
- Tailwind CSS
- Prisma ORM
- PostgreSQL
- REST API routes
- JWT authentication
- Role-based access control
- Modular service architecture
- Role-based dashboards for
sales_rep,service_rep, andmanager - CRM adapter pattern (
EleadAdapter,FortellisAdapter,XtimeAdapter,DripJobsAdapter) - Webhook ingestion with source secret validation
- Idempotent ingestion via unique external record IDs
- Monthly leaderboard aggregation with dynamic sorting
- Podium separation (top 3) and remaining list
- Manager CSV export endpoint
src/app: App Router pages and REST API routessrc/components: UI components (auth, dashboard, layout)src/lib: shared utilities (db, auth, env, adapters)src/services: business logic layersrc/types: API/domain typesprisma/schema.prisma: database schema
Copy .env.example to .env and set real values:
DATABASE_URLJWT_SECRETJWT_EXPIRES_INCOOKIE_DOMAIN(optional)ELEAD_WEBHOOK_SECRETFORTELLIS_WEBHOOK_SECRETXTIME_WEBHOOK_SECRETDRIPJOBS_WEBHOOK_SECRET
npm install
npm run prisma:generate
npm run prisma:migrate
npm run devPOST /api/auth/signupPOST /api/auth/loginPOST /api/auth/logoutGET /api/auth/me
POST /api/onboarding
GET /api/leaderboard?metric=<...>&department=<sales|service|all>GET /api/leaderboard/export?metric=<...>&department=<sales|service|all>(manager only)
POST /api/webhooks/eleadPOST /api/webhooks/fortellisPOST /api/webhooks/xtimePOST /api/webhooks/dripjobs
Each webhook requires header:
x-webhook-secret: <source secret>
All vendor-specific payload logic is isolated in:
src/lib/adapters/crm/elead.adapter.tssrc/lib/adapters/crm/fortellis.adapter.tssrc/lib/adapters/crm/xtime.adapter.tssrc/lib/adapters/crm/dripjobs.adapter.ts
Business logic reads normalized records only via src/lib/adapters/crm/index.ts.
- Filters by current month/year
- Aggregates totals per user
- Computes
efficiency_rate = (hours_billed / hours_worked) * 100 - Dynamically sorts by selected metric
- Returns
podium(top 3) +leaderboard(remaining rows)