Commit Graph
37 Commits
Author SHA1 Message Date
UbuntuandClaude Opus 5 4c823f92ca Fix lunch-break guard being skipped on same-day bookings
getAvailableTimes() overloaded $dayStartTime: it starts as the worker's
real shift start, but for same-day requests it is overwritten with the
earliest bookable slot (next 15-min block + 1 hour). The lunch-break
setup further down still read it as the shift start, so $shiftSeconds
measured the *remaining* day instead of the whole shift. Once that fell
under 6 hours, $lunchRequired went false and the guard was skipped
entirely -- every free slot was offered, including ones that leave no
room for a lunch break.

Observed on prod on 2026-08-21: at 12:17 the cutoff pushed the start to
13:30, so Kateryna's 10:00-18:00 shift measured 4.5h and a 13:30-15:30
booking was accepted even though it consumed the last possible lunch
slot. booking_process() re-validates through the same function, so the
server-side check agreed.

Capture the real shift start in $shiftStartTime before the cutoff runs,
and use it for both $shiftSeconds and the computeLunchBreak() call. The
latter also fixes a second symptom of the same overload: the lunch
window was being clamped to the cutoff rather than to the actual shift.

Verified end-to-end against pages/ajax on test: with the bug a slot that
destroys the last lunch break is offered, with the fix it is withheld.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZv7QGLyRYwdD8NyimYbQQ
2026-08-21 11:14:18 +00:00
UbuntuandClaude Opus 5 baa597a7c5 Guard against null permission_ids in getModulesByPermissionSlug()
modules.permission_ids is NULL for module 8 ("Szerviz kategoriak") on every
environment, and PHP 8.1 deprecates passing null to explode(). The notice was
only visible on prod because its index.php still has the stock
error_reporting(-1), whereas dev was previously changed to
E_ALL & ~E_DEPRECATED & ~E_WARNING & ~E_NOTICE.

- Skip rows whose permission_ids is null or empty. Behaviour-preserving:
  explode(',', null) returned array(''), which matched no permission id, so
  such modules were already excluded. Verified by simulating old vs new
  against the real module rows across six permission-id values including 0,
  '1' and 'admin' - identical results.
- Return early when the slug matches no permission, rather than dereferencing
  null on the next line.

Does not address the wider issue that prod runs ENVIRONMENT='development'
with display_errors=1, so any notice is rendered to real visitors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 10:49:07 +00:00
UbuntuandClaude Opus 5 35115404be Validate the guest e-mail before the booking is saved
An address like "asdf" used to be accepted: the field was type="text" with
only `required`, and there was no server-side check. createBooking() runs
long before sendEmail(), so the failure was silent rather than loud -
reproduced end to end on test:

  - the booking row WAS created, with a manage_token
  - PHPMailer's addAddress() threw, so nothing was ever sent
  - the Location header was already queued, so the guest was redirected to
    the normal "booking finished" page and saw success
  - Evelin is a CC on that same message, so the salon was not told either
  - the guest had no manage link, so they could not cancel

Fixes
- booking_process() rejects an empty or malformed address BEFORE any write,
  returning invalid_email / HTTP 400. Message added in all three languages,
  worded to say why it matters (the confirmation and the manage link go
  there). filter_var is equal-or-stricter than PHPMailer's own validator -
  checked against it on ten cases - so anything accepted here cannot throw
  later.
- The three public booking forms use type="email", so most typos never
  reach the server.
