@reblu/site-blocks 0.23.0 → 0.25.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 CHANGED
@@ -1,5 +1,31 @@
1
1
  # @reblu/site-blocks
2
2
 
3
+ ## 0.25.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1860554: New `CoursePurchaseTerms`: the drop-in for what a buyer must see before paying for a course (REB-1199). It shows the withdrawal waiver, the access duration and the refund guarantee from `AcademyCourseDetail`. Each term renders only when the course carries it; with none, nothing renders. It is a Server Component. The default copy is Spanish and can be replaced with `labels`. Also exports `DEFAULT_COURSE_PURCHASE_TERMS_LABELS`.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [1860554]
12
+ - Updated dependencies [82ab415]
13
+ - @reblu/site-contracts@1.25.0
14
+
15
+ ## 0.24.0
16
+
17
+ ### Minor Changes
18
+
19
+ - c7f3a43: Course catalogue filters are tenant config (REB-1234).
20
+ - `@reblu/site-contracts`: new `AcademyConfigPublic` and `AcademyCatalogFiltersPublic` types for `GET /api/site/academy/config`.
21
+ - `@reblu/site-client`: new `client.academy.config()`, returning `{ catalogFilters: { category, instructor, price } }` as switched on in nexus. A filter the tenant never set is `true`.
22
+ - `@reblu/site-blocks`: `CourseCatalogFilters` gains a category filter (`categories` + `hrefs.category`, `active.category`) and an `enabled` prop. Pass `catalogFilters` as `enabled` and only the switched-on filters render; with all off it renders nothing. Without `enabled` and `categories` the output is unchanged.
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies [c7f3a43]
27
+ - @reblu/site-contracts@1.24.0
28
+
3
29
  ## 0.23.0
4
30
 
5
31
  ### Minor Changes
package/README.md CHANGED
@@ -25,11 +25,12 @@ 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
+ | `CoursePurchaseTerms`, `DEFAULT_COURSE_PURCHASE_TERMS_LABELS` | `.` | What the buyer sees before paying: withdrawal waiver, access duration, refund guarantee. Server Component. See "Course purchase terms". |
28
29
  | `LessonPreview`, `DEFAULT_LESSON_PREVIEW_LABELS` | `.` | A public free-preview lesson: hosted video player + sanitised body (`getLessonPreview()`). Server Component. See "Free-preview lesson". |
29
30
  | `Treatment`, `ParentPage`, `ProfileCard`, `ProductCard` | `.` | Presentational organisms over the SITE API contracts. RSC-safe. `Specialty`: deprecated alias, see below. |
30
31
  | `ProfessionalFields` | `.` | The tenant's own professional fields (`professional.fields`) on a team card or profile. RSC-safe. See below. |
31
32
  | `ShopFilters` | `.` | Plug-and-play shop filter chrome over `GET /api/site/shop/facets`. RSC-safe. See "Shop filters" below. |
32
- | `CourseCatalogFilters`, `DEFAULT_COURSE_PRICE_BANDS` | `.` | Course catalogue filters by instructor and price (`client.academy.instructors()`). RSC-safe. See "Course catalogue filters". |
33
+ | `CourseCatalogFilters`, `DEFAULT_COURSE_PRICE_BANDS` | `.` | Course catalogue filters by category, instructor and price, each switchable per tenant (`client.academy.config()`). RSC-safe. See "Course catalogue filters". |
33
34
  | `CartLineList`, `CartSummary`, `CartAdjustmentsNotice`, `AddedToCartToast`, `CheckoutLoginPrompt`, `CheckoutForm`, `OrderConfirmation` | `.` | Cart & checkout organisms. RSC-safe by construction. See "Cart & checkout" below. |
34
35
  | `PageBlocks`, `FaqAccordion` | `.` | Registry-block renderer (`PageDetail.blocks`) and native `<details>` FAQ. Server Components. See "Page blocks" below. |
35
36
  | `ProductTrustItems`, `DEFAULT_TRUST_ICONS` | `.` | Store-wide trust messages from `ShopConfig.productTrustItems`. Server Component. See "Product trust items" below. |
@@ -991,13 +992,19 @@ import { CourseSyllabus } from '@reblu/site-blocks/client'
991
992
  and `course-syllabus-preview`. No slot was renamed in REB-1135. It renders
992
993
  nothing for a course without modules.
993
994
 
994
- ## Course catalogue filters — `CourseCatalogFilters` (REB-547)
995
+ ## Course catalogue filters — `CourseCatalogFilters` (REB-547, REB-1234)
995
996
 
996
- Drop-in filters for the academy catalogue: an instructor filter fed by
997
+ Drop-in filters for the academy catalogue: a category filter fed by
998
+ `client.academy.categories()`, an instructor filter fed by
997
999
  `client.academy.instructors()` (instructors of published courses, sorted by
998
1000
  name) and a price filter (bands in integer cents plus «Gratis»). Each option
