A two-sided scheduling application where professional availability is strictly constrained by physical resource capacity, and clients are auto-matched to professionals based on timeslot selection.
Ward solves a critical problem in service scheduling: physical capacity constraints. Traditional schedulers are "person-centric" and ignore facility limits. Ward is "timeslot-centric" and "resource-constrained," ensuring that booking availability reflects actual physical capacity.
End user Documentation: Github Pages
Two-step availability loop:
- Admins create Events with date ranges, hours, and Resources (rooms, tables, stations) with defined capacities
- Professionals book Shifts on Resources during Event hours to generate client-facing availability
- Clients pick from a pool of available timeslots, and the system auto-assigns an available Professional
- π Event-Driven Scheduling: Multi-day events with configurable hours per day, blackout periods (e.g., lunch breaks), and timezone support
- π’ Resource Capacity Management: Resources with quantities and professional-per-unit limits (e.g., 5 tables, 1 professional per table)
- π€ Auto-Matching Algorithm: Clients select timeslots; the system randomly assigns from available professionals
- β±οΈ Fixed 30-Minute Slots: All bookings use consistent 30-minute intervals
- π Soft Deletion: Preserves data history without cascade deletion
- π Phone-Based Authentication: Twilio Verify SMS codes with NextAuth session management
- π₯ Role-Based Access Control: ADMIN, PROFESSIONAL, and CLIENT roles with strict permission boundaries
- π‘οΈ Security Hardened: Rate limiting (global, API, auth tiers), IDOR prevention, environment validation
- π Admin Dashboard: System-wide statistics, user/resource/event management
- π Event Agenda View: Calendar-style drill-down into event schedules showing all shifts and bookings
- π Booking Management: Reassign clients to different professionals/timeslots, remove bookings
- π€ User Management: Sortable/filterable user list, role assignment, account deletion
- ποΈ Resource Management: CRUD with sorting/filtering, location fields, quantity and capacity configuration
- π§ Email Connectivity Test: Send a test email from settings to validate Resend configuration step-by-step
- π€ Data Export: Export system data as JSON
- Booking confirmed β sent to client on successful booking
- Booking cancelled β sent to client on self-cancellation
- Admin-cancelled booking β sent to client when admin removes their booking
- Booking reassignment β sent to client when admin moves their booking
- Welcome email β sent to new users after account setup
- Role change β sent when admin updates a user's role
- Account deleted β sent when admin soft-deletes a user
- New booking β professional β notifies the assigned professional
- Booking cancelled β professional β notifies professional when a booking on their shift is cancelled
- Appointment reminder β daily cron job sends reminders for next-day appointments (8 AM Central)
All emails are fire-and-forget β failures never block or roll back the underlying operation.
- Browser push notifications β VAPID-based Web Push Protocol via
web-pushnpm package - Opt-in banner β clients and professionals see a one-time banner to enable push (dismissable)
- Settings toggles β independent email and push notification preferences per user
- Booking confirmed β push to client on successful booking
- Booking cancelled β push to client on self-cancellation or admin cancellation
- Booking reassigned β push to client when admin moves their booking
- New booking β professional β push to professional when a client books on their shift
- Booking cancelled β professional β push when a booking on their shift is cancelled
- Appointment reminder β push reminders sent alongside email reminders via daily cron
- Click-to-navigate β notification clicks open the relevant page (appointments, bookings, calendar)
- Stale subscription cleanup β automatically removes expired/invalid push subscriptions (410/404)
- π Dark Mode: Toggle between light and dark themes, stored per-user in the database
- βοΈ User Preferences: Per-user settings for timezone, date format (North American default), 12-hour/24-hour time display
- πΌοΈ Branding: Admin-uploadable login page image (up to 4000Γ4000 pixels, dynamically scaled)
- π± Responsive Design: Mobile navigation with hamburger menus, responsive calendar grids, mobile-first booking forms
- π¨ Custom Favicon: Distinctive browser tab icon
- π Vercel Web Analytics: Built-in analytics integration
- β€οΈ Health Check:
GET /api/healthendpoint for uptime monitoring
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Next.js 16 (App Router) | Server components, server actions, API routes |
| Language | TypeScript 5 | Type safety across the entire stack |
| Database | Turso (production) / SQLite (local) | Serverless SQLite with libSQL protocol |
| ORM | Prisma 7 with libSQL adapter | Type-safe database queries and migrations |
| Authentication | NextAuth 4 + Twilio Verify | Phone-based SMS verification with JWT sessions |
| Resend | Transactional email notifications from noreply@career-ward.app |
|
| Push | web-push (VAPID) | Browser push notifications for booking events and reminders |
| Styling | Tailwind CSS 4 + Geist font | Utility-first CSS with dark mode support |
| Testing | Vitest + Testing Library | Unit and integration tests |
| Deployment | Vercel | Serverless hosting with auto-deploy from GitHub |
| Analytics | Vercel Web Analytics | Usage and performance tracking |
| Validation | Zod 4 | Runtime schema validation |
| Service | Role | Setup Required |
|---|---|---|
| Twilio | Phone verification (SMS codes for login) | Create a Verify service, obtain Account SID, Auth Token, and Service SID |
| Turso | Production database (serverless SQLite) | Create a database, obtain the libsql:// URL and auth token |
| Resend | Transactional email delivery | Verify your domain (DNS records), generate an API key |
| Vercel | Hosting and deployment | Import GitHub repo, configure environment variables |
| Role | Core Actions | Data Access |
|---|---|---|
| ADMIN | Create Events & Resources, manage users, reassign/remove bookings, view analytics, system settings | Full access to all data |
| PROFESSIONAL | Book Shifts on Resources during Events, view assigned client details | Available Resources, own shifts, assigned client name/phone/email |
| CLIENT | Select timeslots, book appointments, cancel own bookings | Aggregated timeslot availability only (cannot see individual professionals) |
User: Phone-authenticated user with role, name, optional email, preferences (theme, timezone, date/time format), notification preferences (email/push toggles)Event: Multi-day scheduling event with timezone and date rangeEventDay: Per-day configuration (start/end hours, blackout periods)Resource: Physical capacity constraint with quantity, professionals-per-unit, optional locationEventResource: Association between Events and ResourcesShift: Professional availability on a Resource during Event hoursBooking: Client appointment auto-matched to a ShiftAppSetting: System-wide configuration (e.g., branding image)PushSubscription: Web Push subscription (endpoint, p256dh, auth keys) linked to User
- Event-Driven: All scheduling occurs within defined Events with explicit date ranges and hours
- Resource Capacity: Resources have quantities and professional-per-unit limits that constrain availability
- Time Slot Granularity: All bookings use fixed 30-minute intervals
- No Double-Booking: Resources, Professionals, and Shifts cannot have overlapping commitments
- Cascade on Event Edit: Shifts and bookings outside updated event dates are automatically cancelled
- Soft Deletion: All entities use
deletedAttimestamps instead of hard deletion
When displaying timeslots to a Client:
- Find all active Shifts (not deleted) covering the time slot within the Event's hours
- Subtract Bookings with status
CONFIRMED - If
(Total concurrent Shifts) - (Total concurrent CONFIRMED Bookings) > 0, the timeslot is available
When a Client selects a valid timeslot:
- Query all available, unbooked Shifts for that 30-minute slot
- Randomly select one Shift from the pool
- Create a Booking with status
CONFIRMEDtied to that Shift
- Database transactions with appropriate locking
- Race condition prevention for simultaneous bookings
- Automatic retry with different shifts on conflict
- Node.js 18.17+ and npm 9+
- Twilio Account: Create a Verify service for phone-based login
- Sign up and navigate to Verify β Services
- Create a new service, note the Service SID (starts with
VA...) - Obtain your Account SID and Auth Token from the Twilio dashboard
- Turso Account (production only): Serverless SQLite database
- Install CLI:
curl -sSfL https://get.tur.so/install.sh | bash - Create database:
turso db create ward-database - Get URL:
turso db show ward-database --url - Create token:
turso db tokens create ward-database
- Install CLI:
- Resend Account (for email notifications):
- Add your domain and configure DNS records (TXT/SPF/DKIM)
- Generate an API key in the Resend dashboard
- Vercel Account (for deployment):
- Import your GitHub repository
- Configure environment variables (see table below)
-
Clone and install dependencies:
git clone https://github.com/p0rkchop/ward.git cd ward npm install -
Configure environment:
cp .env.example .env # Edit .env with your settings (see Environment Variables below) -
Set up local SQLite database:
npx prisma generate npx prisma db push
-
Run development server:
npm run dev
-
Access the application at http://localhost:3000
| Variable | Description | Required |
|---|---|---|
DATABASE_URL |
Database connection URL (file:./dev.db for local, libsql://... for Turso) |
Yes |
TURSO_AUTH_TOKEN |
Turso authentication token | Production only |
NEXTAUTH_SECRET |
Secret for JWT session encryption (generate with openssl rand -base64 32) |
Yes |
NEXTAUTH_URL |
Public URL of the app (http://localhost:3000 for local) |
Yes |
TWILIO_ACCOUNT_SID |
Twilio Account SID | Yes |
TWILIO_AUTH_TOKEN |
Twilio Auth Token | Yes |
TWILIO_VERIFY_SERVICE_SID |
Twilio Verify Service SID (starts with VA...) |
Yes |
RESEND_API_KEY |
Resend API key for email notifications | Yes |
CRON_SECRET |
Secret for authenticating the daily reminder cron job | Production only |
NEXT_PUBLIC_VAPID_PUBLIC_KEY |
VAPID public key for Web Push (generate with npx web-push generate-vapid-keys) |
Yes |
VAPID_PRIVATE_KEY |
VAPID private key for Web Push | Yes |
VAPID_SUBJECT |
VAPID subject (e.g., mailto:admin@career-ward.app) |
Yes |
NODE_ENV |
Environment mode (development or production) |
No |
- Push to GitHub and import the repo in Vercel Dashboard
- Configure all environment variables from the table above
- Deploy β Vercel auto-builds on push to
mainusing the build command invercel.json:npx prisma generate && npm run build - Cron job: The daily appointment reminder runs automatically via Vercel Cron (
/api/cron/remindersat 8 AM Central)
turso db create ward-production
turso db show ward-production --url # β libsql://...
turso db tokens create ward-production # β auth token
npx prisma db push # push schema- Domain: Configure your custom domain in Vercel β Settings β Domains
- Email: Add DNS records (TXT, SPF, DKIM) for your domain in Resend to authorize sending from
noreply@<your-domain>
GET /api/healthβ returns{"status":"healthy","database":"connected"}when the app and database are operational
For comprehensive deployment instructions, troubleshooting, and production hardening, see DEPLOYMENT.md.
npm run test:run # Run all tests
npm run test:ui # Run with Vitest UI
npm run test:coverage # Run with coverage report- 106 tests across 7 test files (unit + integration)
- Covers shift creation, booking auto-matching, admin actions, validation, push notifications, and end-to-end booking lifecycle
- 100% pass rate
-
Rate Limiting (
app/lib/rate-limit.ts):- Global: 100 requests/minute per IP
- API: 60 requests/minute per IP
- Auth: 10 attempts/15 minutes per IP
- Sliding window algorithm with automatic cleanup
-
Environment Validation:
- Server-side validation of all required environment variables
- Warnings for default/development values in production
- Early failure on missing configuration
-
Authorization Enforcement:
- Role-based access control (ADMIN, PROFESSIONAL, CLIENT)
- Session-based user identification (prevents IDOR)
- Business rule validation in server actions
-
Database Security:
- Parameterized queries via Prisma (prevents SQL injection)
- Connection pooling and timeout configuration
- Turso auth token encryption in transit
-
Composite Indexes for common query patterns:
@@index([professionalId, startTime, deletedAt])@@index([resourceId, startTime, deletedAt])@@index([shiftId, startTime, status, deletedAt])@@index([clientId, startTime, deletedAt])
-
Batch Query Processing:
- Timeslot aggregation uses batch queries instead of N+1 queries
- Reduced database round-trips for timeslot availability checks
-
Connection Management:
- Prisma connection pooling for serverless environments
- Turso connection reuse
- Image Optimization: AVIF and WebP formats with responsive sizes
- Server Components: Maximum use of React Server Components
- Code Splitting: Automatic route-based code splitting
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
- Follow TypeScript strict mode
- Write tests for new functionality
- Update documentation as needed
- Use descriptive commit messages
This project is proprietary. All rights reserved.
- DEPLOYMENT.md - Comprehensive deployment guide
- DEVELOPMENT.md - Development plan and progress tracking
- REQUIREMENTS.md - Software requirements specification
- GitHub Issues: Report bugs or request features
- Documentation: See the documentation links above
- Emergency Contacts:
Vibe Coded like a ADHD multi-tasking agent herding baller by Chris Merkel
Built with β€οΈ using Next.js, Prisma, Turso, Resend, Twilio Auth
Claude Code - you mah boy, my daily driver, but you're cripped with those token limits, thankfully there's Deepseek all your cheap token goodneess, fuckin rad cash-money caching layer, and OpenAI/Anthropic compatible API endpoints. Rigging you to Claude code was next level shit. Keep on stealin that American IP, all their shit is stolen anyways.
- Eat π©, Twilio SMS service and taking my 20 bucks but not verifying me
- Github Copilot. Look bro, I want to like you, but you're dumb. Wise up, up your game. Stop releasing such jank-ass tools.