- Removed three debug echoes from User_model::sendEmail() that leaked $lang
  and Hungarian strings ("Üzenet elküldve", "Üzenetküldési hiba. Mailer
  Error: ...") into the guest-facing response.

Scope
- Public flow only. 508 existing bookings have an empty guest_email because
  admin-created block bookings legitimately have none; those go through
  Admin::booking_process(), which is untouched, and its form stays
  type="text".
- Not covered: a valid address whose SMTP delivery fails still leaves the
  booking created and the guest seeing success, logged only via
  log_message(). Different failure mode, needs a separate decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 09:55:33 +00:00
UbuntuandClaude Opus 5 7f51a795a5 Cache-bust local css/js so a deploy is picked up immediately
Apache serves this site's static files with only Last-Modified/ETag and no
Cache-Control or Expires, so browsers apply heuristic freshness and can hold
a stale stylesheet for hours. Deploys here are a file copy, so nothing else
signals a change. During development this masked CSS edits three separate
times; on the live site a returning customer would keep the old stylesheet
after a deploy with no way to know.

Adds asset_ver() (autoloaded), which returns the asset URL with the file's
mtime appended, so the URL itself changes whenever the file does. Falls back
to the plain URL when the file is missing, so a bad path degrades to the
previous behaviour rather than warning.

All 54 local css/js links now route through it, across the four *-head.php
and four *-skeleton-bottom.php includes.

Notes
- Remote assets are deliberately untouched: Google Fonts, momentjs,
  cookieyes and the googleapis jQuery keep their own URLs. Only files inside
  the webroot are versioned.
- vertical-head.php has one dynamic path (assets/css/<?= $vertical['css'] ?>)
  which is special-cased; a generic rewrite would swallow the nested <?php.
- Images are NOT versioned. Replacing an image under the same filename can
  still serve stale.
- head.php is shared by the public home page and the whole admin portal;
  /login verified to still render with versioned CSS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 09:00:40 +00:00
UbuntuandClaude Opus 5 de38aae8d5 Apply client design feedback on the parking copy, icons and barber layout
Four items from the salon, all three languages where applicable.

Massage section icon
- The icon above Om oss (and above Behandlinger, Priser and Åpningstider -
  it is the same image in all four) was beauty's makeup icon, copied in as
  a placeholder before massage had any assets of its own. Replaced with the
  herbal-compress icon tinted to the massage sage, mirroring how beauty's
  is tinted to its rose. beauty/ollo_rose.png is untouched; the copy under
  assets/img/massage/ is removed, and the one dead template rule that still
  pointed at it (.box-heading:before, which renders on no page) repointed.

Parking copy, barber + beauty + massage, no/en/hu
- Dropped the "park free of charge during the treatments" clause from the
  owner bio and replaced it with the new two-space wording.
- Replaced the note under Åpningstider. The Norwegian original was
  misspelled differently in each vertical ("kundeprarking" on barber,
  "kunderparking" on beauty); both are gone.
- massage picks both up automatically: its owner block is extracted from
  beauty-form-<lang>.php at generation time, and the hours note comes from
  the generator.

Barber treatments grid: level the six icons
- .service-text reserves padding-bottom for the 80px floated icon but has
  no height, so every block sizes to its own text. Measured on the live
  page the left column ran 155/155px against the right column's 185/206px,
  because its titles wrap to two lines - so the icons drifted further apart
  with each row (0, 30, 81px).
- Trimmed the reserved padding and gave every block the same floor, so all
  six are identical and the rows line up. 195px clears the tallest block
  and leaves room for a three-line title at the narrow end of the desktop
  range. Desktop only: below 981px the columns stack full width, where a
  floor would only add dead space.
- beauty drifts 20px and massage 0px, so both are left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 09:00:25 +00:00
UbuntuandClaude Opus 5 5c30467fbd Add massage as a third vertical, driven by a config registry
Introduces /massage alongside barber and beauty: landing page, booking
flow, admin support, home tile and SEO entries, in all three languages.

Architecture
- application/config/verticals.php + vertical_helper.php: one registry
  entry per vertical (branding, assets, views, behaviour flags). A fourth
  vertical is a config entry plus content files.
- Strangler: barber and beauty keep pointing at their existing view files,
  so their rendered HTML is unchanged. Only massage uses the new generic
  pages/vertical-*.php and includes/vertical-*.php views, which collapse
  the four duplicated per-language nav/footer branches into one.
- Pages::vertical() + one route; booking(), booking_finished(),
  _booking_error() and manage_booking() are now registry-driven.

Worker/vertical coupling
- getActiveWorkers() derives the vertical from services.service_category_id
  instead of the workers.is_barber / is_beauty flags, which were a
  hand-maintained cache of exactly that fact. Verified against production
  data: the derived set reproduced the stored flags for every worker, in
  both verticals. No schema change was needed for massage.
- The legacy flags are now written through from the category so a rollback
  cannot strand a new worker, and the admin worker UI shows the derived
  verticals read-only instead of two dropdowns that controlled nothing.

Bug fixes found along the way (all pre-existing)
- booking_process() had no server-side category guard; cross-vertical
  mixing was prevented only by client-side JS.
- add-service-form / add-worker-form emitted `selected` on every category
  option, so the newest category silently became the default.
- update-service-form offered only barber/beauty, so editing a service of
  any other type silently rewrote it.
- getWorkerScheduleByDay ignored schedule overrides while getAvailableTimes
  honoured them, so slots could be shown and then rejected. Added an
  override-aware getWorkerScheduleForDate() and used it in both guards.
- Booking lists dereferenced a null service if one had been hard-deleted.
- main.css: .tiles was tuned for exactly two tiles, including an
  absolutely-positioned .style1 at the 1280px breakpoint.

Massage-specific behaviour, opt-in per vertical
- strip_category_prefix: grouped service lists show "50 min" under the
  treatment heading rather than repeating the full name. The full name is
  carried in data-service-name so the totals panel stays unambiguous, and
  services.service_name is untouched for emails and admin.
- single_service_booking: one treatment per booking, enforced in the UI and
  in booking_process(). Re-clicking the selection releases it.
- Displayed treatment time (50/80/110 min) is in the service name; the
  booked slot (60/90/120 min) is service_time and covers changing and
  payment. service_time is never shown to the guest.

DB migrations for dev/prod are in documents/ - additive only, no ALTER.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 16:34:47 +00:00
UbuntuandClaude Opus 4.8 0fd7f7a7a5 Fix "Conflict. Timeslot is taken" raw-JSON error on booking
Customers intermittently hit a full-screen raw JSON error when booking:
{"error":"Conflict. Timeslot is taken or does not fit the service."}

booking_process() re-validates the chosen slot at submit time and returned
409/403 raw JSON. Because the public booking form is a full-page POST, that
JSON filled the whole screen.

The trigger is a double submit. After inserting the booking, booking_process()
synchronously runs two Google Calendar createEvent calls, a lunch sync, an ntfy
push and an SMTP confirmation e-mail before redirecting - several seconds - and
the submit button was never disabled. On mobile the guest taps "Send" again; the
second request arrives after the first has committed, so the slot reads as taken.

Evidence: 168 duplicate booking pairs exist in prod (same guest, slot and worker,
consecutive booking ids, including runs of four). All are from 2025, none from
2026 - the 409 guard added around May 2025 converted those silent duplicates
into today's visible error.

Prevent the double submit:
- disable the submit button and relabel it on first submit, ignore later ones
- add a hidden sendBooking field, since disabling a submit button can drop its
  name/value from the POST and booking_process() bails to the homepage without it

Handle it gracefully when it still happens:
- new _booking_error() renders a localised page in the right skin instead of raw
  JSON, replacing all six JSON responses in booking_process()
- new booking-error views for barber/beauty in no/en/hu, each with a message per
  error case and a link back to booking
- new Service_model::getBookingBySlotAndGuest(); if the guest's own booking for
  that exact slot already exists the submit is a duplicate rather than a real
  conflict, so finish normally instead of erroring. Guarded on a non-empty
  e-mail, as admin block bookings are stored with an empty guest_email.

No schema change. Verified on test, dev and prod: friendly page in all three
languages and both skins, double submit redirects to booking-finished without
creating a duplicate row, and no raw JSON in any response.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJso3iGT7TkW5tm4RSBohs
2026-07-21 16:21:53 +00:00
UbuntuandClaude Fable 5 06966a898a Add job postings section with vacancies listing and footer links
- New /ledigestilling/ index listing all open positions (split
  barber/beauty card styling)
- Three ads: negletekniker + massør (beauty design), barber (gold
  barber design with recolored CSS + assets)
- Rename ledigestilling.html -> negletekniker.html with 301 redirect
- "Ledige stillinger" footer link on barber/beauty pages (NO/EN/HU)
- Back-to-list link on each ad page

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:32:17 +00:00
UbuntuandClaude Opus 4.7 29467211c5 Redirect direct GETs of booking-process to home
Hitting /booking-process via GET (or any POST without sendBooking) fell
through past the form-submit guard and tripped three undefined-variable
warnings on the final sendEmail() call. Bail out to the site root
early, matching the pattern in manage_booking_process.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 08:06:43 +00:00
UbuntuandClaude Opus 4.7 f86952dc95 Filter manage-booking workers by service category
Guests modifying a booking saw every worker for the subpage, so an
eyelash booking exposed nail-only workers as switchable. Now the worker
list is scoped to the booking's service category, other-category
services are disabled in step 1, and the process handler rejects any
worker/service category mismatch to defend against crafted POSTs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 07:43:15 +00:00
Ubuntu 4a93b5efa0 Fix undefined array key in header on short URLs
The language switcher in barber/beauty headers reads
$currentPageUrlArray[2] unguarded after exploding REQUEST_URI on '/'.
On routes with no language prefix (e.g. POST /manage-booking-cancel
rendering the cancelled page inline), the array has only 2 elements,
triggering "Undefined array key 2" warnings and producing malformed
language links like https://studiobeve.no/en//.

Normalize the array with += [2 => '', 3 => ''] right after the explode
so indices 2 and 3 always exist.
2026-05-11 19:28:33 +00:00
UbuntuandClaude Opus 4.7 92a84aadb1 Remove dead strftime() call in worker calendar form
The strftime() result was immediately overwritten by a hardcoded
Hungarian month-name array. Drop the dead line so PHP 8.1+ stops
emitting an E_DEPRECATED warning when prod loads the worker calendar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 11:41:47 +00:00
UbuntuandClaude Opus 4.7 e8aa9084c1 Include manage link + 24h notice in admin notification emails
Admin create/modify booking notifications now match the public
confirmation/modify emails. Admin-created bookings get a manage_token
generated; admin-modified bookings reuse the existing token (or
backfill one if missing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 11:29:48 +00:00
Ubuntu e24e68f603 Fix manage-booking time slots; align modify email with original
- getAvailableTimes() takes optional exclude_booking_id so a guest's
  own booking isn't counted as a conflict when editing — original time
  now reappears when extending services
- Manage-booking AJAX passes manage_token; server resolves to booking_id
- manage_booking_process uses the new param instead of the date-swap
  workaround (removes a small race-condition risk)
- Modify-booking emails (no/en/hu) now include Name/Email/Phone rows
  and the 24h cancellation policy, matching the original booking email
2026-05-10 13:18:07 +00:00
UbuntuandClaude Opus 4.7 d7bdcac978 Add search filter to services admin list; refresh load_services.json
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 12:41:04 +00:00
UbuntuandClaude Opus 4.7 52e00216f7 Fix admin update forms — restore proper value="" quoting
The security hardening commit accidentally rendered every input value as
value=\"...\" (literal backslash-quote in HTML), which mangled all
submitted fields including the hidden worker_id/service_id, causing
UPDATE to match zero rows and silently no-op.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 12:40:50 +00:00
UbuntuandClaude Opus 4.6 6abb90329d Add visual booking calendar (weekly Teams-style view)
- New weekly calendar at /bookings/calendar with time blocks per booking
- Color-coded by worker, overlapping bookings shown side-by-side
- Click booking to see details, edit or delete
- Worker filter dropdown, week navigation (prev/next/today)
- AJAX week loading for smooth navigation
- Link between list view and calendar view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 14:03:41 +00:00
UbuntuandClaude Opus 4.6 0e172ceea8 Sync outstanding changes: beauty forms, config updates, gitignore
- Update beauty booking forms (en, hu, no)
- Update manage-booking view, worker calendar view
- Update config.php, google_calendar.php, GoogleCalendar library
- Add CLAUDE.md project instructions
- Add service-account-key.json to .gitignore
- Update ledigestilling page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 13:02:18 +00:00
UbuntuandClaude Opus 4.6 420bcb37fd Security hardening: fix SQLi, XSS, file upload, and migrate DB to RDS
- Fix all SQL injection vulnerabilities across Service_model, User_model,
  Module_model, Log_model, and Admin controller using parameterized queries
- Add htmlspecialchars() to all user-controlled output in admin views
  (bookings, services, workers, service categories, login form)
- Fix XSS in AJAX worker response and manage-booking-cancelled view
- Add file extension whitelist (jpg, jpeg, png, gif, webp) to all uploads
- Remove webshell (pentest2.php) from assets/img/profiles/
- Stop logging plaintext passwords on failed login attempts
- Migrate database.php hostname from localhost to AWS RDS endpoint
- Fix dropdown styling (white-on-white) in worker calendar view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 13:00:32 +00:00
Astral04andClaude Sonnet 4.6 307f17faa6 Fix timezone bug causing slots past closing time; remove lunch fallback outside window
- Add Europe/Oslo timezone to all DateTime constructors in getAvailableTimes() and
  computeLunchBreak() to prevent UTC vs local time mismatch that allowed booking
  slots 1 hour past the worker's end time on same-day bookings
- Remove fallback loop in computeLunchBreak() that pushed the lunch break outside
  the configured window; lunch break is now strictly enforced within the interval

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 11:52:30 +01:00
Astral04andClaude Sonnet 4.6 f30d8d1a07 Fix lunch break: float-to-gap logic + Google Calendar sync
- Lunch slot no longer pre-blocked; a slot is only unavailable if
  booking it would eliminate the last possible 30-min break window
- Added preferred lunch time per worker (closest-to-preferred slot wins)
- Lunch break only applies for shifts >= 6 hours
- Google Calendar: lunch event created/updated/deleted on every
  booking create, modify, or cancel via _syncLunchCalendarEvent()
- New table worker_lunch_gcal_events tracks lunch event IDs per worker/date
- New model methods: getBookingsForWorkerDay, getLunchGcalEventId,
  upsertLunchGcalEventId, deleteLunchGcalEventRecord, getBookingsForWorkerMonth,
  computeLunchBreak

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:12:08 +01:00
Astral04andClaude Sonnet 4.6 b8f4f67b23 Add visual worker calendar and floating lunch break system
- Monthly calendar grid per worker with colour-coded day status
- Override types: vacation, sick, custom hours, other, day-off
- Date-range override support via modal
- Floating 30-min lunch break: finds slot closest to preferred time
  within configurable window, adapts to existing bookings
- Lunch break only applies for shifts >= 6 hours
- Lunch slot shown in admin calendar; blocked in booking availability
- DB migrations: absence_type/note on worker_schedule_overrides,
  lunch_window_start/end/preferred_time on workers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 13:34:58 +01:00
Astral04andClaude Sonnet 4.6 993e0cb8b1 per-worker ntfy topics for targeted push notifications
Evelin receives all booking notifications via the shared ntfy topic in config.
Each worker also gets notified on their own personal ntfy topic (set per-worker
in the admin panel) — so workers only see their own booking events.

- _ntfy() now accepts optional $workerTopic and sends to both topics if different
- All three call sites (new/modify/cancel) pass $worker->ntfy_topic
- Admin worker form + worker_process() wired for ntfy_topic field
- DB: ALTER TABLE workers ADD COLUMN ntfy_topic VARCHAR(100) NULL DEFAULT NULL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 15:30:08 +01:00
Astral04andClaude Sonnet 4.6 1b96632f9b Set ntfy topic for booking push notifications
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 15:23:14 +01:00
Astral04andClaude Sonnet 4.6 a71dc8d801 Add ntfy.sh push notifications for booking events
Sends instant push notification on new booking, modification, and
cancellation. Set ntfy_topic in application/config/google_calendar.php
to activate. No-ops silently if topic is empty.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 15:21:13 +01:00
Astral04andClaude Sonnet 4.6 ae890c81ee Revert Domain-Wide Delegation — not applicable for personal Gmail accounts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 15:18:11 +01:00
Astral04andClaude Sonnet 4.6 804d454618 Google Calendar: Domain-Wide Delegation support for attendee invitations
Service account can now impersonate a Workspace user (set gcal_impersonate_email
in config) to unlock attendee invitations with push notifications.
Falls back to silent event creation if impersonation is not configured.

Setup required in Google Admin Console:
  Security → API Controls → Domain-wide delegation
  → Add service account client_id with scope:
  https://www.googleapis.com/auth/calendar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 15:09:26 +01:00
Astral04andClaude Sonnet 4.6 3561aa30bb Revert: remove attendee invitations from calendar events
Google API forbids attendees on service account events without
Domain-Wide Delegation (requires Google Workspace). Reverts to
direct event creation which works with personal Gmail calendars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 14:58:45 +01:00
Astral04andClaude Sonnet 4.6 06e25183da Google Calendar: send invitation notifications on booking events
Add attendees to calendar events with sendUpdates=all so workers and
Evelin receive an instant push notification + invitation email when a
booking is created or modified, instead of the event silently appearing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 14:54:05 +01:00
Astral04andClaude Sonnet 4.6 bb3a65259b Google Calendar integration for booking notifications
- New GoogleCalendar library: createEvent/updateEvent/deleteEvent via service account, all wrapped in try/catch so failures never break booking flow
- booking_process: creates worker + owner calendar events on new booking
- manage_booking_process: deletes old events, creates new ones on modify
- manage_booking_cancel: deletes events before cancellation
- Service_model: updateBookingCalEvents() stores gcal event IDs
- Admin worker form: Google Calendar ID field added
- PHPMailer: enabled exceptions (was silently swallowing SMTP errors)
- Config: application/config/google_calendar.php for service account path + Evelin calendar ID

DB migration required:
  ALTER TABLE workers ADD COLUMN google_calendar_id VARCHAR(255) NULL DEFAULT NULL;
  ALTER TABLE bookings ADD COLUMN gcal_event_id_worker VARCHAR(255) NULL DEFAULT NULL;
  ALTER TABLE bookings ADD COLUMN gcal_event_id_owner VARCHAR(255) NULL DEFAULT NULL;

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 14:46:58 +01:00
Astral04andClaude Sonnet 4.6 2183c8aff2 guest booking self-management feature + layout fixes
- add manage_token column to bookings (DB migration done)
- generate unique token per booking, include manage link in confirmation emails (no/en/hu)
- new routes: manage-booking, manage-booking-process, manage-booking-cancel
- new views: manage-booking.php, manage-booking-cancelled.php
- 24h cutoff enforcement for cancel/modify; emails sent to guest + studio CC
- fix manage-booking step 3 float layout (overflow:auto BFC clearfix)
- fix booking page time slot overflow: bookingResultsWrapper 378px -> 360px

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 16:01:12 +01:00
Astral04 02169a89f6 booking form category show fix 2026-01-25 10:26:12 +01:00
Astral04 3a7dd84992 foglalás időpont +1 órás baszakodás 2025-12-16 11:10:33 +01:00
Astral04 fb7178a919 mobile redirect fix 2025-10-04 14:16:51 +02:00
Astral04 45c192058a 3 month limit setup 2025-10-04 13:52:17 +02:00
Astral04 64d5a8fbf3 e-mail text modification 2025-10-04 12:11:04 +02:00
Astral04 68c8245cef add all files 2025-10-04 11:38:07 +02:00