Go to file
Brian Fertig a849293bd0 Add account linking and per-account theme persistence
- Profile page gains a Request Account Link form, plus sections for pending requests (create / deny / deny-and-block), confirmed linked accounts (toggle / remove), and blocked requesters (allow requests)
- Toggle signs the browser in as the linked account using a single-use 60-second switch token minted server-side after link verification, so the target's password is never needed; a new "account-switch" Credentials provider in auth.ts consumes the token atomically to prevent replay
- Theme preference now stored on the User profile row (new `theme` column) and applied from the root layout's no-FOUC script before first paint, so each account keeps its own look across toggles and devices; anonymous visitors keep the old localStorage behavior
- New Prisma models: AccountLinkRequest, AccountLink, AccountLinkBlock, AccountSwitchToken with composite uniques, self-link check constraint, and cascade deletes
2026-08-30 13:28:55 -06:00
.agents/skills
.cdp
.claude/skills
.windsurf/skills
app Add account linking and per-account theme persistence 2026-08-30 13:28:55 -06:00
components Add account linking and per-account theme persistence 2026-08-30 13:28:55 -06:00
hooks
lib Add account linking and per-account theme persistence 2026-08-30 13:28:55 -06:00
prisma Add account linking and per-account theme persistence 2026-08-30 13:28:55 -06:00
public
shots
types Add account linking and per-account theme persistence 2026-08-30 13:28:55 -06:00
.dockerignore
.env.example
.gitattributes
.gitignore
AGENTS.md
CLAUDE.md
Dockerfile
README.md Add account linking and per-account theme persistence 2026-08-30 13:28:55 -06:00
auth.ts Add account linking and per-account theme persistence 2026-08-30 13:28:55 -06:00
components.json
docker-compose.yml Add user profile page with name and photo support 2026-08-30 11:24:05 -06:00
docker-entrypoint.sh
eslint.config.mjs
next.config.ts Add user profile page with name and photo support 2026-08-30 11:24:05 -06:00
package-lock.json
package.json
postcss.config.mjs
prisma.config.ts
proxy.ts
skills-lock.json
software.md
tsconfig.json

README.md

Organize

A self-hosted, installable PWA for staying organized: an extended to-do list and notes organizer laid out as a Kanban-style board.

  • Categories are the board's lanes (e.g. "To Do", "In Progress", "Done").
  • Groups are the cards within a lane — each has a color, a markdown notes panel, and a to-do list, and can be dragged between lanes.
  • To-Dos live inside a Group, with an optional details field.

