@reblu/site-blocks 0.23.0 → 0.24.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,19 @@
1
1
  # @reblu/site-blocks
2
2
 
3
+ ## 0.24.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c7f3a43: Course catalogue filters are tenant config (REB-1234).
8
+ - `@reblu/site-contracts`: new `AcademyConfigPublic` and `AcademyCatalogFiltersPublic` types for `GET /api/site/academy/config`.
9
+ - `@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`.
10
+ - `@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.
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [c7f3a43]
15
+ - @reblu/site-contracts@1.24.0
16
+
3
17
  ## 0.23.0
4
18
 
5
19
  ### Minor Changes
package/README.md CHANGED
@@ -29,7 +29,7 @@ colour on the red background. See `docs/theming/theming.md`.
29
29
  | `Treatment`, `ParentPage`, `ProfileCard`, `ProductCard` | `.` | Presentational organisms over the SITE API contracts. RSC-safe. `Specialty`: deprecated alias, see below. |
30
30
  | `ProfessionalFields` | `.` | The tenant's own professional fields (`professional.fields`) on a team card or profile. RSC-safe. See below. |
31
31
  | `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". |
32
+ | `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
33
  | `CartLineList`, `CartSummary`, `CartAdjustmentsNotice`, `AddedToCartToast`, `CheckoutLoginPrompt`, `CheckoutForm`, `OrderConfirmation` | `.` | Cart & checkout organisms. RSC-safe by construction. See "Cart & checkout" below. |
34
34
  | `PageBlocks`, `FaqAccordion` | `.` | Registry-block renderer (`PageDetail.blocks`) and native `<details>` FAQ. Server Components. See "Page blocks" below. |
35
35
  | `ProductTrustItems`, `DEFAULT_TRUST_ICONS` | `.` | Store-wide trust messages from `ShopConfig.productTrustItems`. Server Component. See "Product trust items" below. |
@@ -991,13 +991,19 @@ import { CourseSyllabus } from '@reblu/site-blocks/client'
991
991
  and `course-syllabus-preview`. No slot was renamed in REB-1135. It renders
992
992
  nothing for a course without modules.
993
993
 
994
- ## Course catalogue filters — `CourseCatalogFilters` (REB-547)
994
+ ## Course catalogue filters — `CourseCatalogFilters` (REB-547, REB-1234)
995
995
 
996
- Drop-in filters for the academy catalogue: an instructor filter fed by
996
+ Drop-in filters for the academy catalogue: a category filter fed by
997
+ `client.academy.categories()`, an instructor filter fed by
997
998
  `client.academy.instructors()` (instructors of published courses, sorted by
998
999
  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
1000
+ maps 1:1 onto a `client.academy.courses()` param: `category`, `instructor`,
1001
+ `priceMin`, `priceMax`, `free`.
1002
+
1003
+ Which filters show is the tenant's choice, made in nexus › Formación ›
1004
+ Configuración › «Filtros del catálogo» and served by `client.academy.config()`
1005
+ as `catalogFilters`. Pass it as `enabled`: the component renders only the
1006
+ filters switched on, with no SITE code change when the tenant flips one. Same RSC-safe contract as `ShopFilters`: every option is a
1001
1007
  real `<a href>`; pass `onChange` only from a `'use client'` boundary.
1002
1008
 
1003
1009
  ```tsx
