@reblu/site-contracts 1.1.0 → 1.3.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,79 @@
1
1
  # @reblu/site-contracts
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - baa36cf: Expose each taxonomy axis's selection mode on `ShopFacets` (REB-809).
8
+
9
+ `ShopFacets.taxonomies[].selectionMode` is a new required field: `'single' |
10
+ 'multiple'`, mirroring REB-801's per-axis `TaxonomySelectionMode`. It reflects
11
+ how many terms a PRODUCT may carry on that axis, not how many the SITE
12
+ storefront filter may select at once — `GET /api/site/shop/products`'s
13
+ `taxonomy[<key>]` param still accepts only one term slug per axis regardless
14
+ of this value (see `@reblu/site-blocks`'s `ShopFilters` docs).
15
+
16
+ Additive: a new required field on the existing `ShopFacets` shape, populated
17
+ for every axis. A SITE build that has not migrated its `ShopFacets` type yet
18
+ keeps working unchanged — the extra field is simply ignored at runtime.
19
+
20
+ ## 1.2.0
21
+
22
+ ### Minor Changes
23
+
24
+ - 34b15c4: Expose the academy verification gate's field list on `AcademyCourseDetail` (REB-83).
25
+
26
+ `AcademyCourseDetail.validationFields` is a new field: an ordered array of
27
+ `AcademyValidationFieldPublic` (`{ key, label, required, maxLength? }`), the
28
+ tenant's OWN configured list of data points its access-validation gate asks
29
+ for (e.g. a colegiado number and a professional college), resolved server-side
30
+ from `AcademyConfig.validationFields` — or derived as a single legacy field
31
+ under the key `'validationField'` when the tenant has not configured a list
32
+ yet, so an already-integrated SITE keeps working unchanged. A SITE renders
33
+ its enrollment form from this array instead of a fixed field.
34
+
35
+ Additive: a new required field on the existing `AcademyCourseDetail` shape
36
+ (populated for every course, empty array when the gate does not apply) and a
37
+ new exported type. The existing `validationFieldLabel` field is unchanged and
38
+ still present for a SITE build that has not migrated to the new array yet.
39
+
40
+ - 7ff47d7: Add the cookie-consent fields to `Analytics` (REB-642). _Entry added after
41
+ publishing (REB-787): these fields shipped in 1.2.0, but no changeset
42
+ targeted this package, so the generated notes above left them out._
43
+
44
+ `GET /api/site/analytics` now also returns what a SITE needs to run the
45
+ `@reblu/site-next/consent` banner. There is still one request, and the root
46
+ layout already makes it:
47
+ - `consentRequired: boolean`: `true` when the tenant has a valid GA4 or
48
+ Clarity id (computed from the already-validated `gaId`/`clarityId`, so an
49
+ id that fails validation never requires a banner). reCAPTCHA is excluded
50
+ on purpose because it is strictly necessary. `false` means no configured
51
+ vendor needs consent, so the SITE can skip the banner.
52
+ `<ConsentedAnalytics>` does not read this field.
53
+ - `cookiePolicyVersion: string | null`: a content hash of the tenant's cookie
54
+ policy. It is the same algorithm as `cookiePolicyVersion()` in
55
+ `@reblu/site-next/consent-server` on the same text, and a shared test
56
+ vector keeps the two identical. Pass it to
57
+ `<ConsentProvider policyVersion>`. When the tenant edits the policy in
58
+ nexus, the hash changes and visitors are asked again. `null` means no
59
+ policy is stored.
60
+ - `cookieBanner`: the banner copy the tenant edits in nexus:
61
+ `{ enabled, title, body, acceptLabel, rejectLabel, manageLabel, policyUrl }`.
62
+ A `null` text field means the SDK default applies. Map `body` to the
63
+ banner's `description` text and `policyUrl` to `policyHref`. The buttons
64
+ and categories are fixed and have no field here.
65
+ - `cookieInventory`: `{ vendor, category }[]`, where `category` is
66
+ `'necessary' | 'analytics' | 'marketing'`. It lists only the vendors this
67
+ SITE actually loads, and is meant for the cookie-policy table.
68
+
69
+ **Consumer note:** the four fields are required on the `Analytics` type.
70
+ Runtime responses always include them, but code that builds an `Analytics`
71
+ object by hand (for example test fixtures) must now set them. A neutral
72
+ fixture is `consentRequired: false`, `cookiePolicyVersion: null`,
73
+ `cookieBanner: { enabled: true, title: null, body: null, acceptLabel: null,
74
+ rejectLabel: null, manageLabel: null, policyUrl: null }`,
75
+ `cookieInventory: []`.
76
+
3
77
  ## 1.1.0
4
78
 
5
79
  ### Minor Changes
package/dist/index.d.cts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export type AcademyCategoryPublic = { id: string; name: string; slug: string; sortOrder: number;}
7
7
 
8
- export type AcademyCourseDetail = { id: string; slug: string; title: string; shortDescription: string | null; coverUrl: string | null; priceCents: number; accessMode: "IMMEDIATE" | "DRIP" | "COHORT"; category: { id: string; name: string; slug: string; sortOrder: number; } | null; description: string | null; modules: { id: string; title: string; description: string | null; lessons: { id: string; slug: string; title: string; isFreePreview: boolean; durationSeconds: number | null; hasVideo: boolean; }[]; }[]; instructors: { id: string; firstName: string | null; lastName: string | null; avatarUrl: string | null; }[]; salesEnabled: boolean; requiresValidation: boolean; validationFieldLabel: string | null;}
8
+ export type AcademyCourseDetail = { id: string; slug: string; title: string; shortDescription: string | null; coverUrl: string | null; priceCents: number; accessMode: "IMMEDIATE" | "DRIP" | "COHORT"; category: { id: string; name: string; slug: string; sortOrder: number; } | null; description: string | null; modules: { id: string; title: string; description: string | null; lessons: { id: string; slug: string; title: string; isFreePreview: boolean; durationSeconds: number | null; hasVideo: boolean; }[]; }[]; instructors: { id: string; firstName: string | null; lastName: string | null; avatarUrl: string | null; }[]; salesEnabled: boolean; requiresValidation: boolean; validationFieldLabel: string | null; validationFields: { key: string; label: string; required: boolean; maxLength?: number | undefined; }[];}
9
9
 
10
10
  export type AcademyCourseSort = "price_asc" | "price_desc" | "newest"
11
11
 
@@ -17,7 +17,9 @@ export type AcademyLessonPublic = { id: string; slug: string; title: st
17
17
 
18
18
  export type AcademyModulePublic = { id: string; title: string; description: string | null; lessons: { id: string; slug: string; title: string; isFreePreview: boolean; durationSeconds: number | null; hasVideo: boolean; }[];}
19
19
 
20
- export type Analytics = { gaId: string | null; clarityId: string | null; recaptchaSiteKey: string | null; contactFormMode: "NOT_CONFIGURED" | "DISABLED" | "DB_ONLY" | "WEBHOOK" | "RESEND"; metaPixelId: string | null; metaVerificationContent: string | null; googleVerificationContent: string | null;}
20
+ export type AcademyValidationFieldPublic = { key: string; label: string; required: boolean; maxLength?: number | undefined;}
21
+
22
+ export type Analytics = { gaId: string | null; clarityId: string | null; recaptchaSiteKey: string | null; contactFormMode: "NOT_CONFIGURED" | "DISABLED" | "DB_ONLY" | "WEBHOOK" | "RESEND"; metaPixelId: string | null; metaVerificationContent: string | null; googleVerificationContent: string | null; consentRequired: boolean; cookiePolicyVersion: string | null; cookieBanner: { enabled: boolean; title: string | null; body: string | null; acceptLabel: string | null; rejectLabel: string | null; manageLabel: string | null; policyUrl: string | null; }; cookieInventory: { vendor: string; category: "necessary" | "analytics" | "marketing"; }[];}
21
23
 
22
24
  export type BlogAuthor = { firstName: string | null; lastName: string | null; avatarUrl: string | null;}
23
25
 
@@ -143,7 +145,7 @@ export type ShopDiscountApplication = { valid: boolean; discount: number;
143
145
 
144
146
  export type ShopEmailCheck = { exists: boolean;}
145
147
 
146
- export type ShopFacets = { taxonomies: { key: string; label: string; showAsBadge: boolean; terms: { slug: string; name: string; count: number; }[]; }[]; attributes: { key: string; label: string; valueType: "boolean" | "text" | "select" | "multiselect"; options: { value: string; label: string; count: number; }[]; }[]; price: { min: number; max: number; }; onSale: { count: number; };}
148
+ export type ShopFacets = { taxonomies: { key: string; label: string; showAsBadge: boolean; selectionMode: "single" | "multiple"; terms: { slug: string; name: string; count: number; }[]; }[]; attributes: { key: string; label: string; valueType: "boolean" | "text" | "select" | "multiselect"; options: { value: string; label: string; count: number; }[]; }[]; price: { min: number; max: number; }; onSale: { count: number; };}
147
149
 
148
150
  export type ShopInventoryRef = { stockQuantity: number; reservedQuantity: number; lowStockThreshold: number | null;}
149
151
 
@@ -169,6 +171,8 @@ export type ShopSort = "price_asc" | "price_desc" | "newest" | "on_sale"
169
171
 
170
172
  export type ShopTaxRateRef = { rate: number; type: "IVA" | "IGIC" | "RETENTION" | "OTHER"; isExempt: boolean;}
171
173
 
174
+ export type ShopTaxonomySelectionMode = "single" | "multiple"
175
+
172
176
  export type ShopTaxonomyTermRef = { taxonomyKey: string; taxonomyLabel: string; showAsBadge: boolean; slug: string; name: string; imageUrl: string | null;}
173
177
 
174
178
  export type ShopValidateCartInput = { items: { productId: string; quantity: number; variantId?: string | null | undefined; }[];}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export type AcademyCategoryPublic = { id: string; name: string; slug: string; sortOrder: number;}
7
7
 
8
- export type AcademyCourseDetail = { id: string; slug: string; title: string; shortDescription: string | null; coverUrl: string | null; priceCents: number; accessMode: "IMMEDIATE" | "DRIP" | "COHORT"; category: { id: string; name: string; slug: string; sortOrder: number; } | null; description: string | null; modules: { id: string; title: string; description: string | null; lessons: { id: string; slug: string; title: string; isFreePreview: boolean; durationSeconds: number | null; hasVideo: boolean; }[]; }[]; instructors: { id: string; firstName: string | null; lastName: string | null; avatarUrl: string | null; }[]; salesEnabled: boolean; requiresValidation: boolean; validationFieldLabel: string | null;}
8
+ export type AcademyCourseDetail = { id: string; slug: string; title: string; shortDescription: string | null; coverUrl: string | null; priceCents: number; accessMode: "IMMEDIATE" | "DRIP" | "COHORT"; category: { id: string; name: string; slug: string; sortOrder: number; } | null; description: string | null; modules: { id: string; title: string; description: string | null; lessons: { id: string; slug: string; title: string; isFreePreview: boolean; durationSeconds: number | null; hasVideo: boolean; }[]; }[]; instructors: { id: string; firstName: string | null; lastName: string | null; avatarUrl: string | null; }[]; salesEnabled: boolean; requiresValidation: boolean; validationFieldLabel: string | null; validationFields: { key: string; label: string; required: boolean; maxLength?: number | undefined; }[];}
9
9
 
10
10
  export type AcademyCourseSort = "price_asc" | "price_desc" | "newest"
11
11
 
@@ -17,7 +17,9 @@ export type AcademyLessonPublic = { id: string; slug: string; title: st
17
17
 
18
18
  export type AcademyModulePublic = { id: string; title: string; description: string | null; lessons: { id: string; slug: string; title: string; isFreePreview: boolean; durationSeconds: number | null; hasVideo: boolean; }[];}
19
19
 
20
- export type Analytics = { gaId: string | null; clarityId: string | null; recaptchaSiteKey: string | null; contactFormMode: "NOT_CONFIGURED" | "DISABLED" | "DB_ONLY" | "WEBHOOK" | "RESEND"; metaPixelId: string | null; metaVerificationContent: string | null; googleVerificationContent: string | null;}
20
+ export type AcademyValidationFieldPublic = { key: string; label: string; required: boolean; maxLength?: number | undefined;}
21
+
22
+ export type Analytics = { gaId: string | null; clarityId: string | null; recaptchaSiteKey: string | null; contactFormMode: "NOT_CONFIGURED" | "DISABLED" | "DB_ONLY" | "WEBHOOK" | "RESEND"; metaPixelId: string | null; metaVerificationContent: string | null; googleVerificationContent: string | null; consentRequired: boolean; cookiePolicyVersion: string | null; cookieBanner: { enabled: boolean; title: string | null; body: string | null; acceptLabel: string | null; rejectLabel: string | null; manageLabel: string | null; policyUrl: string | null; }; cookieInventory: { vendor: string; category: "necessary" | "analytics" | "marketing"; }[];}
21
23
 
22
24
  export type BlogAuthor = { firstName: string | null; lastName: string | null; avatarUrl: string | null;}
23
25
 
@@ -143,7 +145,7 @@ export type ShopDiscountApplication = { valid: boolean; discount: number;
143
145
 
144
146
  export type ShopEmailCheck = { exists: boolean;}
145
147
 
146
- export type ShopFacets = { taxonomies: { key: string; label: string; showAsBadge: boolean; terms: { slug: string; name: string; count: number; }[]; }[]; attributes: { key: string; label: string; valueType: "boolean" | "text" | "select" | "multiselect"; options: { value: string; label: string; count: number; }[]; }[]; price: { min: number; max: number; }; onSale: { count: number; };}
148
+ export type ShopFacets = { taxonomies: { key: string; label: string; showAsBadge: boolean; selectionMode: "single" | "multiple"; terms: { slug: string; name: string; count: number; }[]; }[]; attributes: { key: string; label: string; valueType: "boolean" | "text" | "select" | "multiselect"; options: { value: string; label: string; count: number; }[]; }[]; price: { min: number; max: number; }; onSale: { count: number; };}
147
149
 
148
150
  export type ShopInventoryRef = { stockQuantity: number; reservedQuantity: number; lowStockThreshold: number | null;}
149
151
 
@@ -169,6 +171,8 @@ export type ShopSort = "price_asc" | "price_desc" | "newest" | "on_sale"
169
171
 
170
172
  export type ShopTaxRateRef = { rate: number; type: "IVA" | "IGIC" | "RETENTION" | "OTHER"; isExempt: boolean;}
171
173
 
174
+ export type ShopTaxonomySelectionMode = "single" | "multiple"
175
+
172
176
  export type ShopTaxonomyTermRef = { taxonomyKey: string; taxonomyLabel: string; showAsBadge: boolean; slug: string; name: string; imageUrl: string | null;}
173
177
 
174
178
  export type ShopValidateCartInput = { items: { productId: string; quantity: number; variantId?: string | null | undefined; }[];}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reblu/site-contracts",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "description": "Public, dependency-free TypeScript types for the Reblu SITE API (types only — no Zod, no Prisma at runtime).",
6
6
  "license": "Apache-2.0",