Accounts are required (there's no anonymous/local-only mode — the board needs a database to persist to), and the first account ever created on a given instance automatically becomes its administrator.

Features

  • Kanban board with drag-and-drop: reorder Categories, and drag Groups between/within Categories
  • Per-Group markdown notes and a to-do list with a completion progress indicator
  • Light/dark theme (follows system by default)
  • Per-account theme: the theme you pick (theme menu, bottom-left) is saved on that account's profile, so each account keeps its own look — after an account toggle you land in the other account's theme. Applies from the first frame of each page load (no flash)
  • Installable PWA (add-to-home-screen); requires a live connection to the server for its database, so there's no offline mode
  • Credentials-based accounts (email + password), with an Admin page to:
    • change a user's role (Administrator / User / Pending), email, or password, or delete their account
    • control how the site handles new sign-ups (see Sign-up modes below)
  • Profile page (/profile): set a first/last name and a profile photo, shown in the menu on the left (the photo replaces the initial-letter avatar when set). The account's default theme lives on the profile too — pick it from the theme menu (bottom-left of the sidebar); it persists per account, not per browser
  • Account linking (Profile page): link multiple accounts on the same server so one person can juggle more than one identity.
    • Request Account Link — ask to link by the other account's email; the request shows "awaiting confirmation" until they respond
    • Requested Account Link — the recipient's list of pending requests, with Create Account Link, Deny Account Link, or Deny and Block Account Link (blocks that account from requesting again; blocks can be lifted from the Blocked Account Link Requests section)
    • Linked Accounts — the other side of each confirmed link, with Toggle (signs this browser out and back in as that account — it never asks for that account's password) and Remove Link (either linked account can do it)

Tech stack

Next.js 16 (App Router) · React 19 · TypeScript · Tailwind CSS 4 · PostgreSQL via Prisma 7 · Auth.js v5 (Credentials provider, JWT sessions) · dnd-kit · base-ui

Quick start (Docker)

This is the recommended way to run Organize. All you need is Docker with the Compose plugin.

  1. Clone the repo

    git clone https://git.brianfertig.com/brianfertig/Organize.git
    cd Organize
    
  2. Create your .env

    cp .env.example .env
    

    Then open .env and:

    • Generate a real AUTH_SECRET (used to sign session cookies) — for example with openssl rand -base64 32 — and paste it in.
    • Optionally change POSTGRES_PASSWORD from the placeholder.

    See Configuration below for what each variable does.

  3. Start everything

    docker compose up -d
    

    This builds the app image, starts Postgres, waits for it to be healthy, then starts the app — which applies any pending database migrations automatically on every boot before serving traffic. No separate migration or setup step is needed.

  4. Open the app at http://localhost:3000 (or whatever APP_PORT you set) and sign up. The very first account created becomes the site's administrator; everyone after that follows whatever sign-up mode is currently set (Open, by default).

Updating

git pull
docker compose up -d --build

--build matters here: without it, Compose will keep running whatever image it already built and won't notice that the source changed.

Stopping

docker compose down

Postgres's data lives in ./data/postgres on the host (a bind mount, not a Docker volume), so it survives docker compose down — back up that directory if you want a safety net before major upgrades.

Configuration

All configuration is via environment variables, read from .env by Docker Compose (see .env.example for the template).

Variable Used by Purpose
AUTH_SECRET app Encrypts/signs session cookies. Required in production — generate with openssl rand -base64 32.
POSTGRES_USER db Postgres superuser created on first init.
POSTGRES_PASSWORD db Password for POSTGRES_USER. Change this from the .env.example placeholder.
POSTGRES_DB db Database name created on first init.
APP_PORT app (host mapping only) Host port the app is published on, e.g. 3000 for http://localhost:3000. Defaults to 3000 if unset. The container always listens on 3000 internally regardless of this value.
DATABASE_URL host-side tooling only Prisma connection string for running things like npx prisma studio from your own machine. Not used by the app container itself — see note below.

Note on DATABASE_URL: inside docker-compose.yml, the app service is given its own DATABASE_URL that points at the db service over the Compose network (db:5432), built from the POSTGRES_* variables — it ignores the DATABASE_URL in .env. The .env value (pointed at localhost:5432) is only there for convenience if you want to run Prisma commands, or the app itself, directly on the host. Since the bundled db service doesn't publish port 5432 to the host by default, that only works once you either add a ports: ["5432:5432"] mapping to the db service, or point DATABASE_URL at a Postgres instance you're running yourself.

Sign-up modes

The Admin page (/admin, administrators only) has a "New sign-ups" setting with four modes:

  • Open (default) — anyone can sign up and immediately gets the User role.
  • Approved — anyone can sign up, but starts as Pending and can't log in until an administrator changes their role to User or Administrator.
  • Confirmednot implemented yet (shown disabled in the UI); intended to gate new accounts on email confirmation instead of manual approval.
  • Closed — sign-ups are turned off entirely: the sign-up form and any links to it are removed, and the sign-up action itself refuses new accounts even if called directly.

The very first account on a fresh instance always becomes an administrator, regardless of the current sign-up mode — otherwise there'd be no one able to approve anyone.

Local development (without Docker)

Running the app directly on your host is mainly useful for editing code with hot reload. You'll still need a Postgres instance it can reach.

  1. Node 24 (matching the Docker image; no engines field enforces this, but it's what's tested) and npm install.
  2. A reachable Postgres. The simplest option is to add a port mapping to the db service in docker-compose.yml (ports: ["5432:5432"]) and run just that service: docker compose up -d db. Then set DATABASE_URL in .env to match (the .env.example default of postgresql://organize:organize@localhost:5432/organize will work as-is if you keep the default Postgres credentials).
  3. Apply migrations:
    npx prisma migrate dev
    
    Against a fresh database this also runs prisma/seed.ts automatically, creating dev@example.com / password123 as an administrator with a small sample board. If it doesn't (e.g. you'd already applied every migration before), run npx prisma db seed to trigger it explicitly. Don't run either against a database you care about.
  4. Run the dev server:
    npm run dev
    
    Open http://localhost:3000.

Other useful scripts: npm run build (production build), npm run start (serve a production build), npm run lint.

Project structure

app/                   Next.js App Router routes: (app) is the signed-in board + admin,
                       (auth) is login/signup
components/board/      The Kanban board: categories, groups, to-dos, drag-and-drop
components/admin/      Admin page: user table, role menu, sign-up mode settings
lib/actions/           Server Actions (auth, categories, groups, todos, notes, admin)
lib/                   Shared server helpers (db client, auth helpers, settings, colors)
prisma/                Schema, migrations, and the dev-only seed script
auth.ts                Auth.js configuration (Credentials provider, JWT session callbacks)
proxy.ts               Route protection (redirects signed-out users to /login, etc.)