Client booking portal, separate chauffeur portal and security fixes (v1.95.0 to v1.126.1) #8
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "ClientBookingForm"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a client booking portal: a separate public app (
clientportal/, port 3001, forbook.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 bothpackage.jsonfiles 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.acc-admin(ACC_APP=admin, port 3000, admin.alwaysclassiccars.com.au) andacc-chauffeur(ACC_APP=chauffeur, port 3002, portal.alwaysclassiccars.com.au). The client portal stays on 3001 and still calls port 3000.acc-admin-session,acc-chauffeur-session, plus CSRF and callback cookies), encryption keyed to the cookie name, and arealmclaim in every token that ends the session if it doesn't match the app. Everyone signs in once after updating.User.emailis unique per role (indexUser_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 andprisma/create-user.tslook accounts up by email and role. Reset and setup links only work on their own app.CHAUFFEUR_APP_URL;APP_URLstays the office app's address./login.Follow ups (v1.116.1 to v1.116.8)
AdminSidebar.passwordSetAtbackfill never ran on servers updated withdb push.update.shnow runs it afterdb push(only fills empty values; unfinished invites keep their link and stay "Invite expired"). A chauffeur changing their own password andprisma/create-user.tsnow recordpasswordSetAttoo."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/deployment.mdsection 12 (v1.116.1), including a check that the admin and portal routes aren't swapped.Security and chauffeur fixes (v1.117.0)
/api/chauffeur/booking-sheet/<booking>fromCHAUFFEUR_APP_URL(the offer's is filled in when sent). Since the split they went to the office app.callbackUrl, same site paths only); background requests without a session still get a 401.AuditLog.userRolerecords 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 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.POST /api/bookings/[id]/pdfsreturns them).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.
/bookings/abc,/api/cars/abc, an attachment id) reached Prisma asNaNand failed with a 500. Every[id]page and route now reads its id throughparseId()(src/lib/route-params.ts).?month=garbagebuilt an Invalid Date (500) and?month=13showed the next January; an invalid month or year now shows the current one.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.docs/user-manual/has an office guide, a chauffeur guide and a client guide, with screenshots of demo bookings. Linked fromdocs/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).[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.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)./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 fromdocs/user-manualbyscripts/build-user-manual.mjs(newmarkeddev dependency) into the committedpublic/help/andclientportal/public/help/; a unit test fails if the HTML is stale.Security hardening (v1.123.0 to v1.124.10)
See
CHANGELOG.mdfor 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)
trackedDeviceIds()), instead of showing and then failing when the chauffeur's own car isn't tracked./*turbopackIgnore: true*/, so.next/standalonedrops from 362 MB to 62 MB and no longer carriesdocs/,clientportal/or thesrc/libsources.update.sh,install.shandsetup-portal.shcopiedpublicinto the existingstandalone/publicaspublic/public, so/help/and the TinyMCE files gave 404. They now copy the folder's contents./loginwith a session redirects to thecallbackUrl, the schedule or the chauffeur home instead of showing the form inside the app layout.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:
APP_URL/CHAUFFEUR_APP_URL, never from a forgeableHostorX-Forwarded-Host. Unset, nothing is sent and the failure is logged; the answer is the same either way.CLIENT_IP_HEADER(v1.124.19): names the one header the proxy always sets (cf-connecting-ipbehind a Cloudflare tunnel), in all three apps, so rate limits can't be dodged with a forgedX-Real-IPorX-Forwarded-For. Unset, behaviour is unchanged.TURNSTILE_SECRET_KEYis unset; development still skips it.setup-portal.shno longer offers to skip the keys (v1.124.20).Cache-Control: private) and sharp 0.35.5 in the client portal (v1.124.19).example.envandclientportal/example.envlist every setting the apps read; the authentication, deployment, environment, client portal and OneDrive docs match the fixes.docs/upgrade-from-1.93.md, every step in order, with the dev server record.Test pass fixes (v1.124.23)
21112026or211126as 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.Job videos (v1.125.0)
proxyClientMaxBodySizegoes 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 needsclient_max_body_size 95m.Test pass fixes (v1.125.1)
Chauffeur portal privacy (v1.125.2)
Office signatures per admin (v1.126.0)
OfficeSignature.userId, migration20261009120000_office_signature_user. Production needsdb pushfor the new column.Test pass fix (v1.126.1)
Main system
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.CAR_PHOTOS_PATH), passenger count, "Show in client booking portal".mainContactRolecolumn. 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.Booking.holdUntilcolumn./api/cron/hold-reminders,CRON_SECRET, needs a crontab entry) emails the client 2 days before a hold ends. NewholdSentAtandholdReminderSentAtcolumns.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].sanitize-htmlbefore saving (safe formatting only). The portal gains apublic/folder for TinyMCE, copied into the build by the scripts and Dockerfile.Booking.jobFolderrecords 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).Attachments/), checked by type and content, 20 MB each; per-file Share with chauffeur, shown on the job in the chauffeur portal. NewBookingAttachmenttable; proxy body limit raised to 25 MB.prisma/normalize-times.tsto 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.User.reportsMoney(nullable, safe withdb push). Also Reports wording: "1 car", lead time in days/weeks/months, plain hyphens./chauffeurs/availabilitymatches 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./chauffeursand/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.Client portal (
clientportal/)/wedding,/general,/transfer(work even when a type is hidden from the main page); edit mode at/b/<token>.Deployment
db pushaddsAuditLog.userRole(no data changes). Everyone signs in once more (sessions now carry a password stamp).update.shmarks accounts with no outstanding setup or reset link as having set a password, on every run. Safe to repeat.update.shreplaces theacc-systemPM2 app withacc-adminandacc-chauffeur, built from the existing entry so custom env values carry over (old file kept asecosystem.config.js.bak).install.sh,setup-portal.sh,restore.shanddocker-compose.yml(newchauffeurservice) use the two apps. New env varsACC_APP(per process) andCHAUFFEUR_APP_URL.npm run dev:chauffeurruns the portal in development on 3002. Upgrade steps, Nginx sites for admin.* and portal.*, and rollback are indocs/deployment.mdsection 15.scripts/update.sh(db push) applies them. No data migration needed.update.shrebuilds and restarts the portal only where theacc-clientportalPM2 process exists, and prints how to add it otherwise.scripts/setup-portal.shadds the portal to an existing server: shared key, both.envfiles, portal build,acc-clientportalin 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.1or0.0.0.0). Safe to rerun; unchanged answers don't rebuild the main system.install.shsetsCAR_PHOTOS_PATHand offers the portal setup at the end.acc-clientportal. Servers that already run it asacc-portalare switched over byupdate.shautomatically.clientportal/Dockerfile,portalservice indocker-compose.yml, root.dockerignore.PORTAL_API_KEY,PORTAL_URL,CAR_PHOTOS_PATH(andAPP_URLfor links); portalMAIN_API_URL,PORTAL_API_KEY,HOSTNAME,TURNSTILE_SECRET_KEY,NEXT_PUBLIC_TURNSTILE_SITE_KEY. They can go in the PM2 ecosystem files or in.envfiles (rebuild after changing.env;PORTandHOSTNAMEstay in the ecosystem file). Seedocs/environment-variables.mdanddocs/deployment.md.Testing
prisma validateand 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.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.npm test(245) in the root andnpm test(28) inclientportal/; typecheck and lint clean in both.update.shon this branch andsetup-portal.shrun;acc-clientportalonline and connected to the main system.CLIENT_IP_HEADERand 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 (onmain) to v1.124.20 withupdate.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 splitacc-systemintoacc-adminandacc-chauffeur;db pushonly added the three unique indexes, and booking and car counts were unchanged.setup-portal.shstopped on a blank Turnstile key without writing anything, then installedacc-clientportalwith 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.Before go-live
docs/deployment.mdsection 15, add DNS for admin.* and portal., and putAPP_URLandCHAUFFEUR_APP_URLin/opt/acc-system/.envbefore runningupdate.sh. Then add the two reverse proxy hosts (admin. to 3000, portal.* to 3002).book.alwaysclassiccars.com.au, Turnstile keys (required: without the secret the portal refuses new bookings), thenscripts/setup-portal.sh(or the manual steps indocs/deployment.md) and the reverse proxy.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.CRON_SECRETin.env), see "Hold reminders" indocs/client-portal.md.npx tsx prisma/normalize-times.ts(dry run), then with--apply.CLIENT_IP_HEADERin all three apps for the proxy in front of them (cf-connecting-ipbehind a Cloudflare tunnel), and make sureAPP_URLandCHAUFFEUR_APP_URLare set, or password reset emails aren't sent.docs/upgrade-from-1.93.mdwhen updating a v1.93.2 server: fetch the latestupdate.shwith curl rather than running the server's old copy.To be fully tested before merge.
tomorrow 24-9-2026 i will deploy to test server to validation
Client booking portal (v1.95.0 to v1.95.9)to Client booking portal (v1.95.0 to v1.95.11)Client booking portal (v1.95.0 to v1.95.11)to Client booking portal (v1.95.0 to v1.95.13)Client booking portal (v1.95.0 to v1.95.13)to Client booking portal (v1.95.0 to v1.95.14)Client booking portal (v1.95.0 to v1.95.14)to Client booking portal (v1.95.0 to v1.95.16)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"
Client booking portal (v1.95.0 to v1.95.16)to Client booking portal (v1.95.0 to v1.95.17)Bug when i add a car to a booking and save it still show Unsaved changes
Fixed add a car to a booking and save it still show Unsaved changes. Test ok
Add to settings
src/app/login-hero.jpgand switch it on insrc/components/auth/hero.ts(see the TODO there). Until then the panel is plain burgundyThe date-range pills had This week/Next 7 days/{Month}/Next 30 days but nothing for just today.need to remove type col from payment received as can't see full receipt no and date.
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)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)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)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)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)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)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)Needs fixing
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.
Non-numeric IDs crash with a 500 instead of a 404. Number("abc") gives NaN, which goes straight into Prisma:
The chauffeur portal pages already guard against this with Number(x) || 0, so the fix is to do the same here.
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).
"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
Worth confirming
Working correctly
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)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)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)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)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)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)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)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)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)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)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)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)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)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)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)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)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)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)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)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)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)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)