Client booking portal, separate chauffeur portal and security fixes (v1.95.0 to v1.126.1) #8

Merged
sb merged 172 commits from ClientBookingForm into main 2026-10-09 19:44:56 +11:00
Owner

Summary

Adds a client booking portal: a separate public app (clientportal/, port 3001, for book.alwaysclassiccars.com.au) where clients book weddings, general events and transfers themselves, built from the "ACC Booking Prototype" design. It talks to the main system only through a new key protected /api/portal/* API. Plan: plans/ClientBookingForm.md. Office guide: docs/client-portal.md.

Versions v1.95.0 to v1.126.1 (see CHANGELOG.md). The client portal shares the main system's version number (a unit test keeps both package.json files and both lock files in step).

Office app and chauffeur portal split (v1.116.0)

The office app and the chauffeur portal are now separate sites with separate logins, so the same email can have an office account and a chauffeur account. Details: docs/authentication.md.

  • One build, two processes: acc-admin (ACC_APP=admin, port 3000, admin.alwaysclassiccars.com.au) and acc-chauffeur (ACC_APP=chauffeur, port 3002, portal.alwaysclassiccars.com.au). The client portal stays on 3001 and still calls port 3000.
  • The app comes from the process env, never the hostname. Each process only serves its own routes; the other app's routes return 404 whether or not anyone is signed in. The office app still answers offer links and iCal feeds sent before the split.
  • Separate sessions: own cookie names (acc-admin-session, acc-chauffeur-session, plus CSRF and callback cookies), encryption keyed to the cookie name, and a realm claim in every token that ends the session if it doesn't match the app. Everyone signs in once after updating.
  • Same email, two accounts: User.email is unique per role (index User_email_role_key) instead of on its own; no rows change. Sign in, forgot password, the Admin accounts and Portal access duplicate checks, the Reports money setting and prisma/create-user.ts look accounts up by email and role. Reset and setup links only work on their own app.
  • Chauffeur links: offer and setup links use the new CHAUFFEUR_APP_URL; APP_URL stays the office app's address.
  • Behaviour change: API calls with no session get a 401 JSON reply instead of a redirect to /login.

Follow ups (v1.116.1 to v1.116.8)

  • Sign in screens say which site they are: "Office sign in" or "Chauffeur sign in" as the heading, the panel label ("Office sign in" / "Chauffeur portal", also on Forgot and Reset password) and the tab title. The old "The office and chauffeurs both sign in here" line is gone. (v1.116.2 added a line under the heading, a link to the other site and a line on failed sign in; v1.116.3 to v1.116.5 took those back out.)
  • Chauffeur portal sidebar styled like the admin sidebar (v1.116.7): logo and company name header, same width, burgundy marker on the current page, admin style offer count, ivory account card with DC licence status and sign out, legal name and version. Always expanded; phones keep the bottom tab bar. Logo, company name and avatar are shared with AdminSidebar.
  • Fixed: chauffeurs with a working login shown as "Invite expired" (v1.116.6): the v1.81.0 passwordSetAt backfill never ran on servers updated with db push. update.sh now runs it after db push (only fills empty values; unfinished invites keep their link and stay "Invite expired"). A chauffeur changing their own password and prisma/create-user.ts now record passwordSetAt too.
  • Fixed: sign in photo never showed (v1.116.8, broken since v1.113.8): Next 16 refuses to optimize local image URLs with a query string ("url" parameter is not allowed), and the photo is loaded as /api/login-hero?v=…. It is now loaded directly (unoptimized); it is already a resized WebP.
  • Docs: Cloudflare tunnel setup in docs/deployment.md section 12 (v1.116.1), including a check that the admin and portal routes aren't swapped.

Security and chauffeur fixes (v1.117.0)

  • A new password signs the account out everywhere: each session remembers when its password was set and every request checks it, so a reset, a chauffeur's own change or the office setting a password ends all of that account's sessions; deleting an account ends them too. The proxy now uses the full auth config (Prisma) to do this.
  • Current password needed to change it in My Profile; the chauffeur is asked to sign in again afterwards.
  • Fixed: Change Password in My Profile never worked: sessions didn't carry the account id. They do now.
  • Fixed: chauffeur job sheet links: job change, "notify chauffeurs" and the offer's Job Sheet button now link the portal's /api/chauffeur/booking-sheet/<booking> from CHAUFFEUR_APP_URL (the offer's is filled in when sent). Since the split they went to the office app.
  • Fixed: Verify button in My Profile: now uses a portal route for the signed in chauffeur's typed DC number.
  • Sign in returns you where you were going (callbackUrl, same site paths only); background requests without a session still get a 401.
  • Activity page by role: new nullable AuditLog.userRole records whether an office or chauffeur account acted; older entries fall back to email matching.

Chauffeur emails, portal fleet, schedule and fixes (v1.118.0 to v1.120.0)

  • Job change emails show what changed (v1.118.0): the Job Details Updated email to chauffeurs has a WHAT CHANGED table (Detail / Was / Now). Each chauffeur sees only their job sheet's details: the booking's and their own car's, never another car's or costs; a chauffeur on two cars sees each row named by its car. Default subject Job details updated - [DATE] - [CLIENT NAME]; new [CHANGES] and [CLIENT NAME] placeholders. A customised template sends without the table until [CHANGES] is added or it is reset.
  • Choose a car shows rego and CPV registration (v1.119.0, v1.119.1): fleet cards show the rego and, when the last CPV check came back active, "✓ Active on CPV register · Last checked today", linking to the Safe Transport Victoria public register. The portal fleet API sends the rego, a yes/no and the check date, not the raw status.
  • Choose a car shows three cars to a row (v1.119.2): two under 760px, one on phones.
  • Schedule columns fit their text and line up across dates (v1.119.3): Car, Chauffeur, Start, Finish and Hours size to their longest text on the page (measured across every date's table); Pickup → destination takes the rest of the width.
  • Fixed: Activity Log "Client link: Sent to" with no address (v1.119.4): it logged the booking's email, empty on an office-entered wedding. It now logs the address the link was sent to.
  • Fixed: stop type couldn't be changed on Edit Booking (v1.119.7): each stop's fields were drawn twice (desktop row and hidden phone list) under one form name, and the form read the hidden copy, so changes on a computer (type, address, time) were ignored. They are now drawn once.
  • Fixed: generated PDFs only showed after a reload (v1.119.7): Generate PDFs on Edit Booking now waits for the PDFs and shows the Client PDF and Chauffeur PDF links straight away (POST /api/bookings/[id]/pdfs returns them).
  • Adding a booking stays on it (v1.119.7): Add booking on New Booking opens the new booking's Edit Booking page instead of going back to the schedule.
  • Clients can change cars and hours on a Hold booking (v1.120.0): through the update link, until they sign: add and remove cars and wedding night transfers, hours down to the 3 hour minimum. Not a car with a chauffeur offer sent or accepted; at least one car; added cars must be free that day. Added cars priced from the booking's price list; deposit ($100 a hire) and holiday surcharge follow the car count unless set by hand. After Hold, cars lock and hours only go up, as before. Rules in src/lib/hold-car-changes.ts.

Test pass fixes, user manual and Send offer on Edit Booking (v1.120.1 to v1.122.2)

From a full browser test of the office app, chauffeur portal and client portal.

  • A bad id in a link is a 404, not a server error (v1.120.1): a non-numeric id (/bookings/abc, /api/cars/abc, an attachment id) reached Prisma as NaN and failed with a 500. Every [id] page and route now reads its id through parseId() (src/lib/route-params.ts).
  • Schedule handles a bad month (v1.120.1): ?month=garbage built an Invalid Date (500) and ?month=13 showed the next January; an invalid month or year now shows the current one.
  • Client portal: contact on the day is optional (v1.121.1): v1.120.1 had made it required to match its * and "All optional except the contact" wording; it is meant to be optional, so the check is gone and the wording no longer marks it required.
  • Booking form shows every error on the first try (v1.120.2): an unpicked car sent NaN, a type error ("expected number, received NaN") that also stopped the Email and Phone rules running until the other errors were fixed. It now reads "Car is required" and all errors show together.
  • Section tabs follow the part of Edit Booking you're reading (v1.120.2); Print opens the month you're viewing (v1.120.2); Chauffeur field full width except on weddings (v1.120.2).
  • Chauffeur portal (v1.120.2): a blank route shows "No route yet" instead of an empty box; Save Profile and Update Password use the portal's burgundy instead of a leftover blue.
  • Client portal review shows your details (v1.120.2): name, phone and email head the review and confirmation summaries.
  • Notes hint (v1.120.3): now says Notes show on the chauffeur's job too.
  • User manual with screenshots (v1.120.4): docs/user-manual/ has an office guide, a chauffeur guide and a client guide, with screenshots of demo bookings. Linked from docs/README.md. The screenshots show the car photos from the company website (v1.120.5). Adding a booking ends with sending the client link (v1.120.6). The chauffeur guide shows the emails chauffeurs get, with an "Emails you will get" table (v1.121.2).
  • Portal access email links to the chauffeur guide (v1.121.3): new [GUIDE LINK] placeholder in the Portal Welcome template, filled by Create login and Resend setup link. A customised copy of the template needs Reset to default to pick it up.
  • Office guide shows the emails the system sends (v1.121.4): offer accepted, car on hold, booking link and hold reminder inline, plus an Emails section with what the office and clients receive (new online booking, client updated booking, weekly report, client confirmation).
  • Client guide shows the emails clients get (v1.121.5): booking confirmation, car on hold, hold reminder and check over emails, a new "If the office booked for you" section and an "Emails you will get" table.
  • Send offer on Edit Booking (v1.122.0): a Send offer to [chauffeur] button under Chauffeur status on each saved car in Planning, using the schedule's dialog, route and email builder (moved to src/lib/offer-draft.ts). It waits for unsaved changes to be saved, and shows Offer sent after sending without dirtying the form. The office guide has a screenshot of it (v1.122.1).
  • No colour boxes on the schedule (v1.122.2): the car colour swatch at the start of each schedule row looked like an empty checkbox, so it is removed. Car availability keeps its swatches beside the colour filters.
  • User manual in each app (v1.121.0): served as HTML at /help/, linked as Help in the office and chauffeur sidebars (and My Profile on phones) and How to book online in the client portal footer. Each app serves only its own guide (the office app serves all of them), without signing in. Built from docs/user-manual by scripts/build-user-manual.mjs (new marked dev dependency) into the committed public/help/ and clientportal/public/help/; a unit test fails if the HTML is stale.

Security hardening (v1.123.0 to v1.124.10)

  • Browser security headers (v1.123.0)
  • Sign-in limits (v1.124.0) and sign-in timing doesn't reveal accounts (v1.124.9)
  • Tracking links only for the job's day (v1.124.1)
  • Permission changes apply straight away (v1.124.2)
  • Signature links can't reach the server's network (v1.124.3)
  • Overtime only on the day, and only once (v1.124.4)
  • Customer details and secrets kept out of logs (v1.124.5)
  • Other sites can't make changes through a staff browser (v1.124.6)
  • Docker runs as an unprivileged user on local ports, and the Docker image builds again (v1.124.7)
  • Job photo uploads take photos only (v1.124.8)
  • Client links expire on the day, a client's signature stays as signed, reset and setup links stored hashed, booking sheets once the job is offered (v1.124.10)

See CHANGELOG.md for the details of each.

User manual screenshots retaken, and "booking folder" wording, client confirmation, chauffeur guide additions, test pass fixes (v1.124.11 to v1.124.18)

  • Every office, chauffeur and client screenshot in the user manual is retaken on the current version: the office screens show the Help link, the schedule has no colour boxes, and the client portal footer shows How to book online. The HTML manual is rebuilt.
  • "Booking folder" instead of OneDrive (v1.124.12): the Attachments panel, its remove confirmation, the missing file chip and the attachment setup and error messages say "booking folder" rather than OneDrive, and the user manual matches. Storage is unchanged.
  • Client confirmation shows the deposit, not the total (v1.124.13): the client's booking confirmation email drops the booking total and balance amount (travel fees may still be added) and says "The final balance is due by" the date. A customised copy of the template needs Reset to default.
  • Chauffeur guide: photos, car locations and the home screen (v1.124.14): new Uploading photos and Where the other cars are sections (with screenshots, including the Traccar map), and home screen steps for iPhone and Android.
  • Overtime button stays put when the photos list opens (v1.124.15): the chauffeur job's action row is top-aligned, so opening the uploaded photos list no longer pulls the Overtime button down.
  • Car location button only shows when the map can be made (v1.124.16): the button needs two tracked cars on the booking, the same rule as the map link (shared trackedDeviceIds()), instead of showing and then failing when the chauffeur's own car isn't tracked.
  • Passwords never go in the address bar (v1.124.17, security): the sign-in, forgot and reset password forms now post, so a submit before the page's JavaScript loads can't put the password in the URL, history or proxy logs.
  • Adding a booking makes its PDFs (v1.124.17): bookings added in the office with any status but Hold now get their client and chauffeur sheets straight away, as on save.
  • Production build is a fifth of the size (v1.124.18): runtime file paths are marked /*turbopackIgnore: true*/, so .next/standalone drops from 362 MB to 62 MB and no longer carries docs/, clientportal/ or the src/lib sources.
  • Help pages served in production (v1.124.18): update.sh, install.sh and setup-portal.sh copied public into the existing standalone/public as public/public, so /help/ and the TinyMCE files gave 404. They now copy the folder's contents.
  • Signing in when already signed in goes to the app (v1.124.18): /login with a session redirects to the callbackUrl, the schedule or the chauffeur home instead of showing the form inside the app layout.
  • Deposit on the thank you page after signing a held booking (v1.124.18): the client portal shows the deposit, bank details and reference, as the confirmation email does.

