Compare commits
6
Commits
v1.2.3
..
da985c7f71
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da985c7f71 | ||
|
|
7c91e1eef7
|
||
|
|
f3917d0784 | ||
|
|
b508ab92f8
|
||
|
|
2a661a10ff | ||
|
|
95df78d384 |
@@ -11,6 +11,11 @@ When a `v*` tag is pushed, `.gitea/workflows/release.yml` publishes the matching
|
|||||||
the plugin to the next patch version and adds a fresh section here for it. Record
|
the plugin to the next patch version and adds a fresh section here for it. Record
|
||||||
each change under the current top section as you work.
|
each change under the current top section as you work.
|
||||||
|
|
||||||
|
## [1.2.4]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- A student's **upcoming lessons no longer pile on top of each other**. On the booking page, the lesson name, its date and time, the status badge and the **Cancel** button could render over one another instead of sitting in a tidy row — worst with a long lesson-type name, and on narrow screens, where the row had no phone layout at all. The panel now keeps its shape whatever the theme around it does, long names wrap instead of shoving the Cancel button out of the row, and on a phone the lesson details stack above the buttons.
|
||||||
|
|
||||||
## [1.2.3]
|
## [1.2.3]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
+57
-16
@@ -34,47 +34,72 @@
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-my-lessons {
|
/*
|
||||||
|
* 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;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-my-lesson {
|
#us-booking-app .us-my-lesson {
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-width: 100%;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 8px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-my-lesson-info {
|
/*
|
||||||
|
* `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;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
flex: 1 1 14em;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-my-lesson-title {
|
#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;
|
font-size: 1.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-my-lesson-duration {
|
#us-booking-app .us-my-lesson-duration {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-my-lesson-when {
|
#us-booking-app .us-my-lesson-when {
|
||||||
color: #555;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-my-lesson-actions {
|
#us-booking-app .us-my-lesson-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-show-all-lessons {
|
#us-booking-app .us-show-all-lessons {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -82,11 +107,11 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-show-all-lessons:hover {
|
#us-booking-app .us-show-all-lessons:hover {
|
||||||
border-color: #888;
|
border-color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-cancel-lesson {
|
#us-booking-app .us-cancel-lesson {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -95,24 +120,26 @@
|
|||||||
color: #c00;
|
color: #c00;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-cancel-lesson:hover {
|
#us-booking-app .us-cancel-lesson:hover {
|
||||||
border-color: #c00;
|
border-color: #c00;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-lesson-status {
|
#us-booking-app .us-lesson-status {
|
||||||
|
display: inline-block;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-lesson-status-confirmed {
|
#us-booking-app .us-lesson-status-confirmed {
|
||||||
background: #e2f5e5;
|
background: #e2f5e5;
|
||||||
color: #1a7d2e;
|
color: #1a7d2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-lesson-status-pending {
|
#us-booking-app .us-lesson-status-pending {
|
||||||
background: #fdf3d7;
|
background: #fdf3d7;
|
||||||
color: #8a6d1a;
|
color: #8a6d1a;
|
||||||
}
|
}
|
||||||
@@ -345,6 +372,20 @@
|
|||||||
.us-week-day {
|
.us-week-day {
|
||||||
min-height: 0;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Shown only in block-editor previews (see BlockPreview). */
|
/* Shown only in block-editor previews (see BlockPreview). */
|
||||||
|
|||||||
@@ -582,16 +582,19 @@
|
|||||||
function lessonRowHtml(l) {
|
function lessonRowHtml(l) {
|
||||||
const title = l.offering_title ? escHtml(String(l.offering_title)) : 'Lesson';
|
const title = l.offering_title ? escHtml(String(l.offering_title)) : 'Lesson';
|
||||||
const duration = l.duration_minutes ? ` <span class="us-my-lesson-duration">(${escHtml(String(l.duration_minutes))} min)</span>` : '';
|
const duration = l.duration_minutes ? ` <span class="us-my-lesson-duration">(${escHtml(String(l.duration_minutes))} min)</span>` : '';
|
||||||
|
// The two columns are divs, not spans: as spans the layout only held up
|
||||||
|
// while the stylesheet's display:flex won, and a theme rule on span
|
||||||
|
// collapsed the row onto itself.
|
||||||
return `
|
return `
|
||||||
<div class="us-my-lesson">
|
<div class="us-my-lesson">
|
||||||
<span class="us-my-lesson-info">
|
<div class="us-my-lesson-info">
|
||||||
<strong class="us-my-lesson-title">${title}${duration}</strong>
|
<strong class="us-my-lesson-title">${title}${duration}</strong>
|
||||||
<span class="us-my-lesson-when">${escHtml(dayLabel(dayKey(l.start_dt)))} · ${escHtml(timeOf(l.start_dt))}–${escHtml(timeOf(l.end_dt))}</span>
|
<span class="us-my-lesson-when">${escHtml(dayLabel(dayKey(l.start_dt)))} · ${escHtml(timeOf(l.start_dt))}–${escHtml(timeOf(l.end_dt))}</span>
|
||||||
</span>
|
</div>
|
||||||
<span class="us-my-lesson-actions">
|
<div class="us-my-lesson-actions">
|
||||||
<span class="us-lesson-status us-lesson-status-${escHtml(String(l.status))}">${escHtml(lessonStatusLabel(String(l.status)))}</span>
|
<span class="us-lesson-status us-lesson-status-${escHtml(String(l.status))}">${escHtml(lessonStatusLabel(String(l.status)))}</span>
|
||||||
<button type="button" class="us-cancel-lesson" data-lesson-id="${l.id}">Cancel</button>
|
<button type="button" class="us-cancel-lesson" data-lesson-id="${l.id}">Cancel</button>
|
||||||
</span>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ instructor may only open their own lessons; the studio **Scheduler** may open an
|
|||||||
- Admin lesson detail presenter: `Unsupervised\Schedular\Booking\LessonDetail` (per-lesson intake answers + policy acceptances), template `templates/admin/lesson-detail.php`
|
- Admin lesson detail presenter: `Unsupervised\Schedular\Booking\LessonDetail` (per-lesson intake answers + policy acceptances), template `templates/admin/lesson-detail.php`
|
||||||
- REST endpoint: `Unsupervised\Schedular\Booking\BookingEndpoint`
|
- REST endpoint: `Unsupervised\Schedular\Booking\BookingEndpoint`
|
||||||
- Frontend: `Unsupervised\Schedular\Booking\BookingPage`, `Unsupervised\Schedular\Auth\LoginPage`
|
- Frontend: `Unsupervised\Schedular\Booking\BookingPage`, `Unsupervised\Schedular\Auth\LoginPage`
|
||||||
|
- Upcoming-lessons panel: rendered client-side into `#us-my-lessons` by `assets/js/booking.js` (`lessonRowHtml`/`renderMyLessons`), mirrored for the editor by `BlockPreview::upcomingLessons()` — keep the two markup shapes in step.
|
||||||
|
|
||||||
> **Payment seam:** a priced booking is created with `status = pending` and its
|
> **Payment seam:** a priced booking is created with `status = pending` and its
|
||||||
> payment linked via `payment_id`; the lesson is confirmed when the payment is
|
> payment linked via `payment_id`; the lesson is confirmed when the payment is
|
||||||
@@ -161,6 +162,14 @@ instructor may only open their own lessons; the studio **Scheduler** may open an
|
|||||||
> Unpriced bookings skip the seam entirely and are confirmed at creation.
|
> Unpriced bookings skip the seam entirely and are confirmed at creation.
|
||||||
> `GET /policies?scope=booking` returns just the booking-gate policies the form
|
> `GET /policies?scope=booking` returns just the booking-gate policies the form
|
||||||
> must collect.
|
> must collect.
|
||||||
|
>
|
||||||
|
> **Frontend CSS scoping:** every rule for the booking page's own markup is
|
||||||
|
> written under `#us-booking-app` (`assets/css/frontend.css`). These panels sit
|
||||||
|
> inside whatever layout the active theme provides, and bare class selectors lose
|
||||||
|
> to theme rules on `div`/`span`/`strong` — which flattens the flex layout and
|
||||||
|
> renders the lesson details on top of the actions. The row's two columns are
|
||||||
|
> `div`s for the same reason: the layout must not depend on overriding the
|
||||||
|
> inline default. New booking-page rules should follow both conventions.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
- `tests/Unit/Booking/BookingRepositoryTest.php`
|
- `tests/Unit/Booking/BookingRepositoryTest.php`
|
||||||
|
|||||||
@@ -86,13 +86,13 @@ class BlockPreview {
|
|||||||
private static function upcomingLessons(): string {
|
private static function upcomingLessons(): string {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<div class="us-my-lessons"><h3>%s</h3>'
|
'<div class="us-my-lessons"><h3>%s</h3>'
|
||||||
. '<div class="us-my-lesson"><span class="us-my-lesson-info">'
|
. '<div class="us-my-lesson"><div class="us-my-lesson-info">'
|
||||||
. '<strong class="us-my-lesson-title">%s <span class="us-my-lesson-duration">(30 min)</span></strong>'
|
. '<strong class="us-my-lesson-title">%s <span class="us-my-lesson-duration">(30 min)</span></strong>'
|
||||||
. '<span class="us-my-lesson-when">%s</span></span>'
|
. '<span class="us-my-lesson-when">%s</span></div>'
|
||||||
. '<span class="us-my-lesson-actions">'
|
. '<div class="us-my-lesson-actions">'
|
||||||
. '<span class="us-lesson-status us-lesson-status-confirmed">%s</span>'
|
. '<span class="us-lesson-status us-lesson-status-confirmed">%s</span>'
|
||||||
. '<button type="button" class="us-cancel-lesson" disabled>%s</button>'
|
. '<button type="button" class="us-cancel-lesson" disabled>%s</button>'
|
||||||
. '</span></div></div>',
|
. '</div></div></div>',
|
||||||
esc_html__( 'Your upcoming lessons', 'unsupervised-schedular' ),
|
esc_html__( 'Your upcoming lessons', 'unsupervised-schedular' ),
|
||||||
esc_html__( 'Piano Lesson', 'unsupervised-schedular' ),
|
esc_html__( 'Piano Lesson', 'unsupervised-schedular' ),
|
||||||
esc_html__( 'Monday · 4:00 PM–4:30 PM', 'unsupervised-schedular' ),
|
esc_html__( 'Monday · 4:00 PM–4:30 PM', 'unsupervised-schedular' ),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: Unsupervised Scheduler
|
* Plugin Name: Unsupervised Scheduler
|
||||||
* Plugin URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler
|
* Plugin URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler
|
||||||
* Description: Instructor/student lesson scheduling for WordPress.
|
* Description: Instructor/student lesson scheduling for WordPress.
|
||||||
* Version: 1.2.3
|
* Version: 1.2.4
|
||||||
* Requires at least: 6.2
|
* Requires at least: 6.2
|
||||||
* Requires PHP: 8.1
|
* Requires PHP: 8.1
|
||||||
* Author: Unsupervised
|
* Author: Unsupervised
|
||||||
@@ -21,7 +21,7 @@ if (! defined('ABSPATH')) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define('USC_VERSION', '1.2.3');
|
define('USC_VERSION', '1.2.4');
|
||||||
define('USC_PLUGIN_FILE', __FILE__);
|
define('USC_PLUGIN_FILE', __FILE__);
|
||||||
define('USC_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
define('USC_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
define('USC_PLUGIN_URL', plugin_dir_url(__FILE__));
|
define('USC_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||||
|
|||||||
Reference in New Issue
Block a user