@@ -1013,14 +1019,17 @@ export default async function CoursesPage({
1013
1019
  const sp = await searchParams
1014
1020
  const num = (v?: string) => (v ? Number(v) : undefined)
1015
1021
  const active: AcademyCourseListQuery = {
1022
+ category: sp.category,
1016
1023
  instructor: sp.instructor,
1017
1024
  priceMin: num(sp.priceMin),
1018
1025
  priceMax: num(sp.priceMax),
1019
1026
  free: sp.free === 'true',
1020
1027
  }
1021
- const [courses, instructors] = await Promise.all([
1028
+ const [courses, categories, instructors, academy] = await Promise.all([
1022
1029
  client.academy.courses(active),
1030
+ client.academy.categories(),
1023
1031
  client.academy.instructors(),
1032
+ client.academy.config(),
1024
1033
  ])
1025
1034
 
1026
1035
  // Keep the other params; `free` and a price band exclude each other.
@@ -1035,10 +1044,14 @@ export default async function CoursesPage({
1035
1044
 
1036
1045
  return (
1037
1046
  <>
1047
+ {/* Your search box stays: it is not a switchable filter. */}
1038
1048
  <CourseCatalogFilters
1049
+ enabled={academy.catalogFilters}
1050
+ categories={categories.data}
1039
1051
  instructors={instructors.data}
1040
1052
  active={active}
1041
1053
  hrefs={{
1054
+ category: (slug) => href({ category: slug }),
1042
1055
  instructor: (id) => href({ instructor: id }),
1043
1056
  price: (min, max) => href({ priceMin: min, priceMax: max, free: false }),
1044
1057
  free: (next) => href({ free: next, priceMin: undefined, priceMax: undefined }),
@@ -1051,6 +1064,15 @@ export default async function CoursesPage({
1051
1064
  }
1052
1065
  ```
1053
1066
 
1067
+ - **Tenant switches.** `enabled` takes `{ category, instructor, price }`; a
1068
+ key that is missing counts as on, so leaving out `enabled` shows every filter
1069
+ (the behaviour before REB-1234). A tenant that never set the switches gets
1070
+ all three `true` from `client.academy.config()`. With every filter off the
1071
+ component renders nothing, and the search box (yours) stays.
1072
+ - **Category needs data and an href.** The category group shows only with
1073
+ `categories` and `hrefs.category`; without them the component behaves as
1074
+ before. An active category slug that is no longer listed keeps the group
1075
+ visible, so it can be cleared.
1054
1076
  - **Price is integer cents.** The default bands (`DEFAULT_COURSE_PRICE_BANDS`)
1055
1077
  are EUR: up to 50, 50 to 150, 150 to 300, from 300. Pass `priceBands` for
1056
1078
  another catalogue and `currencyCode` / `locale` for the labels.
package/dist/index.cjs CHANGED
@@ -14824,6 +14824,8 @@ var DEFAULT_COURSE_PRICE_BANDS = [
14824
14824
  { min: 3e4 }
14825
14825
  ];
14826
14826
  var DEFAULT_COURSE_CATALOG_FILTERS_LABELS = {
14827
+ category: "Categor\xEDa",
14828
+ allCategories: "Todas",
14827
14829
  instructor: "Docente",
14828
14830
  allInstructors: "Todos",
14829
14831
  price: "Precio",
@@ -14847,6 +14849,8 @@ function bandLabel(band, t, currencyCode, locale) {
14847
14849
  }
14848
14850
  function CourseCatalogFilters({
14849
14851
  instructors,
14852
+ categories = [],
14853
+ enabled = {},
14850
14854
  hrefs,
14851
14855
  active = {},
14852
14856
  onChange,
@@ -14858,9 +14862,39 @@ function CourseCatalogFilters({
14858
14862
  }) {
14859
14863
  const t = { ...DEFAULT_COURSE_CATALOG_FILTERS_LABELS, ...labels };
14860
14864
  const named = instructors.filter((i) => instructorName(i) !== "");
14861
- const showInstructors = named.length > 0 || active.instructor !== void 0;
14865
+ const sortedCategories = [...categories].sort((a, b2) => a.sortOrder - b2.sortOrder);
14866
+ const categoryHref = hrefs.category;
14867
+ const showCategories = enabled.category !== false && categoryHref !== void 0 && (sortedCategories.length > 0 || active.category !== void 0);
14868
+ const showInstructors = enabled.instructor !== false && (named.length > 0 || active.instructor !== void 0);
14869
+ const showPrice = enabled.price !== false;
14862
14870
  const isFree = Boolean(active.free);
14871
+ if (!showCategories && !showInstructors && !showPrice) return null;
14863
14872
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: (0, import_utils15.cn)("space-y-6", className), "data-slot": "course-catalog-filters", children: [
14873
+ showCategories && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(FilterFieldset, { legend: t.category, children: [
14874
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14875
+ OptionLink,
14876
+ {
14877
+ href: categoryHref(void 0),
14878
+ active: active.category === void 0,
14879
+ label: t.allCategories,
14880
+ onActivate: onChange ? () => onChange({ category: void 0 }) : void 0
14881
+ }
14882
+ ),
14883
+ sortedCategories.map((category) => {
14884
+ const isActive = active.category === category.slug;
14885
+ const next2 = isActive ? void 0 : category.slug;
14886
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14887
+ OptionLink,
14888
+ {
14889
+ href: categoryHref(next2),
14890
+ active: isActive,
14891
+ label: category.name,
14892
+ onActivate: onChange ? () => onChange({ category: next2 }) : void 0
14893
+ },
14894
+ category.id
14895
+ );
14896
+ })
14897
+ ] }),
14864
14898
  showInstructors && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(FilterFieldset, { legend: t.instructor, children: [
14865
14899
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14866
14900
  OptionLink,
@@ -14886,7 +14920,7 @@ function CourseCatalogFilters({
14886
14920
  );
14887
14921
  })
14888
14922
  ] }),
14889
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(FilterFieldset, { legend: t.price, children: [
14923
+ showPrice && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(FilterFieldset, { legend: t.price, children: [
14890
14924
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14891
14925
  OptionLink,
14892
14926
  {
@@ -14921,6 +14955,7 @@ function CourseCatalogFilters({
14921
14955
  onClick: onChange ? (event) => {
14922
14956
  event.preventDefault();
14923
14957
  onChange({
14958
+ category: void 0,
14924
14959
  instructor: void 0,
14925
14960
  priceMin: void 0,
14926
14961
  priceMax: void 0,