999
- maps 1:1 onto a `client.academy.courses()` param: `instructor`, `priceMin`,
1000
- `priceMax`, `free`. Same RSC-safe contract as `ShopFilters`: every option is a
1001
+ maps 1:1 onto a `client.academy.courses()` param: `category`, `instructor`,
1002
+ `priceMin`, `priceMax`, `free`.
1003
+
1004
+ Which filters show is the tenant's choice, made in nexus › Formación ›
1005
+ Configuración › «Filtros del catálogo» and served by `client.academy.config()`
1006
+ as `catalogFilters`. Pass it as `enabled`: the component renders only the
1007
+ filters switched on, with no SITE code change when the tenant flips one. Same RSC-safe contract as `ShopFilters`: every option is a
1001
1008
  real `<a href>`; pass `onChange` only from a `'use client'` boundary.
1002
1009
 
1003
1010
  ```tsx
@@ -1013,14 +1020,17 @@ export default async function CoursesPage({
1013
1020
  const sp = await searchParams
1014
1021
  const num = (v?: string) => (v ? Number(v) : undefined)
1015
1022
  const active: AcademyCourseListQuery = {
1023
+ category: sp.category,
1016
1024
  instructor: sp.instructor,
1017
1025
  priceMin: num(sp.priceMin),
1018
1026
  priceMax: num(sp.priceMax),
1019
1027
  free: sp.free === 'true',
1020
1028
  }
1021
- const [courses, instructors] = await Promise.all([
1029
+ const [courses, categories, instructors, academy] = await Promise.all([
1022
1030
  client.academy.courses(active),
1031
+ client.academy.categories(),
1023
1032
  client.academy.instructors(),
1033
+ client.academy.config(),
1024
1034
  ])
1025
1035
 
1026
1036
  // Keep the other params; `free` and a price band exclude each other.
@@ -1035,10 +1045,14 @@ export default async function CoursesPage({
1035
1045
 
1036
1046
  return (
1037
1047
  <>
1048
+ {/* Your search box stays: it is not a switchable filter. */}
1038
1049
  <CourseCatalogFilters
1050
+ enabled={academy.catalogFilters}
1051
+ categories={categories.data}
1039
1052
  instructors={instructors.data}
1040
1053
  active={active}
1041
1054
  hrefs={{
1055
+ category: (slug) => href({ category: slug }),
1042
1056
  instructor: (id) => href({ instructor: id }),
1043
1057
  price: (min, max) => href({ priceMin: min, priceMax: max, free: false }),
1044
1058
  free: (next) => href({ free: next, priceMin: undefined, priceMax: undefined }),
@@ -1051,6 +1065,15 @@ export default async function CoursesPage({
1051
1065
  }
1052
1066
  ```
1053
1067
 
1068
+ - **Tenant switches.** `enabled` takes `{ category, instructor, price }`; a
1069
+ key that is missing counts as on, so leaving out `enabled` shows every filter
1070
+ (the behaviour before REB-1234). A tenant that never set the switches gets
1071
+ all three `true` from `client.academy.config()`. With every filter off the
1072
+ component renders nothing, and the search box (yours) stays.
1073
+ - **Category needs data and an href.** The category group shows only with
1074
+ `categories` and `hrefs.category`; without them the component behaves as
1075
+ before. An active category slug that is no longer listed keeps the group
1076
+ visible, so it can be cleared.
1054
1077
  - **Price is integer cents.** The default bands (`DEFAULT_COURSE_PRICE_BANDS`)
1055
1078
  are EUR: up to 50, 50 to 150, 150 to 300, from 300. Pass `priceBands` for
1056
1079
  another catalogue and `currencyCode` / `locale` for the labels.
@@ -1105,6 +1128,33 @@ export default async function Page({ params }) {
1105
1128
  `lesson-preview-badge`, `lesson-preview-duration`, `lesson-preview-player`,
1106
1129
  `lesson-preview-content`.
1107
1130
 
1131
+ ## Course purchase terms — `CoursePurchaseTerms` (REB-1199)
1132
+
1133
+ What a buyer must see before paying for a course, from `client.academy.bySlug()`:
1134
+
1135
+ - **Withdrawal waiver** (`withdrawalWaiverRequired`): checkout asks for a separate box in which the buyer requests immediate access and waives the 14-day withdrawal right.
1136
+ - **Access duration** (`accessDurationMonths`): months of access after enrolment. `null` means no end date, and nothing is shown.
1137
+ - **Refund guarantee** (`refundGuarantee`): the course's terms. `null` means none, and nothing is shown.
1138
+
1139
+ Each term renders only when the course carries it. A course with none renders nothing, not even the heading. It is a Server Component, RSC-safe. The copy is Spanish and neutral; replace any text with `labels`.
1140
+
1141
+ ```tsx
1142
+ import { CoursePurchaseTerms } from '@reblu/site-blocks'
1143
+
1144
+ const course = await client.academy.bySlug(slug)
1145
+
1146
+ <CoursePurchaseTerms course={course} heading='Antes de comprar' />
1147
+
1148
+ // In another language:
1149
+ <CoursePurchaseTerms
1150
+ course={course}
1151
+ labels={{
1152
+ withdrawalWaiver: 'Immediate access: you waive the 14-day withdrawal right.',
1153
+ accessDuration: (months) => `Access for ${months} months.`,
1154
+ }}
1155
+ />
1156
+ ```
1157
+
1108
1158
  ## Page blocks
1109
1159
 
1110
1160
  `PageBlocks` renders a page's registry blocks (`PageDetail.blocks` from