A group meeting scheduler with weighted availability and real-time aggregation. Create an event, share the link, and find the best time for everyone.
Go to the home page and fill out the event form:
- Event Name — give your meeting a title
- Meeting Type — In-Person (requires a location) or Virtual
- Time Range — set the start and end hours to consider
- Days — pick which days of the week are options (defaults to Mon-Fri)
You must sign in with your Clerk account before creating an event. After creating, you'll be redirected to the organizer dashboard.
Click Copy Share Link in the top-right corner to get a link like:
https://yoursite.com/event?code=j9eaFNJH
Send this to everyone who should participate. Everyone who opens the link must sign in before they can view the event or submit availability.
Each participant:
- Signs in and clicks Join
- Uses the Availability Slider to pick a level (0 = Busy, 1 = Free, with 0.25 steps)
- Clicks and drags on the schedule grid to paint time slots with that availability level
- Clicks Submit Schedule when done
The grid uses color coding: red (busy) -> yellow (partial) -> green (free).
After submitting, participants can see the Group Availability table showing aggregated scores, plus each person's Individual Schedule below it.
Access the organizer view by signing in with the Clerk account that created the event. The organizer can:
- Set their own availability on the same grid
- Adjust participant weights (0.0-1.0) — higher weight means more influence on the group average
- Include/exclude participants from the aggregate calculation
- Remove participants from the event
- View the weighted group average in real time, updated as weights change
The weighted average formula: for each time slot, sum(availability * weight) / sum(weights) across all included participants.
| Layer | Technology |
|---|---|
| Frontend | Next.js 15 (App Router) + React 18 + Material Web components |
| Backend | Express 5 (ESM) |
| Database | DynamoDB (events, participants, weights tables) |
| Infrastructure | AWS ECS Fargate behind ALB (path-based routing) |
| IaC | Terraform (S3 remote state + DynamoDB lock) |
| CI/CD | GitHub Actions (lint, test, build, Docker build check, staging deploy) |
scheduler-monorepo/
frontend/ # Next.js 15 — UI only, no API routes
backend/ # Express 5 — API server, DynamoDB
infra/
prod/ # Production Terraform (single ECS service, disabled)
staging/ # Staging Terraform (2 ECS services, ALB path routing)
bootstrap/ # Terraform state backend setup
scripts/
quality-gate.sh # Full lint + test + build for both workspaces
.github/workflows/
ci.yml # Parallel CI for both workspaces
deploy-staging.yml
npm install # install all workspace dependencies
npm run dev # start backend (4000) + frontend (3000)
npm run dev:backend # backend only
npm run dev:frontend # frontend onlyThe frontend proxies /api/* requests to http://localhost:4000 in dev via next.config.js rewrites.
Run checks:
npm --workspace=backend run lint
npm --workspace=frontend run lint
npm --workspace=backend run test # 61 tests
npm --workspace=frontend run test # 9 tests
npm --workspace=frontend run build
npm run quality-gate # all of the abovePORT(default:4000)AWS_REGION(default:us-west-2)DDB_EVENTS_TABLE(default:scheduler-prod-events)DDB_PARTICIPANTS_TABLE(default:scheduler-prod-participants)DDB_WEIGHTS_TABLE(default:scheduler-prod-weights)DDB_USERS_TABLE(default:scheduler-prod-users)DDB_USER_EVENTS_TABLE(default:scheduler-prod-user-events)CLERK_SECRET_KEYCLERK_JWT_KEYCLERK_AUTHORIZED_PARTIES— comma-separated allowed origins for Clerk session tokens
NEXT_PUBLIC_API_BASE_URL— API base URL (empty = relative paths via proxy)BACKEND_URL— dev proxy target (default:http://localhost:4000)NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
# Backend
docker build -t scheduler-backend:local ./backend
docker run --rm -p 4000:4000 \
-e AWS_REGION=us-west-2 \
-e DDB_EVENTS_TABLE=scheduler-staging-events \
-e DDB_PARTICIPANTS_TABLE=scheduler-staging-participants \
-e DDB_WEIGHTS_TABLE=scheduler-staging-weights \
scheduler-backend:local
# Frontend
docker build -t scheduler-frontend:local ./frontend
docker run --rm -p 3000:3000 scheduler-frontend:localStaging deploys automatically via deploy-staging.yml after CI passes on main. Architecture:
- ALB routes
/api/*to backend ECS service, everything else to frontend - 2 ECS Fargate services (backend on port 4000, frontend on port 3000)
- 3 DynamoDB tables with
scheduler-staging-prefix
Production workflows are disabled (.yml.disabled suffix). Cutover from the monolith is planned separately.
AWS_REGION—us-west-2AWS_ROLE_ARN— OIDC deploy role ARNECR_STAGING_BACKEND—scheduler-staging-backendECR_STAGING_FRONTEND—scheduler-staging-frontend
