@reblu/site-blocks 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +126 -2
- package/dist/client.cjs +372 -45
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +78 -2
- package/dist/client.d.ts +78 -2
- package/dist/client.js +369 -44
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +440 -305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -4
- package/dist/index.d.ts +68 -4
- package/dist/index.js +438 -305
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @reblu/site-blocks
|
|
2
2
|
|
|
3
|
+
## 0.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 76e64f3: Free-preview lessons for the public, and a redesigned course programme (REB-1135).
|
|
8
|
+
- New RSC-safe `LessonPreview`: the hosted video player in an `<iframe>` plus the lesson body through the sanitising Markdown pipeline, over `getLessonPreview()` from `@reblu/site-next/academy-server`. Spanish copy overridable via `DEFAULT_LESSON_PREVIEW_LABELS`. The SITE's CSP needs `frame-src https://player.mux.com`.
|
|
9
|
+
- `CourseSyllabus` redesign: rotating chevron icon, a video/reading icon per lesson, lesson durations as «m:ss» / «h:mm:ss» aligned right, the course total in the summary, «Expandir todo / Contraer todo», and «Vista previa gratuita» as a call to action through the new `getPreviewHref`, `previewHrefTemplate` or `onPreview` props. Every existing `data-slot` is kept; new slots: `course-syllabus-header`, `course-syllabus-toggle-all`, `course-syllabus-chevron`, `course-syllabus-lesson-icon`, `course-syllabus-lesson-duration`, `course-syllabus-preview`. New labels: `expandAll`, `collapseAll`, `videoLesson`, `textLesson`. Visible change: lesson durations read «10:00» instead of «10 min».
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [76e64f3]
|
|
14
|
+
- @reblu/site-contracts@1.21.0
|
|
15
|
+
|
|
16
|
+
## 0.20.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- 4ca8816: New `CourseSyllabus` on `@reblu/site-blocks/client`: the programme of a public course page over `AcademyCourseDetail.modules`. The header shows the number of modules and lessons. Each module with lessons is an expandable button (`aria-expanded`, `aria-controls`) showing its lesson count and total duration, and lists its published lessons in order with their duration and free-preview badge. Modules without lessons render as a plain row. Spanish default copy, overridable through `labels` (`DEFAULT_COURSE_SYLLABUS_LABELS`).
|
|
21
|
+
- 25d8189: Automatic staff discount (REB-1131). When the buyer has a server-validated staff session, base-api applies the active «Solo personal» code that gives the largest discount, with no code typed. Buyers without a staff session see no change.
|
|
22
|
+
- `@reblu/site-contracts`: adds the `ShopStaffAutoDiscount` type, `{ code, label, amount }`. It is an optional `autoDiscount` field on `ShopCartValidation`, `ShopDiscountApplication` and `ShopCheckoutSession`, and is absent for everyone else.
|
|
23
|
+
- `@reblu/site-next`: `createShopValidateCartRoute()` now forwards the `sb-*` auth cookies, the same as checkout and apply-discount. Opt out with `{ forwardAuthCookies: false }`. `validateCart()` returns `outcome.autoDiscount` when the server applied one.
|
|
24
|
+
- `@reblu/site-blocks`: `CartSummary` and `CheckoutForm` accept `autoDiscount` and show «Descuento de personal aplicado» with its amount. `CheckoutForm` shows no remove action for it, and the code field stays available. The text can be changed with `labels.staffDiscountApplied`. Without the prop, both components render exactly as before.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 6a0f8e6: `OrderConfirmation`: the default `foundDescription` now reads «Te hemos enviado un email con tu acceso.» Every shop buyer gets a customer account, and the order confirmation email carries a one-time link into it, where paid digital downloads live. The confirmation page never serves files. A SITE that passes its own `labels.foundDescription` is unaffected.
|
|
29
|
+
- Updated dependencies [25d8189]
|
|
30
|
+
- @reblu/site-contracts@1.20.0
|
|
31
|
+
|
|
3
32
|
## 0.19.0
|
|
4
33
|
|
|
5
34
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ colour on the red background. See `docs/theming/theming.md`.
|
|
|
25
25
|
| `sanitizeHtml` | `.` | Sanitises an HTML string before `dangerouslySetInnerHTML` — the **HTML boundary**. See "Security boundaries" below. |
|
|
26
26
|
| `MarkdownRenderer` | `.` | Canonical Blog renderer: `marked` → **hast-util-sanitize** (XSS) → component map, with a YouTube-domain allowlist. RSC-safe. |
|
|
27
27
|
| `CourseDescription` | `.` | Drop-in for the rich-text course `description` (`client.academy.bySlug()`). Server Component. See "Rich-text descriptions". |
|
|
28
|
+
| `LessonPreview`, `DEFAULT_LESSON_PREVIEW_LABELS` | `.` | A public free-preview lesson: hosted video player + sanitised body (`getLessonPreview()`). Server Component. See "Free-preview lesson". |
|
|
28
29
|
| `Treatment`, `ParentPage`, `ProfileCard`, `ProductCard` | `.` | Presentational organisms over the SITE API contracts. RSC-safe. `Specialty`: deprecated alias, see below. |
|
|
29
30
|
| `ProfessionalFields` | `.` | The tenant's own professional fields (`professional.fields`) on a team card or profile. RSC-safe. See below. |
|
|
30
31
|
| `ShopFilters` | `.` | Plug-and-play shop filter chrome over `GET /api/site/shop/facets`. RSC-safe. See "Shop filters" below. |
|
|
@@ -39,6 +40,7 @@ colour on the red background. See `docs/theming/theming.md`.
|
|
|
39
40
|
| `AddToCartButton`, `CartIcon` | `./client` | Add-to-cart feedback (REB-837) — **moved here from `.`**, see below. |
|
|
40
41
|
| `DataRequestForm` | `./client` | Drop-in public GDPR rights form over `POST /api/site/data-requests` (REB-980). See "GDPR rights form" below. |
|
|
41
42
|
| `ProductCarousel` | `./client` | Scroll-snap product strip with prev/next arrows («Productos relacionados»). See "Product carousel" below. |
|
|
43
|
+
| `CourseSyllabus`, `DEFAULT_COURSE_SYLLABUS_LABELS` | `./client` | A course's programme: expandable modules listing their lessons (`client.academy.bySlug()`). See "Course programme" below. |
|
|
42
44
|
|
|
43
45
|
## Boundary
|
|
44
46
|
|
|
@@ -324,13 +326,31 @@ for the wiring steps and a full example app).
|
|
|
324
326
|
| `AddedToCartToast` | `.` | `productName`, `cartHref`, `status?`, `quantity?`, `imageUrl?` | Yes |
|
|
325
327
|
| `CartLineList` | `.` | `items`, `onQuantityChange?`, `onRemove?`, `liveMessage?`, `notice?`, `lineMessage?` | Yes |
|
|
326
328
|
| `CartAdjustmentsNotice` | `.` | `removed?`, `quantityChanges?`, `priceChanges?`, `labels?` | Yes |
|
|
327
|
-
| `CartSummary` | `.` | `subtotal`, `discount?`, `shipping?`, `shippingEnabled?`, `total`, `notice?`
|
|
329
|
+
| `CartSummary` | `.` | `subtotal`, `discount?`, `autoDiscount?`, `shipping?`, `shippingEnabled?`, `total`, `notice?` | Yes |
|
|
328
330
|
| `CheckoutLoginPrompt` | `.` | `open`, `email`, `onEmailChange`, `onClose`, `onSubmitEmail`, `onContinueAsGuest` | Yes (renders `null` when `open` is false) |
|
|
329
|
-
| `CheckoutForm` | `.` | `contact`, `onContactChange`, `discountCode`, `onDiscountCodeChange`, `onApplyDiscount`, `onRemoveDiscount`, `onSubmit` | Yes |
|
|
331
|
+
| `CheckoutForm` | `.` | `contact`, `onContactChange`, `discountCode`, `onDiscountCodeChange`, `onApplyDiscount`, `onRemoveDiscount`, `onSubmit`, `autoDiscount?` | Yes |
|
|
330
332
|
| `OrderConfirmation` | `.` | `status`, `order`, `cartHref?`, `onRecheck?` | Yes |
|
|
331
333
|
| `OrderInvoiceLink` | `.` | `invoice` (`order.invoice`), `labels?` | Yes (renders `null` without an invoice) |
|
|
332
334
|
| `FullInvoiceFields` | `.` | `value`, `onChange`, `overCap`, `errors?`, `countries?`, `labels?` | Yes (controlled, no hooks) |
|
|
333
335
|
|
|
336
|
+
**Automatic staff discount (REB-1131).** For a signed-in staff member,
|
|
337
|
+
base-api applies the best «Solo personal» code by itself and reports it as
|
|
338
|
+
`autoDiscount` on validate-cart, apply-discount and checkout. Pass it to both
|
|
339
|
+
components and they show «Descuento de personal aplicado» with its amount
|
|
340
|
+
(override with `labels.staffDiscountApplied`). `CheckoutForm` offers no remove
|
|
341
|
+
action for it and keeps the code field: a typed code replaces it only when the
|
|
342
|
+
server finds it larger. Without `autoDiscount`, both render exactly as before.
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
const outcome = await validateCart() // @reblu/site-next/cart
|
|
346
|
+
// After an apply-discount call, prefer its `autoDiscount` (absent when the
|
|
347
|
+
// typed code won).
|
|
348
|
+
const autoDiscount = discountResponse?.autoDiscount ?? outcome.autoDiscount ?? null
|
|
349
|
+
|
|
350
|
+
<CartSummary subtotal={subtotal} autoDiscount={autoDiscount} total={subtotal - (autoDiscount?.amount ?? 0)} />
|
|
351
|
+
<CheckoutForm {...checkoutProps} autoDiscount={autoDiscount} />
|
|
352
|
+
```
|
|
353
|
+
|
|
334
354
|
**REB-837 breaking move**: `AddToCartButton` and `CartIcon` now carry real
|
|
335
355
|
internal state (the confirmed/error timer, the bump-on-add guard) and ship
|
|
336
356
|
from `@reblu/site-blocks/client` instead of the root `.` entry. Update the
|
|
@@ -887,6 +907,110 @@ It renders the package's `ProductCard` by default. To use your own card, pass
|
|
|
887
907
|
`headingClassName` replaces the heading's typography (see the className rule
|
|
888
908
|
above). It renders nothing for an empty list.
|
|
889
909
|
|
|
910
|
+
## Course programme — `CourseSyllabus` (REB-1134, redesigned in REB-1135)
|
|
911
|
+
|
|
912
|
+
The programme of a public course page: every module, and inside each module the
|
|
913
|
+
lessons that make it up. The header reads «Programa» and «3 módulos · 12
|
|
914
|
+
lecciones · 2 h 40 min», with an «Expandir todo / Contraer todo» control. Each
|
|
915
|
+
module row shows a number badge, its lesson count and, when every lesson in it
|
|
916
|
+
has a duration, the module's total («4 lecciones · 1 h 20 min»), and a chevron
|
|
917
|
+
that rotates when open. Opening a module lists its lessons in order: a video or
|
|
918
|
+
reading icon, the title, the duration right-aligned as «m:ss» / «h:mm:ss», and on
|
|
919
|
+
free-preview lessons «Vista previa gratuita» as a call to action.
|
|
920
|
+
|
|
921
|
+
```tsx
|
|
922
|
+
import { CourseSyllabus } from '@reblu/site-blocks/client'
|
|
923
|
+
|
|
924
|
+
// `course` is an `AcademyCourseDetail` from `client.academy.bySlug(slug)`.
|
|
925
|
+
// From a Server Component page, use the serialisable template:
|
|
926
|
+
<CourseSyllabus
|
|
927
|
+
modules={course.modules}
|
|
928
|
+
previewHrefTemplate={`/formacion/${course.slug}/preview/{lessonSlug}`}
|
|
929
|
+
/>
|
|
930
|
+
|
|
931
|
+
// From a Client Component you can pass functions instead:
|
|
932
|
+
<CourseSyllabus
|
|
933
|
+
modules={course.modules}
|
|
934
|
+
getPreviewHref={(lesson) => `/formacion/${course.slug}/preview/${lesson.slug}`}
|
|
935
|
+
// or: onPreview={(lesson) => openPreviewModal(lesson.slug)}
|
|
936
|
+
/>
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
- **Free preview call to action.** `getPreviewHref(lesson)` (or the
|
|
940
|
+
serialisable `previewHrefTemplate`, with `{lessonSlug}` and `{lessonId}`)
|
|
941
|
+
renders a link; `onPreview(lesson)` renders a button (e.g. to open a modal).
|
|
942
|
+
Both apply only to lessons with `isFreePreview`. With neither, the flag is a
|
|
943
|
+
plain label. The link/button is named «Vista previa gratuita: {título}». Point
|
|
944
|
+
it at a page that renders `LessonPreview` (see "Free-preview lesson").
|
|
945
|
+
- **Public data only.** It renders what the API serves. The API returns
|
|
946
|
+
published lessons only, and never a video, playback or attachment id, a
|
|
947
|
+
signed URL or the lesson content.
|
|
948
|
+
- **Accessibility.** The block is a `region` named by its heading. A module
|
|
949
|
+
with lessons is a `<button>` with `aria-expanded` and `aria-controls`
|
|
950
|
+
pointing at its lesson list. Tab reaches «Expandir todo», then each module;
|
|
951
|
+
Enter and Space open and close it, and an open module's preview links follow
|
|
952
|
+
it in the tab order. Modules open independently and start closed. Icons are
|
|
953
|
+
`aria-hidden`; each lesson's type is announced as «Vídeo» / «Lectura».
|
|
954
|
+
Durations are `<time datetime="PT10M0S">`.
|
|
955
|
+
- **Modules without lessons** render as a plain row with the title, not a
|
|
956
|
+
button. A payload without `lessons` renders the same way.
|
|
957
|
+
- **Copy.** The defaults are Spanish (`DEFAULT_COURSE_SYLLABUS_LABELS`).
|
|
958
|
+
Override any of them with `labels={{ heading, moduleSingular, modulePlural,
|
|
959
|
+
lessonSingular, lessonPlural, freePreview, hours, minutes, expandAll,
|
|
960
|
+
collapseAll, videoLesson, textLesson }}`.
|
|
961
|
+
- **Styling.** Uses only the SDK tokens (`border`, `card`, `muted`, `primary`,
|
|
962
|
+
`ring`). `headingLevel` (default `2`), `headingClassName` (replaces the
|
|
963
|
+
heading's typography, see the className rule above) and `className`. Style
|
|
964
|
+
the parts with `[data-slot="course-syllabus"]`, `course-syllabus-header`,
|
|
965
|
+
`course-syllabus-summary`, `course-syllabus-toggle-all`,
|
|
966
|
+
`course-syllabus-module`, `course-syllabus-chevron`, `course-syllabus-lessons`,
|
|
967
|
+
`course-syllabus-lesson`, `course-syllabus-lesson-icon` (`data-type="video" |
|
|
968
|
+
"text"`), `course-syllabus-lesson-title`, `course-syllabus-lesson-duration`
|
|
969
|
+
and `course-syllabus-preview`. No slot was renamed in REB-1135. It renders
|
|
970
|
+
nothing for a course without modules.
|
|
971
|
+
|
|
972
|
+
## Free-preview lesson — `LessonPreview` (REB-1135)
|
|
973
|
+
|
|
974
|
+
A lesson the owner marked «Vista previa gratuita», for visitors who have not
|
|
975
|
+
paid or enrolled: the video and the lesson body. Fetch it on the server with
|
|
976
|
+
`getLessonPreview` (`@reblu/site-next/academy-server`), which returns `null`
|
|
977
|
+
for any lesson the public may not see.
|
|
978
|
+
|
|
979
|
+
```tsx
|
|
980
|
+
// app/formacion/[slug]/preview/[lessonSlug]/page.tsx — a Server Component
|
|
981
|
+
import { notFound } from 'next/navigation'
|
|
982
|
+
import { LessonPreview } from '@reblu/site-blocks'
|
|
983
|
+
import { getLessonPreview } from '@reblu/site-next/academy-server'
|
|
984
|
+
import { client } from '@/lib/reblu' // your server-side RebluClient
|
|
985
|
+
|
|
986
|
+
export const dynamic = 'force-dynamic' // the video token is short-lived: never ISR
|
|
987
|
+
|
|
988
|
+
export default async function Page({ params }) {
|
|
989
|
+
const { slug, lessonSlug } = await params
|
|
990
|
+
const lesson = await getLessonPreview(client, slug, lessonSlug)
|
|
991
|
+
if (!lesson) notFound()
|
|
992
|
+
return <LessonPreview lesson={lesson} headingLevel={1} />
|
|
993
|
+
}
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
- **Player.** `video.embedUrl` is the provider's hosted player, already carrying
|
|
997
|
+
its short-lived token, rendered in an `<iframe>` named «Vídeo: {título}» —
|
|
998
|
+
no video library in the SDK. Only `https:` URLs are embedded. Add
|
|
999
|
+
`frame-src https://player.mux.com` to the SITE's Content-Security-Policy.
|
|
1000
|
+
`video` is `null` for a text-only lesson (no player is rendered).
|
|
1001
|
+
- **Body.** `content` is Markdown, rendered through the same sanitising
|
|
1002
|
+
pipeline as `CourseDescription`: no raw HTML, no scripts, no `javascript:`
|
|
1003
|
+
links. Its headings sit one level below the lesson title. An empty body
|
|
1004
|
+
renders nothing.
|
|
1005
|
+
- **Semantics.** An `<article>` named by the title; the duration is a
|
|
1006
|
+
`<time>`. Server Component (RSC-safe), from the root entry.
|
|
1007
|
+
- **Copy.** Spanish defaults (`DEFAULT_LESSON_PREVIEW_LABELS`), override with
|
|
1008
|
+
`labels={{ badge, player, duration }}`.
|
|
1009
|
+
- **Styling.** `headingLevel` (default `2`), `headingId`, `headingClassName`,
|
|
1010
|
+
`contentClassName`, `className`; slots `lesson-preview`,
|
|
1011
|
+
`lesson-preview-badge`, `lesson-preview-duration`, `lesson-preview-player`,
|
|
1012
|
+
`lesson-preview-content`.
|
|
1013
|
+
|
|
890
1014
|
## Page blocks
|
|
891
1015
|
|
|
892
1016
|
`PageBlocks` renders a page's registry blocks (`PageDetail.blocks` from
|