Security audit fixes, setup docs and the v1.93.2 update guide (v1.124.19 to v1.124.22)

From a full security audit of the branch (no Critical or High findings, no way for one client to reach another's booking). The Medium and Low findings are fixed:

  • Signatures must be signature sized (v1.124.19): a portal, client link or overtime signature must be a real PNG of at most 2000 by 1000 pixels, or it is refused (400) before anything is saved. An 8 KB PNG claiming 8000 by 8000 pixels used about 590 MB when the PDFs were made, enough to stop the office app. Making PDFs also skips any PNG over 16 megapixels, whatever its source.
  • Clients attach PDFs and photos only (v1.124.19): anyone can make a booking and get a link, and client files sync to the office's PCs, so Word, Excel, text and CSV files (macros, remote templates, formulas) are no longer taken from the public. The office can still attach them.
  • Reset links use the configured address (v1.124.19): in production a forgot password email is built only from APP_URL / CHAUFFEUR_APP_URL, never from a forgeable Host or X-Forwarded-Host. Unset, nothing is sent and the failure is logged; the answer is the same either way.
  • Failed sign-ins can't lock out an account's owner (v1.124.19): the 10 failure limit is per account from each IP; the 30 per IP limit across accounts stays.
  • CLIENT_IP_HEADER (v1.124.19): names the one header the proxy always sets (cf-connecting-ip behind a Cloudflare tunnel), in all three apps, so rate limits can't be dodged with a forged X-Real-IP or X-Forwarded-For. Unset, behaviour is unchanged.
  • Turnstile required in production (v1.124.19): the portal refuses new bookings while TURNSTILE_SECRET_KEY is unset; development still skips it. setup-portal.sh no longer offers to skip the keys (v1.124.20).
  • Terms cleaned for the portal, signatures not kept by caches (Cache-Control: private) and sharp 0.35.5 in the client portal (v1.124.19).
  • Example settings and docs (v1.124.20, v1.124.22): example.env and clientportal/example.env list every setting the apps read; the authentication, deployment, environment, client portal and OneDrive docs match the fixes.
  • Guide to updating from v1.93.2 (v1.124.21): docs/upgrade-from-1.93.md, every step in order, with the dev server record.

Test pass fixes (v1.124.23)

  • Dates can be typed as digits: the date fields read 21112026 or 211126 as DDMMYYYY or DDMMYY (a phone's number pad has no slash), and a date that can't be used shows a message under the field instead of being cleared silently. Office app and client portal.
  • Thank you page says Received: the client portal header after a new booking said CONFIRMED; it now says RECEIVED, as the page does. Client guide screenshot and wording updated.
  • Pay counts a job as Done once it has finished: My Pay no longer counts the whole of today's jobs as Done from midnight.

Job videos (v1.125.0)

  • Chauffeurs can upload videos from a job: MP4, MOV or WebM up to 90 MB each, checked by content; photos stay at 20 MB, and the 300 per booking counts both. The phone refuses an oversized video before sending it and the button counts through several files. Buttons read Add photos or videos and Photos & videos.
  • 95 MB requests: proxyClientMaxBodySize goes from 25 MB to 95 MB (each route keeps its own limit) and the chauffeur portal nginx example allows 95 MB, under the Cloudflare tunnel's 100 MB per request. Production nginx in front of the chauffeur portal needs client_max_body_size 95m.

Test pass fixes (v1.125.1)

  • Offer links keep their thank you: answering no longer refreshes the page into "You have already declined this offer".
  • Offer pages stand on their own: no signed in sidebar around them (the proxy passes the path to the layout), and the tab reads Contract Offer.
  • Edit Booking top row fits at 1280 px: two fields per line from 1280 to 1339 px.

Chauffeur portal privacy (v1.125.2)

  • No client emails on chauffeur jobs: the People list keeps Message and Call only, and the page no longer reads the email addresses, so none reach the phone. Chauffeur guide screenshot and text updated.

Office signatures per admin (v1.126.0)

  • Linked to admin accounts: an Admin account picker on each office signature (one signature per account). New optional OfficeSignature.userId, migration 20261009120000_office_signature_user. Production needs db push for the new column.
  • Added at the deposit: when a payment on the booking form reaches the deposit (or pays it off) and Office signature is empty, the signed in admin's signature fills it in, visible before saving. A chosen signature is kept; opening an already paid booking changes nothing.

Test pass fix (v1.126.1)

  • New payment amounts start blank: Add payment and Add refund rows started at 0, so clicking into the right aligned box and typing 100 could give 1000. The amount now starts empty and shows the 0.00 hint.

Main system

  • Stops per car (BookingCarStop): on the Booking form, both PDFs, chauffeur portal, schedule, Activity Log and chauffeur change emails. Existing bookings are unchanged; a save that doesn't send stops leaves them alone.
  • Fleet: car photo upload (resized WebP under CAR_PHOTOS_PATH), passenger count, "Show in client booking portal".
  • Settings, Company Info: Legal Name, contact details (phone, email, address) and bank details.
  • Settings, Client Portal (new tab): booking type visibility with direct links, header text, footer text, and header/card images.
  • Portal API: fleet, availability, car photos, settings, create booking (idempotent), and client link view/update. Portal bookings come in with a blank Source for the office to fill in.
  • Client link: "Send to client" / "Reset link" on Edit Booking. The client can complete, sign and later update a booking; date and cars are locked and hours can't go down. Changes are logged as "Client portal", regenerate PDFs, email the office a diff and follow the chauffeur notify setting. "Who you are" opens as the main contact. The online booking confirmation counts as sending the link, so the Client Link panel shows it as sent. "Send to client" sends a Car on Hold email for Hold bookings (7 day hold, complete and sign) and a check-over email otherwise; signing a held booking sends the client confirmation with the payment details.
  • Emails: six new editable templates (client confirmation, office notification, link email, car on hold link email, hold reminder, client update notification).
  • Client confirmation email design: a Next step block with the deposit, reference, bank details grid, total, balance and due date; the summary as a timeline of pickup and stops with the ceremony start on the Ceremony stop; an Outlook-safe "Update my booking" button; the company phone in the footer. New placeholders [TOTAL], [BALANCE], [BALANCE DUE DATE] and [COMPANY PHONE]. The office notification has its own layout: summary header, deposit/reference/total boxes, a To do list (deposit to watch for, check over the booking form and pricing), client email and phone links, each car with its chauffeur status and a stop table, and an "Open booking" button.
  • Main Contact: a dropdown in a wedding's Booking section on Edit Booking: Automatic (who booked) or a picked Partner 1, Partner 2, planner or other contact, with the email and phone the link emails go to. New mainContactRole column. Worked out from Signed By, then the booking's email, else the first named of planner, Partner 1, Partner 2, other; the update link uses the same check. On an office-entered wedding (no booking email) it uses the contact's own email and phone, so "Send to client" and hold reminders go to them.
  • Hold Until: a date next to Filled Out Date on Hold bookings (default 7 days from today), used by the Car on Hold email. New Booking.holdUntil column.
  • Hold reminders: a daily job (/api/cron/hold-reminders, CRON_SECRET, needs a crontab entry) emails the client 2 days before a hold ends. New holdSentAt and holdReminderSentAt columns.
  • Hold weddings: a wedding on Hold saves with just one contact (a partner, the planner or the other contact, with a name and an email or phone); the full rules apply once it leaves Hold.
  • Settings layout redesign (v1.96.0): grouped left-hand settings menu, an overview page with search (the menu on phones), shared page header, sections and fields, and a sticky save bar with Discard on form pages. Per-page updates, including drag-to-reorder for booking types and sources, email templates grouped by recipient, and one save bar for the client portal texts. Layout and presentation only: no API, schema or save logic changes.
  • Chauffeur and office emails (v1.96.2): the contract offer, welcome, password reset, offer accepted/declined, job updated, client updated booking and weekly report emails use the shared card layout (new src/lib/email-layout.ts), with job facts, notes and changes as tables and panels, and Outlook-safe buttons. New placeholders [COMPANY PHONE], [JOB SUMMARY], [BOOKING LINK], [BOOKING TYPE], [WEDDINGS COUNT].
  • Portal special instructions (v1.96.3): the same rich text editor as Edit Booking; client HTML is cleaned with sanitize-html before saving (safe formatting only). The portal gains a public/ folder for TinyMCE, copied into the build by the scripts and Dockerfile.
  • Job folders (v1.96.5): Booking.jobFolder records each booking's OneDrive folder so it follows date/name changes, Postponed/ and Cancelled/, and back (fixes postponed folders left behind). Groundwork for booking attachments (plans/BookingAttachments.md).
  • Booking attachments (v1.97.0): attach documents (e.g. a run sheet) under Documents on Edit Booking, stored in the booking's OneDrive job folder (Attachments/), checked by type and content, 20 MB each; per-file Share with chauffeur, shown on the job in the chauffeur portal. New BookingAttachment table; proxy body limit raised to 25 MB.
  • Client documents (v1.97.1): clients attach documents (run sheets etc.) on their update link's Details step, 10 MB each and 10 per booking; saved to the job folder, marked From client, office emailed and Activity Log. Portal routes rate limited and size capped.
  • Times (v1.97.2): all booking times stored as 24h HH:MM and shown 12h; diffs compare by meaning (no false "changed" times or blank rows); readable change labels; one-off prisma/normalize-times.ts to fix saved 12h times (run by hand, dry run first). Also: hyphens instead of long dashes in emails, pages and PDFs, and the office email footer names the company.
  • Chauffeur Offer reset: Reset to default now clears the saved offer, so it shows Default again.
  • Office email header: office emails are headed with the company name from Company Info instead of "ACC SYSTEM".
  • Time pickers on Edit Booking: every time field on the booking form uses the same time picker as the client portal.
  • Adding a car: after saving, a new car takes its saved id, so "Unsaved changes" clears and later saves no longer recreate the car.
  • Chauffeur portal redesign: phone first, with a tab bar on phones and a sidebar on wider screens. Today shows the next job with a countdown, route, Navigate and Call. My Jobs puts offers first with the estimated pay. Each job has a detail page. Overtime is recorded per job in three steps, availability is set by tapping days, and pay shows the month by week and per job.
  • Offer cards: a View job details button above Decline and Accept job; portal headings use Source Serif 4 (plain J).
  • People on a job: every contact on the booking, the main contact falling back to the booking's phone and email, plus contact on the day, photographer and videographer, with Email, Message and Call buttons.
  • Job page details: Ceremony & reception (addresses with Navigate, times, drink type) and the special instructions, cleaned to plain formatting.
  • Today: no month pay card (pay stays on My Pay); the next job uses the full width when there's nothing for the side column.
  • Date fields: our own day-first date field (DD/MM/YYYY, typed or picked from a Monday-first calendar) replaces the browser's date picker in both apps, which showed month first on US-English browsers.
  • Hold reminders: sent on the first daily run in the last 2 days of a hold (not only the exact day), so a missed run or a short hold still gets one; the email says "in 2 days", "tomorrow" or "today".
  • Hold reminder heading: "Hi [name], your car is only on hold until [day]."
  • Hold reminder record: "Hold reminder sent" on the Client Link panel and a System entry in the booking's Activity log.
  • Weekly report and hold reminder run logging: both cron endpoints now stamp every run with when it ran, and a run-level failure includes the real error message instead of a generic one. A successful weekly report send, a run-level failure, and a hold reminder that fails for one booking are all recorded to the Activity Log, not just the server console.
  • Activity log cars and payments: added/removed cars logged by name and pickup time and matched by id (not list position); no more "Payments: - → []" on every save.
  • Client Link history: Sent, Hold reminder sent, Completed and Last updated each on their own line.
  • Email template Save: only active once the subject or body changes, so saving an unchanged template (for example right after Reset to default) no longer marks it Customised.
  • Email template reset: Reset to default restarts the editor so it stays Default with nothing unsaved; a company name starting with "Always Classic Cars" no longer doubles in saved templates.
  • Expired reset link: "Back to sign in" alongside "Request a new link".
  • Reports redesign: revenue, received, owing and after chauffeur pay against last year; booking pace; a Needs attention list for the next 60 days; fleet and chauffeur tables; busiest dates, day of the week and lead time; CSV export and print.
  • Reports without money figures: limited admins without the new Show money figures option (under Reports in Settings → Admin accounts) get a version with no dollar amounts, built on the server so the money never reaches their browser, and a CSV without totals or payments.
  • Reports version per account: each admin account has its own Reports choice (with or without money figures) in Settings → Admin accounts, whatever its access; new accounts start without money, existing ones keep what they saw. Adds User.reportsMoney (nullable, safe with db push). Also Reports wording: "1 car", lead time in days/weeks/months, plain hyphens.
  • Lead time bars: 12-24 months, 24-36 months and Over 36 months replace Over 12 months.
  • Delete chauffeur: only for a chauffeur never given a job (deletes their availability and portal login too); otherwise disabled with a note to untick Active. The API refuses instead of quietly deactivating.
  • Duplicate chauffeurs blocked on create: adding a chauffeur with the same name as an existing one (active or inactive) is now rejected with an error instead of silently creating a second record.
  • Unavailable cars on the schedule: a date with a car marked unavailable (Fleet → car availability) shows an "unavailable" badge on that date's header, whether or not the car is actually booked that day; hovering shows the time range (or "All day") and the reason.
  • Branded chauffeur availability: /chauffeurs/availability matches the chauffeur portal's burgundy/gold/ivory branding, with month/week/day views showing accepted jobs and sent offers (not just unavailability) per day, a Saturday coverage strip, a 6am-midnight day timeline, same-day conflict flags, and a right-side edit drawer (bottom sheet on mobile) replacing the old modal.
  • Branded chauffeurs list and profile: /chauffeurs and /chauffeurs/[id] get the same branding, with a search box, an Active/Needs attention/Inactive/All filter, a "needs attention" panel for DC-licence and portal-login issues, and Next job/Jobs this month columns; the profile page adds a header card with status chips, summary tiles, an Upcoming jobs card and a Time off card. Same CPVV refresh, permissions, portal flows and delete rule throughout.
  • Copy a car's journey (client portal): "copy journey to another car" adds the picked car with the same pickup, times, stops, hours and who it's for; the copy is then edited on its own.
  • Who you are (portal): picking Wedding planner or Other takes the booker's details back off Partner 1.
  • Special instructions in change lists: shown as plain text in the client update email and Activity log, not the stored HTML document.
  • Legal name: used at the bottom of PDFs, the sidebar (with version underneath) and the portal footer.
  • PDF header: address, phone and email from Company Info (unchanged until filled in).
  • Edit Booking page layout: redesigned from the Proposed artboard. Header with client names, chips, date and the save/cancel/postpone/activity actions (sticky on large screens) plus section links; Booking, Couple or Client, one Contacts section, Ceremony & Reception combined, collapsible car cards with a summary line and grouped fields, shorter instructions editor, Signature, Delete at the end; sticky right column with Payment (balance due, paid progress), Chauffeur Pay, Documents and the Client Link panel. The New Booking page uses the same layout. Fields, validation and saving are unchanged; errors inside a collapsed car open it.
  • Fixed: the booking form counted as changed on load (fields starting undefined picked up "" from the page), so leaving warned and Cancel asked to discard even with no edits.
  • Chauffeur pay hidden on screen by default: the Chauffeur Pay card and the pay rate fields start hidden so clients in the office can't see them; Show/Hide is remembered per browser. Display only.

Client portal (clientportal/)

  • Five step wizard with fleet picker and availability, stops, night transfers, airport transfers with suggested pickup times, drawn signature, thank you page with deposit and bank details.
  • Direct links /wedding, /general, /transfer (work even when a type is hidden from the main page); edit mode at /b/<token>.
  • Footer, contact details, header text and images come from Settings.
  • Weddings show the couple's names (from "Who you are") on each car's chips, the ceremony arrival times and the summaries, instead of Partner 1 and Partner 2.
  • Per IP rate limits, body size limit, Cloudflare Turnstile (required in production from v1.124.19).

Deployment

  • v1.118.0 to v1.120.0: no database changes. Deploy the office app and the client portal together (the portal's fleet cards read the rego and CPV fields from the office app's fleet API).
  • v1.117.0: db push adds AuditLog.userRole (no data changes). Everyone signs in once more (sessions now carry a password stamp).
  • Account status backfill (v1.116.6): update.sh marks accounts with no outstanding setup or reset link as having set a password, on every run. Safe to repeat.
  • Office app and chauffeur portal (v1.116.0): update.sh replaces the acc-system PM2 app with acc-admin and acc-chauffeur, built from the existing entry so custom env values carry over (old file kept as ecosystem.config.js.bak). install.sh, setup-portal.sh, restore.sh and docker-compose.yml (new chauffeur service) use the two apps. New env vars ACC_APP (per process) and CHAUFFEUR_APP_URL. npm run dev:chauffeur runs the portal in development on 3002. Upgrade steps, Nginx sites for admin.* and portal.*, and rollback are in docs/deployment.md section 15.
  • Database changes are additive only; scripts/update.sh (db push) applies them. No data migration needed.
  • update.sh rebuilds and restarts the portal only where the acc-clientportal PM2 process exists, and prints how to add it otherwise.
  • New scripts/setup-portal.sh adds the portal to an existing server: shared key, both .env files, portal build, acc-clientportal in PM2, a connection check, and (for a local Nginx) the site and certbot. It asks whether the reverse proxy runs on the same server and sets the listen address to match (127.0.0.1 or 0.0.0.0). Safe to rerun; unchanged answers don't rebuild the main system.
  • install.sh sets CAR_PHOTOS_PATH and offers the portal setup at the end.
  • The portal's PM2 app is acc-clientportal. Servers that already run it as acc-portal are switched over by update.sh automatically.
  • Docker: clientportal/Dockerfile, portal service in docker-compose.yml, root .dockerignore.
  • New env vars: main PORTAL_API_KEY, PORTAL_URL, CAR_PHOTOS_PATH (and APP_URL for links); portal MAIN_API_URL, PORTAL_API_KEY, HOSTNAME, TURNSTILE_SECRET_KEY, NEXT_PUBLIC_TURNSTILE_SITE_KEY. They can go in the PM2 ecosystem files or in .env files (rebuild after changing .env; PORT and HOSTNAME stay in the ecosystem file). See docs/environment-variables.md and docs/deployment.md.

Testing

  • v1.116.0: 530 root tests (29 new: sign in per app, route access per app, token realm checks, password reset per app) and 38 portal tests; typecheck, lint, prisma validate and both builds clean. Migrations applied from scratch match the schema exactly. End to end, both built apps were run against a copy of the dev database with a dual account user. Each app signed in its own account and rejected the other password. Admin sessions got 404 on chauffeur routes and chauffeur sessions got 404 on admin routes. Cookies moved between apps and forged wrong-app tokens were refused. /api/portal/* works on 3000 and returns 404 on 3002, and the iCal feed works on both. The PM2 split has since run on the dev server; routing problems there were the tunnel routes, not the apps. Not tested: Docker.
  • v1.116.1 to v1.116.8: 532 root tests (a new one for the chauffeur password change); typecheck and lint clean. The passwordSetAt backfill was run twice on a scratch database (fills the missing value, leaves an unfinished invite alone, reads back through Prisma). The chauffeur sidebar and the sign in pages were checked as rendered HTML on the dev servers, signed in as a test chauffeur; the sign in photo was checked against the old optimizer URL (400) and the new direct URL (200 WebP). Not viewed in a browser.
  • v1.117.0: 542 tests; typecheck, lint and migrations from scratch clean. Both built apps were run against a scratch database. Wrong current password refused; a password change ended both chauffeur "devices"; old password refused and new one accepted; the same email's office account unaffected; an office password reset elsewhere ended that office session (redirect to sign in with callbackUrl). A signed out browser opening the job sheet link went to sign in; fetch got 401. The Activity who filter was checked against real SQLite rows (dual account office edit as office, offer answer as chauffeur, old rows by email). Not checked: the offer email link substitution (reviewed only), a real CPVV result, and any of it in a browser.
  • v1.118.0 to v1.120.0: 545 root tests (new ones for the job change table: own car only, no costs, 12 hour times, car names) and 40 portal tests (CPV check age); typecheck and lint clean in both. The job change email was rendered from the default template and checked for the table. Tested OK on the test server: the stops fix (v1.119.5), generated PDFs showing straight away (v1.119.6) and adding a booking staying on it (v1.119.7). Not checked in a browser: the fleet cards and the schedule column widths. v1.120.0: 559 root tests (new hold-car-changes tests) and 43 portal tests; the real client update was run against a scratch database (add, remove and lower hours on Hold; offered car not removable; car booked elsewhere refused; 3 hour minimum; deposit $200 to $300 and a hand-set deposit kept; non-Hold adding refused and hours only up), all passing. Not checked in a browser: the portal car step on a Hold booking.
  • npm test (245) in the root and npm test (28) in clientportal/; typecheck and lint clean in both.
  • Edit Booking page: checked in a browser (sticky header and column, save keeps all cars and stops, validation opens a collapsed car, New Booking and airport transfer pages, phone width, no unsaved-changes prompt without edits, pay hidden by default and saved unchanged).
  • PDFs: text for existing bookings identical before and after with the new Company Info fields empty; filled in values appear in the header and footer.
  • End to end against a copy of the dev database: wedding (desktop) and return airport transfer (390px mobile) booked through the portal in a headless browser; client link edit and signing; locked fields and hour reduction rejected; old token dead after reset; PDFs for existing bookings identical before and after.
  • Dev server: updated with update.sh on this branch and setup-portal.sh run; acc-clientportal online and connected to the main system.
  • Not tested: Docker images (no Docker locally) and real email delivery (no SMTP locally).
  • v1.124.19 to v1.124.22: 640 root tests and 50 portal tests (new ones for signature sizes, client upload types, reset links from a forged host, per account and IP lockout, CLIENT_IP_HEADER and Turnstile in production); typecheck and lint clean in both. Rebased onto v1.124.18 with no code conflicts. The dev server was updated from v1.93.2 (on main) to v1.124.20 with update.sh: the server's own v1.93.2 script built the new code but didn't split the apps, and the branch's script then split acc-system into acc-admin and acc-chauffeur; db push only added the three unique indexes, and booking and car counts were unchanged. setup-portal.sh stopped on a blank Turnstile key without writing anything, then installed acc-clientportal with Cloudflare's test keys. All three apps answered, /api/portal/ refused requests without the key, a booking without a Turnstile token was refused, and a booking with the 8000 by 8000 signature was refused with no booking or file created and the office app at about 200 MB. Not tried there: anything that sends email (the dev database has real clients and chauffeurs). v1.124.21 and v1.124.22 are docs only.
  • v1.124.23: 641 root tests and 51 portal tests (new: dates typed as digits). Full pass of both builds, a sweep of about 120 pages and APIs, and Chrome runs of adding a booking, the client link, accepting an offer and a portal booking.
  • v1.125.0: 643 root tests (new: video types by content). Uploaded MP4, MOV, WebM and a 40 MB video through the chauffeur portal; a fake .mp4, a 92 MB video and a 21 MB photo were refused.
  • v1.125.1: 643 root tests, both builds and a sweep of 106 requests. Offer page checked signed in to each app and signed out (accept message stays, no sidebar, title); field widths measured from 1024 to 1600 px.
  • v1.125.2: 643 root tests; both of a chauffeur's job pages checked for mailto links and any email address in the page data (none).
  • v1.126.0: 643 root tests and the build. On the demo stack: linking and moving links on Office Signatures, $100 on an unpaid booking fills and saves the admin's signature (client PDF remade with it), $50 fills nothing, a chosen signature is kept, an unlinked admin gets nothing, opening a paid booking changes nothing; PATCH refuses unknown signatures (404) and non admin accounts (400).
  • v1.126.1: full test pass (643 root and 51 portal tests, tsc, lint, both builds, demo sweep) plus Chrome on the demo stack for v1.126.0. After the fix: a new payment row is empty with the placeholder, clicking and typing 100 gives 100 and saves $100, a new refund row is empty.

Before go-live

  1. For v1.116.0: back up, run the account checks in docs/deployment.md section 15, add DNS for admin.* and portal., and put APP_URL and CHAUFFEUR_APP_URL in /opt/acc-system/.env before running update.sh. Then add the two reverse proxy hosts (admin. to 3000, portal.* to 3002).
  2. DNS for book.alwaysclassiccars.com.au, Turnstile keys (required: without the secret the portal refuses new bookings), then scripts/setup-portal.sh (or the manual steps in docs/deployment.md) and the reverse proxy.
  3. Office setup per docs/client-portal.md: car photos and seats, portal images and wording, booking types, Company Info (legal name, ABN, contact and bank details), terms, email templates.
  4. Add the daily hold reminder crontab entry (CRON_SECRET in .env), see "Hold reminders" in docs/client-portal.md.
  5. Back up the database and run npx tsx prisma/normalize-times.ts (dry run), then with --apply.
  6. Switch the website's booking links from the WPForms site to the portal.
  7. Set CLIENT_IP_HEADER in all three apps for the proxy in front of them (cf-connecting-ip behind a Cloudflare tunnel), and make sure APP_URL and CHAUFFEUR_APP_URL are set, or password reset emails aren't sent.
  8. Follow docs/upgrade-from-1.93.md when updating a v1.93.2 server: fetch the latest update.sh with curl rather than running the server's old copy.
## Summary Adds a client booking portal: a separate public app (`clientportal/`, port 3001, for `book.alwaysclassiccars.com.au`) where clients book weddings, general events and transfers themselves, built from the "ACC Booking Prototype" design. It talks to the main system only through a new key protected `/api/portal/*` API. Plan: `plans/ClientBookingForm.md`. Office guide: `docs/client-portal.md`. Versions v1.95.0 to v1.126.1 (see `CHANGELOG.md`). The client portal shares the main system's version number (a unit test keeps both `package.json` files and both lock files in step). ### Office app and chauffeur portal split (v1.116.0) The office app and the chauffeur portal are now separate sites with separate logins, so the same email can have an office account and a chauffeur account. Details: `docs/authentication.md`. - **One build, two processes**: `acc-admin` (`ACC_APP=admin`, port 3000, admin.alwaysclassiccars.com.au) and `acc-chauffeur` (`ACC_APP=chauffeur`, port 3002, portal.alwaysclassiccars.com.au). The client portal stays on 3001 and still calls port 3000. - **The app comes from the process env, never the hostname.** Each process only serves its own routes; the other app's routes return 404 whether or not anyone is signed in. The office app still answers offer links and iCal feeds sent before the split. - **Separate sessions**: own cookie names (`acc-admin-session`, `acc-chauffeur-session`, plus CSRF and callback cookies), encryption keyed to the cookie name, and a `realm` claim in every token that ends the session if it doesn't match the app. Everyone signs in once after updating. - **Same email, two accounts**: `User.email` is unique per role (index `User_email_role_key`) instead of on its own; no rows change. Sign in, forgot password, the Admin accounts and Portal access duplicate checks, the Reports money setting and `prisma/create-user.ts` look accounts up by email and role. Reset and setup links only work on their own app. - **Chauffeur links**: offer and setup links use the new `CHAUFFEUR_APP_URL`; `APP_URL` stays the office app's address. - **Behaviour change**: API calls with no session get a 401 JSON reply instead of a redirect to `/login`. ### Follow ups (v1.116.1 to v1.116.8) - **Sign in screens say which site they are**: "Office sign in" or "Chauffeur sign in" as the heading, the panel label ("Office sign in" / "Chauffeur portal", also on Forgot and Reset password) and the tab title. The old "The office and chauffeurs both sign in here" line is gone. (v1.116.2 added a line under the heading, a link to the other site and a line on failed sign in; v1.116.3 to v1.116.5 took those back out.) - **Chauffeur portal sidebar styled like the admin sidebar** (v1.116.7): logo and company name header, same width, burgundy marker on the current page, admin style offer count, ivory account card with DC licence status and sign out, legal name and version. Always expanded; phones keep the bottom tab bar. Logo, company name and avatar are shared with `AdminSidebar`. - **Fixed: chauffeurs with a working login shown as "Invite expired"** (v1.116.6): the v1.81.0 `passwordSetAt` backfill never ran on servers updated with `db push`. `update.sh` now runs it after `db push` (only fills empty values; unfinished invites keep their link and stay "Invite expired"). A chauffeur changing their own password and `prisma/create-user.ts` now record `passwordSetAt` too. - **Fixed: sign in photo never showed** (v1.116.8, broken since v1.113.8): Next 16 refuses to optimize local image URLs with a query string (`"url" parameter is not allowed`), and the photo is loaded as `/api/login-hero?v=…`. It is now loaded directly (`unoptimized`); it is already a resized WebP. - **Docs**: Cloudflare tunnel setup in `docs/deployment.md` section 12 (v1.116.1), including a check that the admin and portal routes aren't swapped. ### Security and chauffeur fixes (v1.117.0) - **A new password signs the account out everywhere**: each session remembers when its password was set and every request checks it, so a reset, a chauffeur's own change or the office setting a password ends all of that account's sessions; deleting an account ends them too. The proxy now uses the full auth config (Prisma) to do this. - **Current password needed** to change it in My Profile; the chauffeur is asked to sign in again afterwards. - **Fixed: Change Password in My Profile never worked**: sessions didn't carry the account id. They do now. - **Fixed: chauffeur job sheet links**: job change, "notify chauffeurs" and the offer's Job Sheet button now link the portal's `/api/chauffeur/booking-sheet/<booking>` from `CHAUFFEUR_APP_URL` (the offer's is filled in when sent). Since the split they went to the office app. - **Fixed: Verify button in My Profile**: now uses a portal route for the signed in chauffeur's typed DC number. - **Sign in returns you where you were going** (`callbackUrl`, same site paths only); background requests without a session still get a 401. - **Activity page by role**: new nullable `AuditLog.userRole` records whether an office or chauffeur account acted; older entries fall back to email matching. ### Chauffeur emails, portal fleet, schedule and fixes (v1.118.0 to v1.120.0) - **Job change emails show what changed** (v1.118.0): the Job Details Updated email to chauffeurs has a WHAT CHANGED table (Detail / Was / Now). Each chauffeur sees only their job sheet's details: the booking's and their own car's, never another car's or costs; a chauffeur on two cars sees each row named by its car. Default subject `Job details updated - [DATE] - [CLIENT NAME]`; new `[CHANGES]` and `[CLIENT NAME]` placeholders. A customised template sends without the table until `[CHANGES]` is added or it is reset. - **Choose a car shows rego and CPV registration** (v1.119.0, v1.119.1): fleet cards show the rego and, when the last CPV check came back active, "✓ Active on CPV register · Last checked today", linking to the Safe Transport Victoria public register. The portal fleet API sends the rego, a yes/no and the check date, not the raw status. - **Choose a car shows three cars to a row** (v1.119.2): two under 760px, one on phones. - **Schedule columns fit their text and line up across dates** (v1.119.3): Car, Chauffeur, Start, Finish and Hours size to their longest text on the page (measured across every date's table); Pickup → destination takes the rest of the width. - **Fixed: Activity Log "Client link: Sent to" with no address** (v1.119.4): it logged the booking's email, empty on an office-entered wedding. It now logs the address the link was sent to. - **Fixed: stop type couldn't be changed on Edit Booking** (v1.119.7): each stop's fields were drawn twice (desktop row and hidden phone list) under one form name, and the form read the hidden copy, so changes on a computer (type, address, time) were ignored. They are now drawn once. - **Fixed: generated PDFs only showed after a reload** (v1.119.7): Generate PDFs on Edit Booking now waits for the PDFs and shows the Client PDF and Chauffeur PDF links straight away (`POST /api/bookings/[id]/pdfs` returns them). - **Adding a booking stays on it** (v1.119.7): Add booking on New Booking opens the new booking's Edit Booking page instead of going back to the schedule. - **Clients can change cars and hours on a Hold booking** (v1.120.0): through the update link, until they sign: add and remove cars and wedding night transfers, hours down to the 3 hour minimum. Not a car with a chauffeur offer sent or accepted; at least one car; added cars must be free that day. Added cars priced from the booking's price list; deposit ($100 a hire) and holiday surcharge follow the car count unless set by hand. After Hold, cars lock and hours only go up, as before. Rules in `src/lib/hold-car-changes.ts`. ### Test pass fixes, user manual and Send offer on Edit Booking (v1.120.1 to v1.122.2) From a full browser test of the office app, chauffeur portal and client portal. - **A bad id in a link is a 404, not a server error** (v1.120.1): a non-numeric id (`/bookings/abc`, `/api/cars/abc`, an attachment id) reached Prisma as `NaN` and failed with a 500. Every `[id]` page and route now reads its id through `parseId()` (`src/lib/route-params.ts`). - **Schedule handles a bad month** (v1.120.1): `?month=garbage` built an Invalid Date (500) and `?month=13` showed the next January; an invalid month or year now shows the current one. - **Client portal: contact on the day is optional** (v1.121.1): v1.120.1 had made it required to match its * and "All optional except the contact" wording; it is meant to be optional, so the check is gone and the wording no longer marks it required. - **Booking form shows every error on the first try** (v1.120.2): an unpicked car sent `NaN`, a type error ("expected number, received NaN") that also stopped the Email and Phone rules running until the other errors were fixed. It now reads "Car is required" and all errors show together. - **Section tabs follow the part of Edit Booking you're reading** (v1.120.2); **Print opens the month you're viewing** (v1.120.2); **Chauffeur field full width** except on weddings (v1.120.2). - **Chauffeur portal** (v1.120.2): a blank route shows "No route yet" instead of an empty box; Save Profile and Update Password use the portal's burgundy instead of a leftover blue. - **Client portal review shows your details** (v1.120.2): name, phone and email head the review and confirmation summaries. - **Notes hint** (v1.120.3): now says Notes show on the chauffeur's job too. - **User manual with screenshots** (v1.120.4): `docs/user-manual/` has an office guide, a chauffeur guide and a client guide, with screenshots of demo bookings. Linked from `docs/README.md`. The screenshots show the car photos from the company website (v1.120.5). Adding a booking ends with sending the client link (v1.120.6). The chauffeur guide shows the emails chauffeurs get, with an "Emails you will get" table (v1.121.2). - **Portal access email links to the chauffeur guide** (v1.121.3): new `[GUIDE LINK]` placeholder in the Portal Welcome template, filled by Create login and Resend setup link. A customised copy of the template needs Reset to default to pick it up. - **Office guide shows the emails the system sends** (v1.121.4): offer accepted, car on hold, booking link and hold reminder inline, plus an Emails section with what the office and clients receive (new online booking, client updated booking, weekly report, client confirmation). - **Client guide shows the emails clients get** (v1.121.5): booking confirmation, car on hold, hold reminder and check over emails, a new "If the office booked for you" section and an "Emails you will get" table. - **Send offer on Edit Booking** (v1.122.0): a Send offer to [chauffeur] button under Chauffeur status on each saved car in Planning, using the schedule's dialog, route and email builder (moved to `src/lib/offer-draft.ts`). It waits for unsaved changes to be saved, and shows Offer sent after sending without dirtying the form. The office guide has a screenshot of it (v1.122.1). - **No colour boxes on the schedule** (v1.122.2): the car colour swatch at the start of each schedule row looked like an empty checkbox, so it is removed. Car availability keeps its swatches beside the colour filters. - **User manual in each app** (v1.121.0): served as HTML at `/help/`, linked as **Help** in the office and chauffeur sidebars (and My Profile on phones) and **How to book online** in the client portal footer. Each app serves only its own guide (the office app serves all of them), without signing in. Built from `docs/user-manual` by `scripts/build-user-manual.mjs` (new `marked` dev dependency) into the committed `public/help/` and `clientportal/public/help/`; a unit test fails if the HTML is stale. ### Security hardening (v1.123.0 to v1.124.10) - **Browser security headers** (v1.123.0) - **Sign-in limits** (v1.124.0) and **sign-in timing doesn't reveal accounts** (v1.124.9) - **Tracking links only for the job's day** (v1.124.1) - **Permission changes apply straight away** (v1.124.2) - **Signature links can't reach the server's network** (v1.124.3) - **Overtime only on the day, and only once** (v1.124.4) - **Customer details and secrets kept out of logs** (v1.124.5) - **Other sites can't make changes through a staff browser** (v1.124.6) - **Docker runs as an unprivileged user on local ports**, and the Docker image builds again (v1.124.7) - **Job photo uploads take photos only** (v1.124.8) - **Client links expire on the day**, a client's signature stays as signed, reset and setup links stored hashed, booking sheets once the job is offered (v1.124.10) See `CHANGELOG.md` for the details of each. ### User manual screenshots retaken, and "booking folder" wording, client confirmation, chauffeur guide additions, test pass fixes (v1.124.11 to v1.124.18) - Every office, chauffeur and client screenshot in the user manual is retaken on the current version: the office screens show the Help link, the schedule has no colour boxes, and the client portal footer shows How to book online. The HTML manual is rebuilt. - **"Booking folder" instead of OneDrive** (v1.124.12): the Attachments panel, its remove confirmation, the missing file chip and the attachment setup and error messages say "booking folder" rather than OneDrive, and the user manual matches. Storage is unchanged. - **Client confirmation shows the deposit, not the total** (v1.124.13): the client's booking confirmation email drops the booking total and balance amount (travel fees may still be added) and says "The final balance is due by" the date. A customised copy of the template needs Reset to default. - **Chauffeur guide: photos, car locations and the home screen** (v1.124.14): new Uploading photos and Where the other cars are sections (with screenshots, including the Traccar map), and home screen steps for iPhone and Android. - **Overtime button stays put when the photos list opens** (v1.124.15): the chauffeur job's action row is top-aligned, so opening the uploaded photos list no longer pulls the Overtime button down. - **Car location button only shows when the map can be made** (v1.124.16): the button needs two tracked cars on the booking, the same rule as the map link (shared `trackedDeviceIds()`), instead of showing and then failing when the chauffeur's own car isn't tracked. - **Passwords never go in the address bar** (v1.124.17, security): the sign-in, forgot and reset password forms now post, so a submit before the page's JavaScript loads can't put the password in the URL, history or proxy logs. - **Adding a booking makes its PDFs** (v1.124.17): bookings added in the office with any status but Hold now get their client and chauffeur sheets straight away, as on save. - **Production build is a fifth of the size** (v1.124.18): runtime file paths are marked `/*turbopackIgnore: true*/`, so `.next/standalone` drops from 362 MB to 62 MB and no longer carries `docs/`, `clientportal/` or the `src/lib` sources. - **Help pages served in production** (v1.124.18): `update.sh`, `install.sh` and `setup-portal.sh` copied `public` into the existing `standalone/public` as `public/public`, so `/help/` and the TinyMCE files gave 404. They now copy the folder's contents. - **Signing in when already signed in goes to the app** (v1.124.18): `/login` with a session redirects to the `callbackUrl`, the schedule or the chauffeur home instead of showing the form inside the app layout. - **Deposit on the thank you page after signing a held booking** (v1.124.18): the client portal shows the deposit, bank details and reference, as the confirmation email does. ### Security audit fixes, setup docs and the v1.93.2 update guide (v1.124.19 to v1.124.22) From a full security audit of the branch (no Critical or High findings, no way for one client to reach another's booking). The Medium and Low findings are fixed: - **Signatures must be signature sized** (v1.124.19): a portal, client link or overtime signature must be a real PNG of at most 2000 by 1000 pixels, or it is refused (400) before anything is saved. An 8 KB PNG claiming 8000 by 8000 pixels used about 590 MB when the PDFs were made, enough to stop the office app. Making PDFs also skips any PNG over 16 megapixels, whatever its source. - **Clients attach PDFs and photos only** (v1.124.19): anyone can make a booking and get a link, and client files sync to the office's PCs, so Word, Excel, text and CSV files (macros, remote templates, formulas) are no longer taken from the public. The office can still attach them. - **Reset links use the configured address** (v1.124.19): in production a forgot password email is built only from `APP_URL` / `CHAUFFEUR_APP_URL`, never from a forgeable `Host` or `X-Forwarded-Host`. Unset, nothing is sent and the failure is logged; the answer is the same either way. - **Failed sign-ins can't lock out an account's owner** (v1.124.19): the 10 failure limit is per account from each IP; the 30 per IP limit across accounts stays. - **`CLIENT_IP_HEADER`** (v1.124.19): names the one header the proxy always sets (`cf-connecting-ip` behind a Cloudflare tunnel), in all three apps, so rate limits can't be dodged with a forged `X-Real-IP` or `X-Forwarded-For`. Unset, behaviour is unchanged. - **Turnstile required in production** (v1.124.19): the portal refuses new bookings while `TURNSTILE_SECRET_KEY` is unset; development still skips it. `setup-portal.sh` no longer offers to skip the keys (v1.124.20). - **Terms cleaned for the portal**, **signatures not kept by caches** (`Cache-Control: private`) and **sharp 0.35.5 in the client portal** (v1.124.19). - **Example settings and docs** (v1.124.20, v1.124.22): `example.env` and `clientportal/example.env` list every setting the apps read; the authentication, deployment, environment, client portal and OneDrive docs match the fixes. - **Guide to updating from v1.93.2** (v1.124.21): `docs/upgrade-from-1.93.md`, every step in order, with the dev server record. ### Test pass fixes (v1.124.23) - **Dates can be typed as digits**: the date fields read `21112026` or `211126` as DDMMYYYY or DDMMYY (a phone's number pad has no slash), and a date that can't be used shows a message under the field instead of being cleared silently. Office app and client portal. - **Thank you page says Received**: the client portal header after a new booking said CONFIRMED; it now says RECEIVED, as the page does. Client guide screenshot and wording updated. - **Pay counts a job as Done once it has finished**: My Pay no longer counts the whole of today's jobs as Done from midnight. ### Job videos (v1.125.0) - **Chauffeurs can upload videos from a job**: MP4, MOV or WebM up to 90 MB each, checked by content; photos stay at 20 MB, and the 300 per booking counts both. The phone refuses an oversized video before sending it and the button counts through several files. Buttons read Add photos or videos and Photos & videos. - **95 MB requests**: `proxyClientMaxBodySize` goes from 25 MB to 95 MB (each route keeps its own limit) and the chauffeur portal nginx example allows 95 MB, under the Cloudflare tunnel's 100 MB per request. **Production nginx in front of the chauffeur portal needs `client_max_body_size 95m`.** ### Test pass fixes (v1.125.1) - **Offer links keep their thank you**: answering no longer refreshes the page into "You have already declined this offer". - **Offer pages stand on their own**: no signed in sidebar around them (the proxy passes the path to the layout), and the tab reads Contract Offer. - **Edit Booking top row fits at 1280 px**: two fields per line from 1280 to 1339 px. ### Chauffeur portal privacy (v1.125.2) - **No client emails on chauffeur jobs**: the People list keeps Message and Call only, and the page no longer reads the email addresses, so none reach the phone. Chauffeur guide screenshot and text updated. ### Office signatures per admin (v1.126.0) - **Linked to admin accounts**: an Admin account picker on each office signature (one signature per account). New optional `OfficeSignature.userId`, migration `20261009120000_office_signature_user`. **Production needs `db push` for the new column.** - **Added at the deposit**: when a payment on the booking form reaches the deposit (or pays it off) and Office signature is empty, the signed in admin's signature fills it in, visible before saving. A chosen signature is kept; opening an already paid booking changes nothing. ### Test pass fix (v1.126.1) - **New payment amounts start blank**: Add payment and Add refund rows started at 0, so clicking into the right aligned box and typing 100 could give 1000. The amount now starts empty and shows the 0.00 hint. ### Main system - **Stops per car** (`BookingCarStop`): on the Booking form, both PDFs, chauffeur portal, schedule, Activity Log and chauffeur change emails. Existing bookings are unchanged; a save that doesn't send stops leaves them alone. - **Fleet**: car photo upload (resized WebP under `CAR_PHOTOS_PATH`), passenger count, "Show in client booking portal". - **Settings, Company Info**: Legal Name, contact details (phone, email, address) and bank details. - **Settings, Client Portal** (new tab): booking type visibility with direct links, header text, footer text, and header/card images. - **Portal API**: fleet, availability, car photos, settings, create booking (idempotent), and client link view/update. Portal bookings come in with a blank Source for the office to fill in. - **Client link**: "Send to client" / "Reset link" on Edit Booking. The client can complete, sign and later update a booking; date and cars are locked and hours can't go down. Changes are logged as "Client portal", regenerate PDFs, email the office a diff and follow the chauffeur notify setting. "Who you are" opens as the main contact. The online booking confirmation counts as sending the link, so the Client Link panel shows it as sent. "Send to client" sends a Car on Hold email for Hold bookings (7 day hold, complete and sign) and a check-over email otherwise; signing a held booking sends the client confirmation with the payment details. - **Emails**: six new editable templates (client confirmation, office notification, link email, car on hold link email, hold reminder, client update notification). - **Client confirmation email design**: a Next step block with the deposit, reference, bank details grid, total, balance and due date; the summary as a timeline of pickup and stops with the ceremony start on the Ceremony stop; an Outlook-safe "Update my booking" button; the company phone in the footer. New placeholders [TOTAL], [BALANCE], [BALANCE DUE DATE] and [COMPANY PHONE]. The office notification has its own layout: summary header, deposit/reference/total boxes, a To do list (deposit to watch for, check over the booking form and pricing), client email and phone links, each car with its chauffeur status and a stop table, and an "Open booking" button. - **Main Contact**: a dropdown in a wedding's Booking section on Edit Booking: Automatic (who booked) or a picked Partner 1, Partner 2, planner or other contact, with the email and phone the link emails go to. New `mainContactRole` column. Worked out from Signed By, then the booking's email, else the first named of planner, Partner 1, Partner 2, other; the update link uses the same check. On an office-entered wedding (no booking email) it uses the contact's own email and phone, so "Send to client" and hold reminders go to them. - **Hold Until**: a date next to Filled Out Date on Hold bookings (default 7 days from today), used by the Car on Hold email. New `Booking.holdUntil` column. - **Hold reminders**: a daily job (`/api/cron/hold-reminders`, `CRON_SECRET`, needs a crontab entry) emails the client 2 days before a hold ends. New `holdSentAt` and `holdReminderSentAt` columns. - **Hold weddings**: a wedding on Hold saves with just one contact (a partner, the planner or the other contact, with a name and an email or phone); the full rules apply once it leaves Hold. - **Settings layout redesign (v1.96.0)**: grouped left-hand settings menu, an overview page with search (the menu on phones), shared page header, sections and fields, and a sticky save bar with Discard on form pages. Per-page updates, including drag-to-reorder for booking types and sources, email templates grouped by recipient, and one save bar for the client portal texts. Layout and presentation only: no API, schema or save logic changes. - **Chauffeur and office emails (v1.96.2)**: the contract offer, welcome, password reset, offer accepted/declined, job updated, client updated booking and weekly report emails use the shared card layout (new `src/lib/email-layout.ts`), with job facts, notes and changes as tables and panels, and Outlook-safe buttons. New placeholders [COMPANY PHONE], [JOB SUMMARY], [BOOKING LINK], [BOOKING TYPE], [WEDDINGS COUNT]. - **Portal special instructions (v1.96.3)**: the same rich text editor as Edit Booking; client HTML is cleaned with `sanitize-html` before saving (safe formatting only). The portal gains a `public/` folder for TinyMCE, copied into the build by the scripts and Dockerfile. - **Job folders (v1.96.5)**: `Booking.jobFolder` records each booking's OneDrive folder so it follows date/name changes, Postponed/ and Cancelled/, and back (fixes postponed folders left behind). Groundwork for booking attachments (`plans/BookingAttachments.md`). - **Booking attachments (v1.97.0)**: attach documents (e.g. a run sheet) under Documents on Edit Booking, stored in the booking's OneDrive job folder (`Attachments/`), checked by type and content, 20 MB each; per-file Share with chauffeur, shown on the job in the chauffeur portal. New `BookingAttachment` table; proxy body limit raised to 25 MB. - **Client documents (v1.97.1)**: clients attach documents (run sheets etc.) on their update link's Details step, 10 MB each and 10 per booking; saved to the job folder, marked From client, office emailed and Activity Log. Portal routes rate limited and size capped. - **Times (v1.97.2)**: all booking times stored as 24h HH:MM and shown 12h; diffs compare by meaning (no false "changed" times or blank rows); readable change labels; one-off `prisma/normalize-times.ts` to fix saved 12h times (run by hand, dry run first). Also: hyphens instead of long dashes in emails, pages and PDFs, and the office email footer names the company. - **Chauffeur Offer reset**: Reset to default now clears the saved offer, so it shows Default again. - **Office email header**: office emails are headed with the company name from Company Info instead of "ACC SYSTEM". - **Time pickers on Edit Booking**: every time field on the booking form uses the same time picker as the client portal. - **Adding a car**: after saving, a new car takes its saved id, so "Unsaved changes" clears and later saves no longer recreate the car. - **Chauffeur portal redesign**: phone first, with a tab bar on phones and a sidebar on wider screens. Today shows the next job with a countdown, route, Navigate and Call. My Jobs puts offers first with the estimated pay. Each job has a detail page. Overtime is recorded per job in three steps, availability is set by tapping days, and pay shows the month by week and per job. - **Offer cards**: a View job details button above Decline and Accept job; portal headings use Source Serif 4 (plain J). - **People on a job**: every contact on the booking, the main contact falling back to the booking's phone and email, plus contact on the day, photographer and videographer, with Email, Message and Call buttons. - **Job page details**: Ceremony & reception (addresses with Navigate, times, drink type) and the special instructions, cleaned to plain formatting. - **Today**: no month pay card (pay stays on My Pay); the next job uses the full width when there's nothing for the side column. - **Date fields**: our own day-first date field (DD/MM/YYYY, typed or picked from a Monday-first calendar) replaces the browser's date picker in both apps, which showed month first on US-English browsers. - **Hold reminders**: sent on the first daily run in the last 2 days of a hold (not only the exact day), so a missed run or a short hold still gets one; the email says "in 2 days", "tomorrow" or "today". - **Hold reminder heading**: "Hi [name], your car is only on hold until [day]." - **Hold reminder record**: "Hold reminder sent" on the Client Link panel and a System entry in the booking's Activity log. - **Weekly report and hold reminder run logging**: both cron endpoints now stamp every run with when it ran, and a run-level failure includes the real error message instead of a generic one. A successful weekly report send, a run-level failure, and a hold reminder that fails for one booking are all recorded to the Activity Log, not just the server console. - **Activity log cars and payments**: added/removed cars logged by name and pickup time and matched by id (not list position); no more "Payments: - → []" on every save. - **Client Link history**: Sent, Hold reminder sent, Completed and Last updated each on their own line. - **Email template Save**: only active once the subject or body changes, so saving an unchanged template (for example right after Reset to default) no longer marks it Customised. - **Email template reset**: Reset to default restarts the editor so it stays Default with nothing unsaved; a company name starting with "Always Classic Cars" no longer doubles in saved templates. - **Expired reset link**: "Back to sign in" alongside "Request a new link". - **Reports redesign**: revenue, received, owing and after chauffeur pay against last year; booking pace; a Needs attention list for the next 60 days; fleet and chauffeur tables; busiest dates, day of the week and lead time; CSV export and print. - **Reports without money figures**: limited admins without the new Show money figures option (under Reports in Settings → Admin accounts) get a version with no dollar amounts, built on the server so the money never reaches their browser, and a CSV without totals or payments. - **Reports version per account**: each admin account has its own Reports choice (with or without money figures) in Settings → Admin accounts, whatever its access; new accounts start without money, existing ones keep what they saw. Adds `User.reportsMoney` (nullable, safe with `db push`). Also Reports wording: "1 car", lead time in days/weeks/months, plain hyphens. - **Lead time bars**: 12-24 months, 24-36 months and Over 36 months replace Over 12 months. - **Delete chauffeur**: only for a chauffeur never given a job (deletes their availability and portal login too); otherwise disabled with a note to untick Active. The API refuses instead of quietly deactivating. - **Duplicate chauffeurs blocked on create**: adding a chauffeur with the same name as an existing one (active or inactive) is now rejected with an error instead of silently creating a second record. - **Unavailable cars on the schedule**: a date with a car marked unavailable (Fleet → car availability) shows an "unavailable" badge on that date's header, whether or not the car is actually booked that day; hovering shows the time range (or "All day") and the reason. - **Branded chauffeur availability**: `/chauffeurs/availability` matches the chauffeur portal's burgundy/gold/ivory branding, with month/week/day views showing accepted jobs and sent offers (not just unavailability) per day, a Saturday coverage strip, a 6am-midnight day timeline, same-day conflict flags, and a right-side edit drawer (bottom sheet on mobile) replacing the old modal. - **Branded chauffeurs list and profile**: `/chauffeurs` and `/chauffeurs/[id]` get the same branding, with a search box, an Active/Needs attention/Inactive/All filter, a "needs attention" panel for DC-licence and portal-login issues, and Next job/Jobs this month columns; the profile page adds a header card with status chips, summary tiles, an Upcoming jobs card and a Time off card. Same CPVV refresh, permissions, portal flows and delete rule throughout. - **Copy a car's journey (client portal)**: "copy journey to another car" adds the picked car with the same pickup, times, stops, hours and who it's for; the copy is then edited on its own. - **Who you are (portal)**: picking Wedding planner or Other takes the booker's details back off Partner 1. - **Special instructions in change lists**: shown as plain text in the client update email and Activity log, not the stored HTML document. - **Legal name**: used at the bottom of PDFs, the sidebar (with version underneath) and the portal footer. - **PDF header**: address, phone and email from Company Info (unchanged until filled in). - **Edit Booking page layout**: redesigned from the Proposed artboard. Header with client names, chips, date and the save/cancel/postpone/activity actions (sticky on large screens) plus section links; Booking, Couple or Client, one Contacts section, Ceremony & Reception combined, collapsible car cards with a summary line and grouped fields, shorter instructions editor, Signature, Delete at the end; sticky right column with Payment (balance due, paid progress), Chauffeur Pay, Documents and the Client Link panel. The New Booking page uses the same layout. Fields, validation and saving are unchanged; errors inside a collapsed car open it. - **Fixed**: the booking form counted as changed on load (fields starting undefined picked up "" from the page), so leaving warned and Cancel asked to discard even with no edits. - **Chauffeur pay hidden on screen by default**: the Chauffeur Pay card and the pay rate fields start hidden so clients in the office can't see them; Show/Hide is remembered per browser. Display only. ### Client portal (`clientportal/`) - Five step wizard with fleet picker and availability, stops, night transfers, airport transfers with suggested pickup times, drawn signature, thank you page with deposit and bank details. - Direct links `/wedding`, `/general`, `/transfer` (work even when a type is hidden from the main page); edit mode at `/b/<token>`. - Footer, contact details, header text and images come from Settings. - Weddings show the couple's names (from "Who you are") on each car's chips, the ceremony arrival times and the summaries, instead of Partner 1 and Partner 2. - Per IP rate limits, body size limit, Cloudflare Turnstile (required in production from v1.124.19). ### Deployment - **v1.118.0 to v1.120.0**: no database changes. Deploy the office app and the client portal together (the portal's fleet cards read the rego and CPV fields from the office app's fleet API). - **v1.117.0**: `db push` adds `AuditLog.userRole` (no data changes). Everyone signs in once more (sessions now carry a password stamp). - **Account status backfill (v1.116.6)**: `update.sh` marks accounts with no outstanding setup or reset link as having set a password, on every run. Safe to repeat. - **Office app and chauffeur portal (v1.116.0)**: `update.sh` replaces the `acc-system` PM2 app with `acc-admin` and `acc-chauffeur`, built from the existing entry so custom env values carry over (old file kept as `ecosystem.config.js.bak`). `install.sh`, `setup-portal.sh`, `restore.sh` and `docker-compose.yml` (new `chauffeur` service) use the two apps. New env vars `ACC_APP` (per process) and `CHAUFFEUR_APP_URL`. `npm run dev:chauffeur` runs the portal in development on 3002. Upgrade steps, Nginx sites for admin.* and portal.*, and rollback are in `docs/deployment.md` section 15. - Database changes are additive only; `scripts/update.sh` (`db push`) applies them. No data migration needed. - `update.sh` rebuilds and restarts the portal only where the `acc-clientportal` PM2 process exists, and prints how to add it otherwise. - New `scripts/setup-portal.sh` adds the portal to an existing server: shared key, both `.env` files, portal build, `acc-clientportal` in PM2, a connection check, and (for a local Nginx) the site and certbot. It asks whether the reverse proxy runs on the same server and sets the listen address to match (`127.0.0.1` or `0.0.0.0`). Safe to rerun; unchanged answers don't rebuild the main system. - `install.sh` sets `CAR_PHOTOS_PATH` and offers the portal setup at the end. - The portal's PM2 app is `acc-clientportal`. Servers that already run it as `acc-portal` are switched over by `update.sh` automatically. - Docker: `clientportal/Dockerfile`, `portal` service in `docker-compose.yml`, root `.dockerignore`. - New env vars: main `PORTAL_API_KEY`, `PORTAL_URL`, `CAR_PHOTOS_PATH` (and `APP_URL` for links); portal `MAIN_API_URL`, `PORTAL_API_KEY`, `HOSTNAME`, `TURNSTILE_SECRET_KEY`, `NEXT_PUBLIC_TURNSTILE_SITE_KEY`. They can go in the PM2 ecosystem files or in `.env` files (rebuild after changing `.env`; `PORT` and `HOSTNAME` stay in the ecosystem file). See `docs/environment-variables.md` and `docs/deployment.md`. ## Testing - **v1.116.0**: 530 root tests (29 new: sign in per app, route access per app, token realm checks, password reset per app) and 38 portal tests; typecheck, lint, `prisma validate` and both builds clean. Migrations applied from scratch match the schema exactly. End to end, both built apps were run against a copy of the dev database with a dual account user. Each app signed in its own account and rejected the other password. Admin sessions got 404 on chauffeur routes and chauffeur sessions got 404 on admin routes. Cookies moved between apps and forged wrong-app tokens were refused. `/api/portal/*` works on 3000 and returns 404 on 3002, and the iCal feed works on both. The PM2 split has since run on the dev server; routing problems there were the tunnel routes, not the apps. Not tested: Docker. - **v1.116.1 to v1.116.8**: 532 root tests (a new one for the chauffeur password change); typecheck and lint clean. The passwordSetAt backfill was run twice on a scratch database (fills the missing value, leaves an unfinished invite alone, reads back through Prisma). The chauffeur sidebar and the sign in pages were checked as rendered HTML on the dev servers, signed in as a test chauffeur; the sign in photo was checked against the old optimizer URL (400) and the new direct URL (200 WebP). Not viewed in a browser. - **v1.117.0**: 542 tests; typecheck, lint and migrations from scratch clean. Both built apps were run against a scratch database. Wrong current password refused; a password change ended both chauffeur "devices"; old password refused and new one accepted; the same email's office account unaffected; an office password reset elsewhere ended that office session (redirect to sign in with `callbackUrl`). A signed out browser opening the job sheet link went to sign in; fetch got 401. The Activity who filter was checked against real SQLite rows (dual account office edit as office, offer answer as chauffeur, old rows by email). Not checked: the offer email link substitution (reviewed only), a real CPVV result, and any of it in a browser. - **v1.118.0 to v1.120.0**: 545 root tests (new ones for the job change table: own car only, no costs, 12 hour times, car names) and 40 portal tests (CPV check age); typecheck and lint clean in both. The job change email was rendered from the default template and checked for the table. Tested OK on the test server: the stops fix (v1.119.5), generated PDFs showing straight away (v1.119.6) and adding a booking staying on it (v1.119.7). Not checked in a browser: the fleet cards and the schedule column widths. v1.120.0: 559 root tests (new hold-car-changes tests) and 43 portal tests; the real client update was run against a scratch database (add, remove and lower hours on Hold; offered car not removable; car booked elsewhere refused; 3 hour minimum; deposit $200 to $300 and a hand-set deposit kept; non-Hold adding refused and hours only up), all passing. Not checked in a browser: the portal car step on a Hold booking. - `npm test` (245) in the root and `npm test` (28) in `clientportal/`; typecheck and lint clean in both. - Edit Booking page: checked in a browser (sticky header and column, save keeps all cars and stops, validation opens a collapsed car, New Booking and airport transfer pages, phone width, no unsaved-changes prompt without edits, pay hidden by default and saved unchanged). - PDFs: text for existing bookings identical before and after with the new Company Info fields empty; filled in values appear in the header and footer. - End to end against a copy of the dev database: wedding (desktop) and return airport transfer (390px mobile) booked through the portal in a headless browser; client link edit and signing; locked fields and hour reduction rejected; old token dead after reset; PDFs for existing bookings identical before and after. - Dev server: updated with `update.sh` on this branch and `setup-portal.sh` run; `acc-clientportal` online and connected to the main system. - Not tested: Docker images (no Docker locally) and real email delivery (no SMTP locally). - **v1.124.19 to v1.124.22**: 640 root tests and 50 portal tests (new ones for signature sizes, client upload types, reset links from a forged host, per account and IP lockout, `CLIENT_IP_HEADER` and Turnstile in production); typecheck and lint clean in both. Rebased onto v1.124.18 with no code conflicts. The dev server was updated from v1.93.2 (on `main`) to v1.124.20 with `update.sh`: the server's own v1.93.2 script built the new code but didn't split the apps, and the branch's script then split `acc-system` into `acc-admin` and `acc-chauffeur`; `db push` only added the three unique indexes, and booking and car counts were unchanged. `setup-portal.sh` stopped on a blank Turnstile key without writing anything, then installed `acc-clientportal` with Cloudflare's test keys. All three apps answered, `/api/portal/` refused requests without the key, a booking without a Turnstile token was refused, and a booking with the 8000 by 8000 signature was refused with no booking or file created and the office app at about 200 MB. Not tried there: anything that sends email (the dev database has real clients and chauffeurs). v1.124.21 and v1.124.22 are docs only. - **v1.124.23**: 641 root tests and 51 portal tests (new: dates typed as digits). Full pass of both builds, a sweep of about 120 pages and APIs, and Chrome runs of adding a booking, the client link, accepting an offer and a portal booking. - **v1.125.0**: 643 root tests (new: video types by content). Uploaded MP4, MOV, WebM and a 40 MB video through the chauffeur portal; a fake .mp4, a 92 MB video and a 21 MB photo were refused. - **v1.125.1**: 643 root tests, both builds and a sweep of 106 requests. Offer page checked signed in to each app and signed out (accept message stays, no sidebar, title); field widths measured from 1024 to 1600 px. - **v1.125.2**: 643 root tests; both of a chauffeur's job pages checked for mailto links and any email address in the page data (none). - **v1.126.0**: 643 root tests and the build. On the demo stack: linking and moving links on Office Signatures, $100 on an unpaid booking fills and saves the admin's signature (client PDF remade with it), $50 fills nothing, a chosen signature is kept, an unlinked admin gets nothing, opening a paid booking changes nothing; PATCH refuses unknown signatures (404) and non admin accounts (400). - **v1.126.1**: full test pass (643 root and 51 portal tests, tsc, lint, both builds, demo sweep) plus Chrome on the demo stack for v1.126.0. After the fix: a new payment row is empty with the placeholder, clicking and typing 100 gives 100 and saves $100, a new refund row is empty. ## Before go-live 0. For v1.116.0: back up, run the account checks in `docs/deployment.md` section 15, add DNS for admin.* and portal.*, and put `APP_URL` and `CHAUFFEUR_APP_URL` in `/opt/acc-system/.env` before running `update.sh`. Then add the two reverse proxy hosts (admin.* to 3000, portal.* to 3002). 1. DNS for `book.alwaysclassiccars.com.au`, Turnstile keys (required: without the secret the portal refuses new bookings), then `scripts/setup-portal.sh` (or the manual steps in `docs/deployment.md`) and the reverse proxy. 2. Office setup per `docs/client-portal.md`: car photos and seats, portal images and wording, booking types, Company Info (legal name, ABN, contact and bank details), terms, email templates. 3. Add the daily hold reminder crontab entry (`CRON_SECRET` in `.env`), see "Hold reminders" in `docs/client-portal.md`. 4. Back up the database and run `npx tsx prisma/normalize-times.ts` (dry run), then with `--apply`. 5. Switch the website's booking links from the WPForms site to the portal. 6. Set `CLIENT_IP_HEADER` in all three apps for the proxy in front of them (`cf-connecting-ip` behind a Cloudflare tunnel), and make sure `APP_URL` and `CHAUFFEUR_APP_URL` are set, or password reset emails aren't sent. 7. Follow `docs/upgrade-from-1.93.md` when updating a v1.93.2 server: fetch the latest `update.sh` with curl rather than running the server's old copy.
sb added 10 commits 2026-09-23 20:20:46 +10:00
Author
Owner

To be fully tested before merge.

To be fully tested before merge.
Author
Owner

tomorrow 24-9-2026 i will deploy to test server to validation

tomorrow 24-9-2026 i will deploy to test server to validation
sb self-assigned this 2026-09-23 20:24:39 +10:00
sb changed title from Client booking portal (v1.95.0 to v1.95.9) to Client booking portal (v1.95.0 to v1.95.11) 2026-09-24 07:31:05 +10:00
sb changed title from Client booking portal (v1.95.0 to v1.95.11) to Client booking portal (v1.95.0 to v1.95.13) 2026-09-24 09:07:39 +10:00
sb changed title from Client booking portal (v1.95.0 to v1.95.13) to Client booking portal (v1.95.0 to v1.95.14) 2026-09-24 09:20:17 +10:00
sb changed title from Client booking portal (v1.95.0 to v1.95.14) to Client booking portal (v1.95.0 to v1.95.16) 2026-09-24 10:08:18 +10:00
Author
Owner

Requested Change for the chips will show the couple's names from the "Who you are" step, for example Sam Smith | Alex Jones | Party instant of "Partner 1"/"Partner 2"

Requested Change for the chips will show the couple's names from the "Who you are" step, for example Sam Smith | Alex Jones | Party instant of "Partner 1"/"Partner 2"
sb changed title from Client booking portal (v1.95.0 to v1.95.16) to Client booking portal (v1.95.0 to v1.95.17) 2026-09-24 10:19:44 +10:00
Author
Owner

Bug when i add a car to a booking and save it still show Unsaved changes

Bug when i add a car to a booking and save it still show Unsaved changes
Author
Owner

Fixed add a car to a booking and save it still show Unsaved changes. Test ok

Fixed add a car to a booking and save it still show Unsaved changes. Test ok
sb added 2 commits 2026-09-26 07:17:26 +10:00
Reject creating a chauffeur whose name matches an existing one (active or inactive), instead of silently creating a duplicate record.
Stamp every weekly-report cron run with when it ran and how it went (success, failure with the real error, or unauthorized), so the crontab's log file is actually useful for debugging a missed run. A successful send and a failure both land in the Activity Log too.
Same as the weekly report: stamp every hold-reminder cron run with when it ran, and record a run-level failure with its real error. A per-booking send failure now also lands on that booking's Activity Log, not just the console.
Fetch CarUnavailability for the visible month, same pattern as the existing chauffeur-unavailability query, and show a badge on that date's header, whether or not the car is actually booked that day.
The date header showed a car count that excludes night transfers with no count of their own. Add a matching "N night transfers" badge next to it.
Add the same "N cars" and "N night transfers" badges to the print view's date header as the on-screen schedule.
Rebuild /chauffeurs/availability to match the chauffeur portal's burgundy/gold/ivory branding: month/week/day views with job and offer detail (not just unavailability), a Saturday coverage strip, a 6am-midnight day timeline, conflict flags, and a new edit drawer (bottom sheet on mobile) replacing the old modal. Sidebar and the chauffeur/client portals are untouched.
npm test caught two issues: every version bump since 1.103.4 missed the lock files' packages[""].version field alongside the top-level one, and EditDrawer's handleRemove read a possibly-null outer variable inside a closure TypeScript couldn't narrow.
Rebuild /chauffeurs and /chauffeurs/[id] (and /new) to match the chauffeur portal's burgundy/gold/ivory branding: a search + Active/Needs attention/Inactive/All filter and attention panel on the list, a header card with status chips and summary tiles plus Upcoming jobs/Time off cards on the profile. Same CPVV refresh, permissions, portal-access flows and delete rule throughout. Also fixes the Availability redesign never actually loading its branded font (src/app/chauffeurs/layout.tsx now loads it for the whole section).
Rebuild /cars and /cars/[id] (and /new) to match the chauffeur portal's burgundy/gold/ivory branding: a grid/table list with search, a make filter, an Active/Needs attention/Inactive/All filter and attention panel, and a car profile with a hero card (overlaid photo editor, status chips, booking/pricing tiles), Upcoming bookings and Off the road cards. Same CPVV verification, permissions and pricing behaviour throughout.
Author
Owner

Add to settings

  • Optional sign-in photo — the brand panel can show a photo behind the name with a dark fade for legibility; add src/app/login-hero.jpg and switch it on in src/components/auth/hero.ts (see the TODO there). Until then the panel is plain burgundy
Add to settings - **Optional sign-in photo** — the brand panel can show a photo behind the name with a dark fade for legibility; add `src/app/login-hero.jpg` and switch it on in `src/components/auth/hero.ts` (see the TODO there). Until then the panel is plain burgundy
Rebuild /schedule/print to match the admin branding: black-on-white pages (no filled colour bars or chips), a burgundy letterhead, plain-text chauffeur status, and content-sized table columns. Adds a toolbar with quick date-range pills and show/hide toggles for payment status and notes. Data, queries and URL params are unchanged.
The schedule page's Print button opened a popover asking for a date range, then landed on the print page, which now has its own full date-range and options toolbar. Skip the popover and open the print page directly.
The fixed-position running header and footer overlapped the letterhead and a stranded date heading in print preview. Replaced with a simpler, reliable layout: the letterhead once at the top, the printed-at line once at the end, and break-after: avoid on date headings so one can't be orphaned at the bottom of a page.
The date-range pills had This week/Next 7 days/{Month}/Next 30 days but nothing for just today.
Previously ran today through 6 days out; now runs tomorrow through 7 days out, since Today is its own separate quick-range option.
The app's <body> carries an ivory background for the on-screen admin shell, which showed through below the printed content and wasted ink on the empty remainder of the page.
Author
Owner

image

need to remove type col from payment received as can't see full receipt no and date.

![image](/attachments/be789d71-f978-48dd-be58-1e0f99583816) need to remove type col from payment received as can't see full receipt no and date.
When a client or chauffeur booking sheet PDF wasn't found locally, both /api/booking-sheet and the chauffeur portal's booking-sheet route silently redirected to a hardcoded SharePoint URL. Both now just return a plain "not found" response instead.
sb added 2 commits 2026-09-30 11:13:43 +10:00
Receipt No and Date were getting truncated on the booking form's payment list. The Type column was redundant anyway (a refund already shows in red with a leading "-"), so it's gone, freeing the space.
Settings → Company Info gets a "Sign-in photo" upload, replacing the old approach of manually adding src/app/login-hero.jpg and editing code. Served through a new dedicated public route (/api/login-hero), since the existing settings-photo route requires an admin session and this needs to load before anyone's signed in — Next 16's src/proxy.ts (renamed from middleware.ts) redirects any unauthenticated request otherwise.
Settings → Company Info's Branding section adds Logo and Favicon uploads alongside the sign-in photo. Each falls back to its bundled default when nothing's uploaded, served through two new public routes (/api/site-logo, /api/site-favicon) since they need to load before anyone's signed in. The favicon now uses metadata.icons in the root layout instead of the static favicon.ico convention, so it can be swapped dynamically; the default file moved to public/default-favicon.ico as the fallback asset.
Author
Owner

image cant see receipt no. make date smaller

![image](/attachments/246f288a-5e43-47c2-a4dc-721a6b927ac8) cant see receipt no. make date smaller
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
sb changed title from Client booking portal (v1.95.0 to v1.95.17) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.116.0) 2026-09-30 17:50:12 +10:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.116.0) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.116.8) 2026-10-01 05:57:55 +10:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.116.8) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.117.0) 2026-10-01 06:16:35 +10:00
Author
Owner

image can't sel any from dropdown

![image](/attachments/5f2eb681-394a-48a6-b0a9-59faaa3c0137) can't sel any from dropdown
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.117.0) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.119.5) 2026-10-04 07:35:55 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.119.5) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.119.6) 2026-10-04 07:53:27 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.119.6) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.119.7) 2026-10-04 07:55:06 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.119.7) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.0) 2026-10-04 08:10:23 +11:00
Author
Owner

Needs fixing

  1. Your local dev database was two migrations behind. 20260930120000_user_email_unique_per_role and 20261001090000_add_audit_log_user_role hadn't been applied, which broke prisma/create-user.ts. I applied them with prisma migrate deploy. Your memory notes say production uses db push, so check production has the User(email, role) unique index and the AuditLog.userRole column.

  2. Non-numeric IDs crash with a 500 instead of a 404. Number("abc") gives NaN, which goes straight into Prisma:

    • Pages: /bookings/abc, /cars/abc, /chauffeurs/abc
    • API routes: /api/bookings/abc, /api/cars/abc, /api/chauffeurs/abc, /api/bookings/abc/chauffeur-sheet, /api/bookings/1/attachments/abc

    The chauffeur portal pages already guard against this with Number(x) || 0, so the fix is to do the same here.

  3. A bad ?month= crashes the schedule with a 500. /schedule?month=garbage produces an Invalid Date that reaches Prisma (src/app/schedule/page.tsx:93-98).

  4. "Contact on the day" is marked required in the client portal but isn't enforced. The Details step says "All optional except the contact", but you can go on and submit with it blank (clientportal/components/steps/StepDetails.tsx). The server schema treats it as optional too.

UX and polish

  1. Raw validation message on the car field. Submitting without a car shows "Invalid input: expected number, received NaN" instead of something like "Select a car".
  2. Booking form errors come in two rounds. The required Email and Phone errors only appear after the first errors are fixed, because the schema's superRefine only runs once the basic checks pass.
  3. Leftover blue buttons in the chauffeur portal. Save Profile and Change Password use bg-blue-600 (ProfileDetailsForm.tsx:182, PasswordCard.tsx:67), and MonthFilter.tsx has blue hovers.
  4. The chauffeur job page is stale after Accept. The "New offer" badge, "This job is offered to you" and the My Jobs "1" count stay until you reload.
  5. "Accepted · office notified" shows even when the email fails. The send is fire-and-forget. The client portal's "Sent to …" message has the same issue.
  6. The chauffeur job page shows an empty ROUTE box when no route is set.
  7. The Chauffeur dropdown on the booking card is about half the width of the Car dropdown.
  8. The section tabs highlight "Contacts" while you're looking at the Cars section.
  9. The schedule's Print button always opens the next 30 days, not the month you're viewing.
  10. The client portal's "Review your changes" doesn't show the change. Contact details aren't in the summary.

Worth confirming

  • The office's internal Notes field appears to chauffeurs as "From the office", but the booking form says it "Shows on the schedule and the printout." Is that intended?

Working correctly

  • Booking create, edit, payments, balance, audit diffs, PDFs, schedule, search and print.
  • Send offer: it fails cleanly without SMTP and leaves the data unchanged.
  • Chauffeur: sign-in, accept flow, and access to other chauffeurs' jobs is correctly blocked.
  • Client portal: booking flow, signature, edit link, and bad-link handling.
  • Reports, Activity, Settings, and the mobile layouts.
Needs fixing 1. Your local dev database was two migrations behind. 20260930120000_user_email_unique_per_role and 20261001090000_add_audit_log_user_role hadn't been applied, which broke prisma/create-user.ts. I applied them with prisma migrate deploy. Your memory notes say production uses db push, so check production has the User(email, role) unique index and the AuditLog.userRole column. 2. Non-numeric IDs crash with a 500 instead of a 404. Number("abc") gives NaN, which goes straight into Prisma: - Pages: /bookings/abc, /cars/abc, /chauffeurs/abc - API routes: /api/bookings/abc, /api/cars/abc, /api/chauffeurs/abc, /api/bookings/abc/chauffeur-sheet, /api/bookings/1/attachments/abc The chauffeur portal pages already guard against this with Number(x) || 0, so the fix is to do the same here. 3. A bad ?month= crashes the schedule with a 500. /schedule?month=garbage produces an Invalid Date that reaches Prisma (src/app/schedule/page.tsx:93-98). 4. "Contact on the day" is marked required in the client portal but isn't enforced. The Details step says "All optional except the contact", but you can go on and submit with it blank (clientportal/components/steps/StepDetails.tsx). The server schema treats it as optional too. UX and polish 5. Raw validation message on the car field. Submitting without a car shows "Invalid input: expected number, received NaN" instead of something like "Select a car". 6. Booking form errors come in two rounds. The required Email and Phone errors only appear after the first errors are fixed, because the schema's superRefine only runs once the basic checks pass. 7. Leftover blue buttons in the chauffeur portal. Save Profile and Change Password use bg-blue-600 (ProfileDetailsForm.tsx:182, PasswordCard.tsx:67), and MonthFilter.tsx has blue hovers. 8. The chauffeur job page is stale after Accept. The "New offer" badge, "This job is offered to you" and the My Jobs "1" count stay until you reload. 9. "Accepted · office notified" shows even when the email fails. The send is fire-and-forget. The client portal's "Sent to …" message has the same issue. 10. The chauffeur job page shows an empty ROUTE box when no route is set. 11. The Chauffeur dropdown on the booking card is about half the width of the Car dropdown. 12. The section tabs highlight "Contacts" while you're looking at the Cars section. 13. The schedule's Print button always opens the next 30 days, not the month you're viewing. 14. The client portal's "Review your changes" doesn't show the change. Contact details aren't in the summary. Worth confirming - The office's internal Notes field appears to chauffeurs as "From the office", but the booking form says it "Shows on the schedule and the printout." Is that intended? Working correctly - Booking create, edit, payments, balance, audit diffs, PDFs, schedule, search and print. - Send offer: it fails cleanly without SMTP and leaves the data unchanged. - Chauffeur: sign-in, accept flow, and access to other chauffeurs' jobs is correctly blocked. - Client portal: booking flow, signature, edit link, and bad-link handling. - Reports, Activity, Settings, and the mobile layouts.
sb added 3 commits 2026-10-04 09:03:09 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.0) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.3) 2026-10-04 09:03:24 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.3) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.4) 2026-10-04 12:14:09 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.4) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.5) 2026-10-04 17:00:36 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.5) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.6) 2026-10-04 17:03:25 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.120.6) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.0) 2026-10-04 18:26:55 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.0) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.1) 2026-10-04 18:44:26 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.1) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.2) 2026-10-04 20:02:54 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.2) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.3) 2026-10-04 20:15:58 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.3) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.4) 2026-10-04 20:23:39 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.4) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.5) 2026-10-04 20:28:34 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.121.5) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.122.0) 2026-10-04 20:38:33 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.122.0) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.122.1) 2026-10-05 06:25:26 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.122.1) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.122.2) 2026-10-05 06:28:24 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.122.2) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.11) 2026-10-05 17:15:32 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.11) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.12) 2026-10-05 17:53:36 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.12) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.13) 2026-10-05 18:08:35 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.13) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.14) 2026-10-05 18:25:27 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.14) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.15) 2026-10-05 18:29:00 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.15) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.16) 2026-10-05 18:38:57 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.16) to Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.17) 2026-10-06 18:30:56 +11:00
sb added 2 commits 2026-10-07 08:07:34 +11:00
sb added 2 commits 2026-10-07 14:17:26 +11:00
sb changed title from Client booking portal and separate chauffeur portal (v1.95.0 to v1.124.17) to Client booking portal, separate chauffeur portal and security fixes (v1.95.0 to v1.124.22) 2026-10-07 14:19:26 +11:00
sb changed title from Client booking portal, separate chauffeur portal and security fixes (v1.95.0 to v1.124.22) to Client booking portal, separate chauffeur portal and security fixes (v1.95.0 to v1.126.1) 2026-10-09 19:44:55 +11:00
sb merged commit c2da0fd3af into main 2026-10-09 19:44:56 +11:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sb/ACC-System!8
No description provided.