manself is a personal daily-life dashboard: a ten-day calendar, a focus-style task board, drag-to-schedule household chores, sticky notes, weather and a clock, behind a full account system. Vue 3 on the front, Express and PostgreSQL behind it, and the whole thing starts with one Docker command.
It is a portfolio project, built with Claude Code as a pair programmer. Every change went through a pull request that was reviewed and merged by hand, with CI running the tests (and a Docker smoke test) each time.
You only need Docker (Compose 2.24 or newer). No .env, no keys, no database to set up:
git clone https://github.com/mberkyksel/manself-webapp.git
cd manself-webapp
docker compose up --buildThen open http://localhost:8080 and press Try the demo. The first build takes a minute or two.
Stop it with Ctrl+C. docker compose down removes the containers and keeps your data; add -v to wipe the database too.
The quickest way to see everything is the one-click demo account. Start the app (see Quick start), open it, and press Try the demo. Demo mode is on by default with Docker. See Demo mode for what the account contains.
A two-minute tour:
- Calendar. The top card shows the next ten days. Press + Add on a day to create an event, or Full month for the whole grid.
- Chores. Drag an icon from the Home Widget (top right) onto a day. On a chore badge: click toggles done, double-click removes it (an Undo toast appears), right-click cycles repeat (daily / weekly).
- Celebration. Today is one chore short of done. Click the laundry badge in Today's Chores to finish the day and get the confetti.
- Task board. Tick the circle on Prepare Friday demo slides (or drag it into Done): it gets a DONE stamp and the progress bar moves. The overdue and due-today chips at the top show what needs attention. Deleting a task asks for confirmation first.
- Sticky notes. On wide screens, drag a note anywhere or add one with + Note. Below 1440 px wide (phones, tablets, small laptops) they turn into a bottom sheet, so they never cover the page.
- Weather. The card uses your browser location. If you block it, it falls back to a saved city, then to Berlin.
- Settings. Open the menu (top-left logo), then Settings, to change the name or weather city. Email, password and deletion are locked on the demo account, and the page says so.
Resize the window (or open it on a phone) to see the mobile layout.
All taken from the demo account.
| Landing page | Board and sticky notes |
|---|---|
![]() |
![]() |
Working end to end, not a skeleton:
- Auth: register, login, a settings page (
/app/settings) to update name/email/password and delete the account (all destructive/credential-changing actions require the current password), session-expiry handling (expired/invalid tokens redirect to login instead of failing silently) - Dashboard: welcome banner, clock, weather (browser geolocation, falling back to a saved city or a default; OpenWeather with a daily-quota budget, or keyless Open-Meteo when no API key is set), branded favicon/wordmark, copyright footer
- Calendar: compact and full-month views, event CRUD, optional task linking
- Task board: Now / Next / Done lanes with a progress bar, overdue and due-today chips, friendly due-date labels, one-tap completion with a DONE stamp, a collapsing Done lane, and drag-and-drop across lanes (mouse and touch, via Pointer Events)
- Chores widget: drag-to-schedule onto a calendar day (mouse and touch), daily/weekly recurrence
- Sticky notes: free-drag positioning from 1440 px wide up, a stacked bottom-sheet list below that (no free positioning there - they would cover the page)
- Today's Todos: due-today and overdue tasks, inline complete toggle
- Feedback: toast notifications, confirm dialogs before destructive actions, and an Undo for removed chores; helpful empty states
- Public site: landing page with a product preview, 404 page, one-click demo login
- Responsive down to mobile widths across the whole app
Not implemented yet: Google Calendar sync. Calendar events are local to the app for now.
- Frontend: Vue 3 (Composition API,
<script setup>), Vue Router, Pinia, Vite, Tailwind CSS 3, Lucide icons - Backend: Node.js 20+ and Express 5 (ES modules), JWT authentication, bcrypt
- Database: PostgreSQL 16 via Sequelize (models and migrations)
- Tests and CI: Vitest with Vue Test Utils, Node's test runner with supertest, and GitHub Actions (tests on Node 20 and 22, plus a Docker smoke test)
- Packaging: Docker Compose, with nginx serving the build and proxying the API
docker-compose.yml runs the whole stack: Postgres, the backend API, and the frontend (built, then served by nginx, which also proxies /api/* to the backend so the browser only ever talks to one origin - see frontend/nginx.conf).
docker compose up --build # add -d to run in the backgroundIt is set up so that nothing has to be configured:
- Only the web app's port is published:
http://localhost:8080(change it withFRONTEND_PORT). The API and Postgres stay inside Docker's network, so nothing clashes with a database or dev server you already run. - Demo mode is on (
DEMO_MODE=true), so the login page has Try the demo. JWT_SECRETcan be left out: in demo mode a random one is generated on each start (everyone is signed out when the backend restarts). It is required as soon asDEMO_MODEis off.- Weather needs no key: without
WEATHER_API_KEYthe backend uses Open-Meteo; with a key it uses OpenWeather. - The backend waits for Postgres to be healthy, runs any pending migrations on every start, and the frontend waits for the backend to be healthy, so the first click always works.
To change anything, copy .env.example to .env next to docker-compose.yml (optional; it is picked up automatically) or set variables in your shell.
This is a demo setup, not a production one. Everyone who clicks Try the demo shares one account that is reset on each login. For a real deployment set DEMO_MODE=false, a real JWT_SECRET, and your own database credentials.
Developing outside Docker? docker-compose.dev.yml publishes Postgres (POSTGRES_PORT, default 5432) and the backend (BACKEND_PORT, default 3000) for that; see Local development.
With DEMO_MODE=true (set in .env, see .env.example) the login and register pages show a Try the demo button. One click logs into a shared sample account (demo@manself.local / demo-manself) that is reset to fresh, date-relative data every time: tasks due today and overdue, upcoming calendar events, sticky notes, and scheduled chores (today is one chore short of done, so finishing it shows the confetti).
npm run seed(inbackend/) runs the same reset from the command line.- Everything the demo shows (tasks, events, notes and chores) is stored on the server per user and reset on each demo login, like any other account's data. The browser sends its own date so the demo's chores line up with the visitor's "today".
- Email, password and account deletion are blocked on the demo account. Leave
DEMO_MODEoff for any real deployment.
To work on the code, run the backend and frontend with npm and keep only Postgres in Docker.
- Node.js 20+ and npm
- A running PostgreSQL instance (via Docker Compose, or a local install)
-
Copy the env template:
cp .env.example backend/.env
Set
DATABASE_URLto point at your Postgres instance.JWT_SECRETis optional whileDEMO_MODE=true(a random one is made on each start); generate a real one withopenssl rand -hex 32if you turn demo mode off. -
Start Postgres. Easiest via Docker Compose, using the dev file that publishes its port:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d postgres
This starts Postgres 16 on
localhost:5432with a persistedpostgres_datavolume, using thePOSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DBvalues from.env.example(defaults:postgres/postgres/manself_dev) unless overridden by a root-level.env. If something else already uses port 5432 (a local Postgres, for example), setPOSTGRES_PORT=5433in that root.envand use the same port inDATABASE_URL. To make a plaindocker compose up -d postgresdo the same, addCOMPOSE_FILE=docker-compose.yml:docker-compose.dev.ymlto that.env.Without Docker, create the database manually instead:
createdb manself_dev
-
Install dependencies for both apps:
cd backend && npm install cd ../frontend && npm install
-
Run migrations:
cd backend npm run migrate
Backend (from backend/):
npm run dev # nodemon, restarts on file changeStarts the Express API on http://localhost:3000. Health check: GET /api/health.
Frontend (from frontend/):
npm run devStarts the Vite dev server on http://localhost:5173. API calls to /api/* are proxied to the backend during development (see frontend/vite.config.js).
| Script | Description |
|---|---|
npm run dev |
Start the API with nodemon |
npm start |
Start the API with plain node |
npm run migrate |
Run pending Sequelize migrations |
npm run migrate:undo |
Roll back the last migration |
npm run seed |
Run seeders |
npm test |
Run the API tests (needs the test database, see Tests) |
npm run test:setup |
Run migrations on the test database |
| Script | Description |
|---|---|
npm run dev |
Start the Vite dev server |
npm run build |
Production build to frontend/dist |
npm run preview |
Preview the production build locally |
npm test |
Run the unit tests once (Vitest) |
npm run test:watch |
Re-run the unit tests on change |
Both apps run in CI on every push and pull request (.github/workflows/node.js.yml).
Frontend (Vitest + jsdom, nothing else needed):
cd frontend && npm testCovers the chore store (loading and saving per user, recurrence, skipped days, undo), toasts, confirm dialogs, chore removal with undo, the auth store, the weather fallback order, and the "all chores done" celebration.
Backend (Node's built-in test runner + supertest) runs the real Express app against a real Postgres, so the migrations and their cascading deletes are exercised. It uses a separate database and refuses to run if the database name doesn't contain test, because the tests truncate tables:
docker compose exec postgres createdb -U postgres manself_test # once
export TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/manself_test # adjust the port if you changed POSTGRES_PORT
cd backend
npm run test:setup # runs the migrations on the test database
npm testCovers register/login/token checks, profile, password and email changes, account deletion (including that owned data is deleted), per-user data isolation (including chores: an account never sees another's, and a new account after the demo starts empty), demo mode and its restrictions, and the weather endpoint (with the upstream API stubbed).
manself-webapp/
├── frontend/ # Vue 3 + Vite + Tailwind + Pinia
│ ├── src/ # components, composables, stores, views
│ └── tests/ # unit tests, mirroring src/
├── backend/ # Express + Sequelize
│ ├── src/ # controllers, routes, models, migrations
│ └── tests/ # API tests (real Postgres)
├── docs/screenshots/ # images used in this README
├── docker-compose.yml # the zero-config stack
├── docker-compose.dev.yml # extra ports for developing outside Docker
└── .github/workflows/ # CI: tests on Node 20 and 22, Docker smoke test
- Fonts: Fredoka for the wordmark (SIL Open Font License 1.1, see
frontend/src/assets/fonts/fredoka-OFL.txt) and Permanent Marker by Font Diner for the sticky notes (Apache License 2.0, seefrontend/src/assets/fonts/permanent-marker-APACHE-2.0.txt). Both are self-hosted. - Icons: Lucide (ISC). Animated weather icons: Skycons (MIT).
- Weather data: Open-Meteo (CC BY 4.0, used when no OpenWeather key is set) and OpenWeather (when one is).
MIT © 2026 Mehmet Berk Yüksel




0 comments
log in to comment.