A parent registers once and manages lessons for one or more children, who need no login of their own. A child is a real wp_users row with the student role but no usable login — so student_id keeps meaning "a WordPress user" on every table, and booking, credits, policies and enrolments work unchanged. A us_guardians link table maps guardian to child. The signup form gains a parent/guardian tick that reveals a block per child, with the account-signup questions asked per child rather than per guardian — they describe the student, not the account holder. Signup policies are recorded once per child with the guardian as the acceptor, which is the record that actually means something. A family that half-creates is rolled back entirely rather than leaving a guardian who cannot re-register. The booking and enrolment forms gain a "Who is this for?" picker listing children first, so the default selection is never the parent — booking for the wrong child is correctable, quietly billing a parent for their kid's lesson is not. POST /bookings and POST /enrollments take an optional student_id honoured only for that child's guardian; anything else is a 403. That check is the authorisation boundary of the feature. Payments and credits gain a payer: the charge names the child it was for and the guardian who owes it, so per-child reporting is unchanged while notices, receipts and the payment step reach the parent. Credit is held by the payer, so one child's cancellation can settle a sibling's charge, and the daily billing scan sends a guardian one notice covering every child. Closes #132 Co-Authored-By: Claude Opus 5 <[email protected]>
499 lines
9.6 KiB
CSS
499 lines
9.6 KiB
CSS
.us-login-form label {
|
|
display: block;
|
|
margin-bottom: 4px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-login-form input[type="text"],
|
|
.us-login-form input[type="password"] {
|
|
width: 100%;
|
|
max-width: 340px;
|
|
padding: 8px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.us-error {
|
|
color: #c00;
|
|
border-left: 4px solid #c00;
|
|
padding-left: 8px;
|
|
}
|
|
|
|
#us-booking-app .us-slot {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 12px 16px;
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
#us-booking-error {
|
|
color: #c00;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
/*
|
|
* The upcoming-lessons panel. Every rule here is scoped under #us-booking-app —
|
|
* the same id-level specificity .us-slot above uses — because these rows sit in
|
|
* whatever layout the theme provides and carry more content than a calendar
|
|
* cell. Bare class selectors lost to theme rules on div/span/strong, which
|
|
* collapsed the flex layout and piled the details on top of the actions.
|
|
*/
|
|
#us-booking-app .us-my-lessons {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson {
|
|
box-sizing: border-box;
|
|
max-width: 100%;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 12px 16px;
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 8px 12px;
|
|
}
|
|
|
|
/*
|
|
* `min-width: 0` lets the title column shrink below its content width — without
|
|
* it a long offering title cannot compress and shoves the status pill and
|
|
* Cancel button out of the row. The flex-basis keeps the details and the
|
|
* actions on one line while there is room, and wraps them once there is not.
|
|
*/
|
|
#us-booking-app .us-my-lesson-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
flex: 1 1 14em;
|
|
min-width: 0;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-title,
|
|
#us-booking-app .us-my-lesson-when {
|
|
overflow-wrap: break-word;
|
|
word-break: break-word;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-title {
|
|
font-size: 1.05em;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-duration {
|
|
font-weight: normal;
|
|
color: #666;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-when {
|
|
color: #555;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
#us-booking-app .us-show-all-lessons {
|
|
background: transparent;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
padding: 6px 14px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#us-booking-app .us-show-all-lessons:hover {
|
|
border-color: #888;
|
|
}
|
|
|
|
#us-booking-app .us-cancel-lesson {
|
|
background: transparent;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
padding: 4px 12px;
|
|
cursor: pointer;
|
|
color: #c00;
|
|
}
|
|
|
|
#us-booking-app .us-cancel-lesson:hover {
|
|
border-color: #c00;
|
|
}
|
|
|
|
#us-booking-app .us-lesson-status {
|
|
display: inline-block;
|
|
font-size: 0.85em;
|
|
font-weight: 600;
|
|
padding: 2px 10px;
|
|
border-radius: 10px;
|
|
background: #eee;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
#us-booking-app .us-lesson-status-confirmed {
|
|
background: #e2f5e5;
|
|
color: #1a7d2e;
|
|
}
|
|
|
|
#us-booking-app .us-lesson-status-pending {
|
|
background: #fdf3d7;
|
|
color: #8a6d1a;
|
|
}
|
|
|
|
.us-calendar-controls {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.us-filter-toggle {
|
|
padding: 6px 16px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.us-filter-toggle.us-active {
|
|
background: #333;
|
|
border-color: #333;
|
|
color: #fff;
|
|
}
|
|
|
|
.us-type-filter {
|
|
margin-bottom: 12px;
|
|
padding: 8px 12px;
|
|
border: 1px solid #eee;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.us-type-filter-heading {
|
|
display: block;
|
|
margin-bottom: 6px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-type-filter-choices {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 8px 16px;
|
|
}
|
|
|
|
.us-type-filter-choice {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.us-type-filter-clear {
|
|
margin-left: auto;
|
|
padding: 4px 12px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.us-view-toggle {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.us-view-toggle button {
|
|
padding: 6px 16px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.us-view-toggle button.us-active {
|
|
background: #333;
|
|
border-color: #333;
|
|
color: #fff;
|
|
}
|
|
|
|
.us-week-nav {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.us-week-nav button {
|
|
padding: 6px 12px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.us-week-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(7, 1fr);
|
|
gap: 8px;
|
|
}
|
|
|
|
.us-week-day {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
min-height: 90px;
|
|
}
|
|
|
|
.us-week-day-heading {
|
|
margin: 0 0 8px;
|
|
font-size: 0.85em;
|
|
text-align: center;
|
|
}
|
|
|
|
.us-week-slot {
|
|
display: block;
|
|
width: 100%;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.us-week-empty {
|
|
display: block;
|
|
text-align: center;
|
|
opacity: 0.4;
|
|
}
|
|
|
|
/* The price and pay agreement on a booking / enrolment form. */
|
|
.us-price {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 12px 16px;
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.us-price h4 {
|
|
margin: 0 0 8px;
|
|
}
|
|
|
|
.us-price p {
|
|
margin: 0 0 4px;
|
|
}
|
|
|
|
.us-price-amount strong {
|
|
font-size: 1.15em;
|
|
}
|
|
|
|
.us-price-cadence {
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.us-price-tax,
|
|
.us-price-note {
|
|
font-size: 0.9em;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.us-price-agree {
|
|
display: block;
|
|
margin-top: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* The cadence-carrying price on a group-class card. */
|
|
.us-class-price {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Policy acceptance — booking, enrolment, and signup all render this markup. */
|
|
.us-policy {
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.us-policy h4 {
|
|
margin: 0 0 6px;
|
|
}
|
|
|
|
/*
|
|
* The body is admin-authored HTML sitting inside whatever layout the theme
|
|
* provides, so it gets an explicit reading box rather than inheriting one.
|
|
* `overflow-wrap` breaks pasted URLs instead of letting one long token force
|
|
* the horizontal scrollbar, and the bounded height keeps a long policy from
|
|
* pushing the accept checkbox off the screen.
|
|
*/
|
|
.us-policy-body {
|
|
box-sizing: border-box;
|
|
max-width: 100%;
|
|
max-height: 260px;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding: 12px 14px;
|
|
margin-bottom: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background: #fafafa;
|
|
white-space: normal;
|
|
overflow-wrap: break-word;
|
|
word-break: break-word;
|
|
line-height: 1.5;
|
|
text-align: left;
|
|
}
|
|
|
|
.us-policy-body p,
|
|
.us-policy-body ul,
|
|
.us-policy-body ol {
|
|
margin: 0 0 0.75em;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.us-policy-body ul,
|
|
.us-policy-body ol {
|
|
padding-left: 1.5em;
|
|
}
|
|
|
|
.us-policy-body > :last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.us-policy-accept,
|
|
.us-policies input[type="checkbox"] {
|
|
margin-right: 6px;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.us-week-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.us-week-day {
|
|
min-height: 0;
|
|
}
|
|
|
|
/*
|
|
* A lesson row carries a title, a date/time, a status pill and a button —
|
|
* more than fits one narrow line, so stack the details above the actions
|
|
* rather than letting them wrap into each other.
|
|
*/
|
|
#us-booking-app .us-my-lesson {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
#us-booking-app .us-my-lesson-info {
|
|
flex: 0 0 auto;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* "Who is this for?" picker — booking and enrolment. Present only on an account
|
|
* that books for more than one person, so it is styled as a normal field rather
|
|
* than a callout.
|
|
*/
|
|
.us-student-picker select {
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Whose lesson a row in the upcoming panel is — only shown on a family account. */
|
|
.us-my-lesson-who {
|
|
font-weight: normal;
|
|
opacity: 0.75;
|
|
}
|
|
|
|
/* Parent/guardian signup: the child blocks revealed by the checkbox. */
|
|
.us-guardian {
|
|
margin: 16px 0;
|
|
padding: 12px 14px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.us-guardian legend {
|
|
padding: 0 6px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-children-intro {
|
|
margin-top: 0;
|
|
font-size: 0.9em;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/*
|
|
* Each child is a bordered group so a family of three does not read as one long
|
|
* undifferentiated column of fields.
|
|
*/
|
|
.us-child {
|
|
margin-bottom: 12px;
|
|
padding: 10px 12px;
|
|
border-left: 3px solid #ddd;
|
|
background: #fafafa;
|
|
}
|
|
|
|
.us-child > p:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* The guardian's manage-children screen ([us_family]). */
|
|
.us-family-list {
|
|
margin: 0 0 20px;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.us-family-child {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px 12px;
|
|
align-items: baseline;
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.us-family-child-name {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.us-family-child-dob {
|
|
font-size: 0.9em;
|
|
opacity: 0.75;
|
|
}
|
|
|
|
/*
|
|
* The actions sit at the far end of the row. Remove is its own form (it posts),
|
|
* so it is forced inline rather than taking a block of its own.
|
|
*/
|
|
.us-family-child-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: baseline;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.us-family-remove {
|
|
display: inline;
|
|
}
|
|
|
|
/* The editing row replaces the child's line, so it spans the whole width. */
|
|
.us-family-edit {
|
|
width: 100%;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
/* A name, a date and two actions do not fit one narrow line. */
|
|
.us-family-child-actions {
|
|
margin-left: 0;
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
/* Shown only in block-editor previews (see BlockPreview). */
|
|
.us-editor-note {
|
|
font-size: 0.85em;
|
|
font-style: italic;
|
|
opacity: 0.7;
|
|
}
|