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>
This commit is contained in:
+94
-16
@@ -5136,6 +5136,8 @@ input, select, textarea {
|
||||
|
||||
.tiles {
|
||||
|
||||
justify-content: center;
|
||||
|
||||
display: -moz-flex;
|
||||
|
||||
display: -webkit-flex;
|
||||
@@ -5176,7 +5178,7 @@ input, select, textarea {
|
||||
|
||||
position: relative;
|
||||
|
||||
width: 40%;
|
||||
width: 28%;
|
||||
|
||||
/* width: calc(33.33333% - 2.5em); */
|
||||
|
||||
@@ -5190,7 +5192,9 @@ input, select, textarea {
|
||||
|
||||
.tiles article:hover {
|
||||
|
||||
background: #9e8462;
|
||||
/* per-vertical hover fill lives on .style1/.style2/.style3 below;
|
||||
|
||||
this used to hardcode barber gold for any tile without a style class */
|
||||
|
||||
}
|
||||
|
||||
@@ -5204,9 +5208,9 @@ input, select, textarea {
|
||||
|
||||
border-radius: 50px;
|
||||
|
||||
margin-right: 0;
|
||||
margin-right: 1.5em;
|
||||
|
||||
margin-left: 5em;
|
||||
margin-left: 1.5em;
|
||||
|
||||
-moz-transition: background 0.2s ease-in-out;
|
||||
|
||||
@@ -5226,9 +5230,9 @@ input, select, textarea {
|
||||
|
||||
background: #000;
|
||||
|
||||
margin-left: 0;
|
||||
margin-left: 1.5em;
|
||||
|
||||
margin-right: 5em;
|
||||
margin-right: 1.5em;
|
||||
|
||||
border-radius: 50px;
|
||||
|
||||
@@ -5246,6 +5250,32 @@ input, select, textarea {
|
||||
|
||||
|
||||
|
||||
/* massage vertical */
|
||||
|
||||
.tiles article.style3{
|
||||
|
||||
background: #000;
|
||||
|
||||
margin-left: 1.5em;
|
||||
|
||||
margin-right: 1.5em;
|
||||
|
||||
border-radius: 50px;
|
||||
|
||||
border: 10px solid #9bbfae;
|
||||
|
||||
-moz-transition: background 0.2s ease-in-out;
|
||||
|
||||
-webkit-transition: background 0.2s ease-in-out;
|
||||
|
||||
-ms-transition: background 0.2s ease-in-out;
|
||||
|
||||
transition: background 0.2s ease-in-out;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tiles article.style1:hover{
|
||||
|
||||
background: #E4AD9C;
|
||||
@@ -5262,6 +5292,14 @@ input, select, textarea {
|
||||
|
||||
|
||||
|
||||
.tiles article.style3:hover{
|
||||
|
||||
background: #9bbfae;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tiles article > .image {
|
||||
|
||||
-moz-transition: -moz-transform 0.5s ease;
|
||||
@@ -5464,10 +5502,24 @@ input, select, textarea {
|
||||
|
||||
.tiles article > a h2 {
|
||||
|
||||
font-size: 52px;
|
||||
/* Was 52px, chosen when there were two 40%-wide tiles with
|
||||
|
||||
six-letter labels. Tiles are now 28% wide and MASSAGE is
|
||||
|
||||
eight letters, which pushed the word out past the rounded
|
||||
|
||||
border. 42px plus side padding keeps the longest label
|
||||
|
||||
clear of the corner radius on all three tiles. */
|
||||
|
||||
font-size: 42px;
|
||||
|
||||
font-family: 'Abril Fatface', cursive;
|
||||
|
||||
padding: 0 0.35em;
|
||||
|
||||
text-align: center;
|
||||
|
||||
margin: 0;
|
||||
|
||||
}
|
||||
@@ -5530,6 +5582,18 @@ input, select, textarea {
|
||||
|
||||
|
||||
|
||||
.tiles article.style3 > .image:before {
|
||||
|
||||
background-size: cover;
|
||||
|
||||
background-image: url(../../assets/img/massage.jpg);
|
||||
|
||||
background-color: #5f7d6e;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
body:not(.is-touch) .tiles article:hover > .image {
|
||||
|
||||
-moz-transform: scale(1.1);
|
||||
@@ -6814,21 +6878,25 @@ input, select, textarea {
|
||||
|
||||
|
||||
|
||||
.tiles article.style1 {
|
||||
/* .style1 used to be position:absolute; right:0 here - a two-tile-only
|
||||
|
||||
position: absolute;
|
||||
centring trick that overlapped the moment a third tile existed.
|
||||
|
||||
right: 0;
|
||||
Normal flex flow handles any tile count. */
|
||||
|
||||
margin-left: 0;
|
||||
.tiles article.style1,
|
||||
|
||||
|
||||
.tiles article.style2,
|
||||
|
||||
}
|
||||
.tiles article.style3 {
|
||||
|
||||
.tiles article.style2 {
|
||||
position: relative;
|
||||
|
||||
margin-right: 0;
|
||||
width: 28%;
|
||||
|
||||
margin-left: 1em;
|
||||
|
||||
margin-right: 1em;
|
||||
|
||||
}
|
||||
|
||||
@@ -6888,7 +6956,7 @@ input, select, textarea {
|
||||
|
||||
|
||||
|
||||
.tiles article.style1, .tiles article.style2{
|
||||
.tiles article.style1, .tiles article.style2, .tiles article.style3{
|
||||
|
||||
border-radius: 35px;
|
||||
|
||||
@@ -6896,6 +6964,8 @@ input, select, textarea {
|
||||
|
||||
margin-right: 2em;
|
||||
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6918,6 +6988,14 @@ input, select, textarea {
|
||||
|
||||
|
||||
|
||||
.tiles article.style3{
|
||||
|
||||
border: 7px solid #9bbfae;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user