Group scheduling with timezones — paint availability on a grid, see overlap at a glance, and pick a slot that works for everyone. No accounts; events are shared by link.
- Shareable events — Create an event with a title, optional description, meeting duration, and your timezone. Guests open a short URL; no sign-up.
- Timezone-aware — IANA timezones, browser-friendly picker, availability stored in UTC so everyone sees correct local times.
- Paint your availability — Drag on the grid with mouse or touch; works well on mobile.
- Group heatmap — Visual overlap of who is free when.
- Best-time suggestions — Surfaces continuous windows where the whole group can meet for the configured duration.
- PostgreSQL + Prisma — Persistent events, slots, and per-guest responses.
- Next.js 14.2 (App Router), React 18, TypeScript
- Tailwind CSS 3.4
- Prisma 5 · PostgreSQL (16+ recommended)
- Luxon for date/time; IANA data via @vvo/tzdb
- Node.js 22+ on your machine (see
enginesinpackage.jsonand.nvmrc)
Prerequisites: Node.js 22+, PostgreSQL (16 recommended), and npm.
Create a database for the app (e.g. slotmatch) and a user if you want one dedicated to the project. Example:
CREATE DATABASE slotmatch;
-- optional: CREATE USER slotmatch WITH PASSWORD 'your-password';
-- GRANT ALL PRIVILEGES ON DATABASE slotmatch TO slotmatch;If you have PostgreSQL client tools installed locally, the Makefile can create the default database:
make db-createBy default, make db-create uses your current OS username as the PostgreSQL role, which matches common Homebrew PostgreSQL installs on macOS. Override it only if your local server uses a different role:
make db-create DB_USER=postgrespsql and missing roles — With no arguments, psql uses your OS username as the PostgreSQL role. If you see FATAL: role "yourname" does not exist, create that role (typical for local dev):
sudo -u postgres psql -c 'CREATE ROLE yourname WITH LOGIN SUPERUSER;'On macOS/Homebrew, if you see FATAL: role "postgres" does not exist, use your macOS username in DATABASE_URL instead, or create the postgres role manually.
Or use the superuser shell, then create roles and databases in SQL:
sudo -u postgres psqlFor password auth over TCP (localhost:5432), create the role with a password and connect explicitly:
CREATE ROLE yourname WITH LOGIN PASSWORD 'your-secret' CREATEDB;psql -h localhost -U yourname -d postgresApp in Docker, Postgres on the host — Set DATABASE_URL to reach the host from the container (e.g. host.docker.internal on Docker Desktop, or the host’s LAN IP on Linux), not localhost as seen from inside the container.
-
Clone the repository and enter the project directory.
-
Create a database (see PostgreSQL on the host above) and note the connection string for
DATABASE_URL. -
Environment variables — Copy
.env.exampleto.env(or.env.local) and set at least:DATABASE_URL— e.g.postgresql://USER:PASSWORD@localhost:5432/slotmatchNEXT_PUBLIC_APP_URL— where the app is reachable in the browser, e.g.http://localhost:3000
Optional Umami Cloud analytics: set
NEXT_PUBLIC_UMAMI_URL(script origin from the dashboard, no trailing slash) andNEXT_PUBLIC_UMAMI_WEBSITE_ID(see.env.example).Optional Prisma query logging in development:
PRISMA_LOG_QUERIES=1(see.env.example).cp .env.example .env # edit .env with your DATABASE_URL and NEXT_PUBLIC_APP_URL -
Install dependencies and prepare Prisma:
make install
For a fresh database that should only apply existing migrations (no prompts), use
npx prisma migrate deployinstead ofmigrate dev. -
Run the dev server:
make run
-
Open the app — http://localhost:3000
# Install dependencies, generate Prisma client, and apply local migrations
make install
# Start the local dev server
make run
# Check whether the local database exists
make db-status
# Create the local database if it does not exist
make db-create
# ESLint
npm run lint
# TypeScript (no full Next build)
npm run typecheck
# Unit tests (Vitest)
npm run test
# Lint + Prisma generate + typecheck + unit tests (matches GitHub Actions)
npm run ci
# Production build (run prisma generate first if the schema changed)
npx prisma generate && npm run build
# Prisma
npm run prisma:migrate # create/apply migrations (dev)
npm run prisma:deploy # apply migrations (CI / prod)
npm run prisma:studio # open Prisma Studio
npm run prisma:reset # destructive: reset DB and re-apply (dev only)Unit (Vitest) — npm run test runs vitest run on lib/**/*.test.ts (pure logic: intervals, grid, matching). No database required.
End-to-end (Playwright) — @playwright/test uses playwright.config.ts: Chromium, baseURL http://127.0.0.1:3000, and a webServer that runs npm run dev (reuses an existing server locally unless CI is set). Specs live in e2e/.
Install browsers once per machine:
npx playwright installWith Postgres running, migrations applied, and either (a) nothing listening on port 3000 so Playwright can start the dev server, or (b) npm run dev already running and CI unset so the config reuses it:
npm run e2e- Pull requests and pushes to non-
mainbranches —.github/workflows/ci.ymlruns.github/workflows/_tests.yml: lint, typecheck, unit tests, and Playwright E2E (with a PostgreSQL 16 service). - Pushes to
main—.github/workflows/build-and-publish.ymlruns the same tests, then builds and pushesghcr.io/<owner>/slotmatch-appto GitHub Container Registry (seeDEPLOYMENT.mdfor secrets and Lightsail pull).
Run the same checks CI runs, then optionally E2E:
npm ci
npx prisma generate
npm run ci
# optional, needs DB + migrations (see Quick start):
npm run e2eQuick sanity: git status, ensure .env is not staged, and git diff --cached if you use selective staging.
Recommended: Docker on a VPS (e.g. AWS Lightsail) — images are built in CI and pulled from GHCR; nginx terminates TLS. See DEPLOYMENT.md for Compose, Makefile, secrets, and APP_IMAGE.
The app uses Next.js output: "standalone" (see next.config.mjs). Manual standalone (without Docker): set env vars from .env.production.example, run npx prisma generate, npm run build, npx prisma migrate deploy, then node .next/standalone/server.js with public and .next/static beside the standalone output per the Next.js standalone docs.
For managed platforms (e.g. Vercel), follow their Postgres and env-var conventions instead.
MIT — see the license file for the full text.