Cross-device version of the habit dashboard: a full-screen reminder/pomodoro display backed by a real database, so reminders you create on your work computer show up on your personal one too. Protected by Google sign-in, locked to an allowlist of specific emails — each allowed account gets its own private reminders, work blocks, and settings.
Next.js 14 (App Router) · Apollo Server + Apollo Client (GraphQL API at /api/graphql) · Prisma + Postgres · NextAuth.js (Google OAuth) · Vercel Blob (image uploads)
npm install
cp .env.example .envFill in .env (see section 3 for where each value comes from), then:
npx prisma db push # creates tables from prisma/schema.prisma
npm run prisma:seed # adds 5 starter reminders + 2 work blocks
npm run devOpen http://localhost:3000. You'll be sent to Google sign-in first.
- Push this folder to a GitHub repo, then import it on Vercel.
- In the Vercel project, go to Storage → Create Database → Postgres, attach it. Vercel sets
DATABASE_URL(and a few related vars) automatically. - Go to Storage → Create → Blob, attach a store. Vercel sets
BLOB_READ_WRITE_TOKENautomatically. - Add the remaining env vars under Settings → Environment Variables:
NEXTAUTH_SECRET,NEXTAUTH_URL(your production URL, e.g.https://inhabit.vercel.app),GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,ALLOWED_EMAIL. - Deploy. The build script (
prisma generate && next build) generates the Prisma client automatically — no manual step needed on Vercel. - After the first deploy, run the schema push once against the production database:
Easiest way:
npx prisma db push # with DATABASE_URL in your shell set to the Vercel Postgres URL npm run prisma:seed # optional, adds starter reminders
vercel env pull .env.production.localto grab the realDATABASE_URL, then run the two commands locally with that file loaded.
- Google Cloud Console → create a project (or reuse one) → APIs & Services → OAuth consent screen → set it up for External users, add yourself as a test user if it stays in testing mode.
- APIs & Services → Credentials → Create Credentials → OAuth client ID → Application type Web application.
- Authorized redirect URI:
- Local:
http://localhost:3000/api/auth/callback/google - Production:
https://<your-app>.vercel.app/api/auth/callback/google
- Local:
- Copy the generated Client ID and Client Secret into
GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET. ALLOWED_EMAILis a comma-separated list of the Google account emails allowed to sign in (e.g.[email protected],[email protected]). Anyone not on the list is rejected even though Google auth succeeds. Each listed account's data is fully isolated — they never see each other's reminders or work blocks.
NEXTAUTH_SECRET — generate one with openssl rand -base64 32.
Same feature set as the original static version: reminders with text/image/video that take over the screen and recede to a small badge, work blocks with pomodoro-style break takeovers, automatic theme/layout rotation to avoid burn-in, duplicate/skip-today on reminders. The difference is everything now lives in Postgres via GraphQL, so it's the same data from any signed-in browser.
Settings (takeover duration) and sign-out are in the gear icon in the top bar.
- Per-user data isolation was added after the first deploy. If you already ran
npx prisma db pushagainst a real database before this,Reminder/WorkBlockrows won't have auserEmailvalue yet and the newNOT NULLcolumn will fail to apply (orAppSettings'sidcolumn was dropped in favor ofuserEmail). Simplest fix for a personal dev/single-user database: drop the three tables (or the whole DB) and re-runnpx prisma db push+npm run prisma:seed. If you have real data you want to keep, backfilluserEmailon the existing rows with your account's email first, then push. - Scheduling runs in the browser tab, not on a server — the dashboard needs to stay open (e.g. on your second monitor) to fire reminders. There's no server-side push/notification.
- One-minute resolution: reminders fire when the clock matches
HH:MM, checked every 5 seconds. - Video autoplay requires the video to be muted (browser policy) — already wired up for YouTube/Vimeo/direct file links.
- Image uploads go to Vercel Blob and are capped at 8MB.
- I could not run
npx prisma generatein my sandbox while building this (its network access doesn't reachbinaries.prisma.sh), so the Prisma-dependent files (src/lib/prisma.ts,src/graphql/resolvers.ts) are type-correct against the schema by inspection but weren't verified against a freshly generated client. Runningnpx prisma generate(or justnpm install+npm run dev/vercel build) on your machine will generate it normally — if there's a mismatch you'd see it immediately as a TypeScript error pointing at the resolver file.
prisma/schema.prisma Reminder / WorkBlock / AppSettings models
src/graphql/ schema.ts (typeDefs), resolvers.ts, queries.ts (client-side gql ops)
src/app/api/graphql/ Apollo Server route
src/app/api/upload/ Vercel Blob upload route
src/app/api/auth/ NextAuth route
src/components/ Dashboard, modals, MediaEmbed
src/lib/ prisma client, auth options, time helpers, video embed parsing