Costs rendered as bare numbers because the project had no currency to render them with. Add one to the profile, beside the existing appearance preferences: - users.currency in PocketBase, exposed via /api/me, validated against the same 28 codes in the schema, the API and the web app. - Settings offers the European currencies plus the non-European ones the panel already had. Labels come from Intl.DisplayNames rather than a hand-kept table, so the lists read in the user's own language and sort by what is actually on screen. The single "Language & region" picker becomes two controls over the one stored BCP-47 tag, covering European languages and countries, so the two can be mixed (English in Poland). The API now enforces the language-REGION shape: the web app feeds the tag straight to Intl, which throws on a malformed one rather than falling back. formatKm and the km-count labels passed no locale, so kilometres followed the browser while the dates and costs beside them followed the saved preference. They now share one helper that uses the preference. Rename the "Maintenance log" tab to "Maintenance", the name the reminder-type list already used. Amounts are display-only: nothing is converted, so changing currency reinterprets existing records rather than recalculating them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DriverVault — Web App
Maintenance tracker for your cars: a Vue 3 + Vite + Tailwind CSS v4 SPA served
by a small Go backend-for-frontend (BFF). The BFF serves the built SPA and
reverse-proxies /api/* to the API Server, so the browser is always same-origin
and all data access still flows through the API Server (never PocketBase directly).
Browser ─► Web App BFF (:8090) ──/api/*──► API Server (:8080) ─► PocketBase
└── serves embedded Vue SPA
Layout
server/ Go BFF: embeds web/dist, proxies /api -> API_BASE
main.go
.env.example
dist/ built SPA (generated; embedded at compile time)
web/ Vue 3 + Vite + Tailwind v4 source
src/
main.js app bootstrap
router.js /login, / (dashboard), /cars/:id, /settings, /admin
api.js the only place that calls the API Server (base URL resolution)
auth.js session/profile state, isAdmin
prefs.js theme/locale/date/font preferences -> <html>
lib/format.js date/km formatting + next-service status badges
style.css Tailwind v4 entry (+ dark custom-variant)
App.vue layout shell + nav (Admin link when admin)
components/ Modal, CarFormModal, ServiceFormModal, PartFormModal, ShareModal, Logo
views/ Login, Dashboard, CarDetail, Settings, AdminUsers
Requirements
- Node 18+ and Go 1.26+
- A running API Server (see
../API Server)
Develop
Two terminals:
# terminal 1 — API Server (see ../API Server/README.md)
cd "../API Server"; ./api-server.exe
# terminal 2 — Vite dev server with hot reload (proxies /api -> :8080)
cd web; npm install; npm run dev # http://localhost:5173
The dev server proxies /api/* to the API Server (default http://localhost:8080,
override with VITE_API_TARGET), so the client uses same-origin relative URLs and
avoids CORS. It also listens on all interfaces (host: true) so it's reachable on
the LAN (e.g. http://10.2.1.101:5173).
At runtime, users can override the API base URL from the login screen's Server
settings (persisted in localStorage as cc_server_url); resolution order is
that override → VITE_API_BASE → /api.
Build & run (production-style)
./server/Run-WebApp.ps1 # builds frontend, then serves on :8090
# or manually:
cd web; npm run build # outputs to ../server/dist
cd ../server; go run . # http://localhost:8090
Config (server/.env, copy from .env.example):
| Variable | Purpose | Default |
|---|---|---|
WEB_ADDR |
Listen address | :8090 |
API_BASE |
API Server base URL | http://localhost:8080 |
Features
- Dashboard — one card per car: last service, odometer, next-due date/km, and a status badge (OK / due soon ≤30d / overdue) from the Excel formulas. Add a car; shared cars are labelled and gated by your access level.
- Car detail — full service history (date, km, computed next date/km, and the changed-parts flags) plus the per-car parts catalog and all car spec fields (engine / transmission / differential oil, brake fluid, coolant, VIN, …). Add/edit/delete service records, parts, and the car; share the car with other users (read/write, owner only). Edit/delete controls are hidden for read-only shares.
- Settings — account (name / email verification / password), appearance (theme light/dark/system, locale, date format, font size), profile (avatar, bio), data export/import, active sessions with remote logout, and the account-deletion state machine.
- Admin —
/adminuser management (list / create / role / reset password / delete), gated by the admin role via a router guard + nav link. - Theming — light/dark/system app-wide (Tailwind v4 class strategy);
prefs.jstoggles.darkon<html>and applies the saved theme/locale/date/font.
Auth & access
Login gets a JWT from the API Server (stored client-side) and creates a server
session. auth.js exposes isAdmin and the current profile; the router guards
public / admin routes. Cars are per-user (owned + shared), and the UI mirrors
the server's read / write / owner access levels.