@reblu/site-contracts 1.2.0 → 1.4.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,35 @@
1
1
  # @reblu/site-contracts
2
2
 
3
+ ## 1.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 87b7fa0: Add typed `Config.openingHours` (REB-826): `ConfigOpeningHours`,
8
+ `ConfigOpeningHoursRange`, `ConfigOpeningHoursDay`.
9
+
10
+ Additive: a new optional-shaped (`null` when not configured) field on the
11
+ existing `Config` type. A SITE build that has not migrated its `Config` type
12
+ yet keeps working unchanged — the extra field is simply ignored at runtime.
13
+ Replaces the previous untyped convention of stashing opening hours inside
14
+ `Config.metadata`.
15
+
16
+ ## 1.3.0
17
+
18
+ ### Minor Changes
19
+
20
+ - baa36cf: Expose each taxonomy axis's selection mode on `ShopFacets` (REB-809).
21
+
22
+ `ShopFacets.taxonomies[].selectionMode` is a new required field: `'single' |
23
+ 'multiple'`, mirroring REB-801's per-axis `TaxonomySelectionMode`. It reflects
24
+ how many terms a PRODUCT may carry on that axis, not how many the SITE
25
+ storefront filter may select at once — `GET /api/site/shop/products`'s
26
+ `taxonomy[<key>]` param still accepts only one term slug per axis regardless
27
+ of this value (see `@reblu/site-blocks`'s `ShopFilters` docs).
28
+
29
+ Additive: a new required field on the existing `ShopFacets` shape, populated
30
+ for every axis. A SITE build that has not migrated its `ShopFacets` type yet
31
+ keeps working unchanged — the extra field is simply ignored at runtime.
32
+
3
33
  ## 1.2.0
4
34
 
5
35
  ### Minor Changes
@@ -20,6 +50,43 @@
20
50
  new exported type. The existing `validationFieldLabel` field is unchanged and
21
51
  still present for a SITE build that has not migrated to the new array yet.
22
52
 
53
+ - 7ff47d7: Add the cookie-consent fields to `Analytics` (REB-642). _Entry added after
54
+ publishing (REB-787): these fields shipped in 1.2.0, but no changeset
55
+ targeted this package, so the generated notes above left them out._
56
+
57
+ `GET /api/site/analytics` now also returns what a SITE needs to run the
58
+ `@reblu/site-next/consent` banner. There is still one request, and the root
59
+ layout already makes it:
60
+ - `consentRequired: boolean`: `true` when the tenant has a valid GA4 or
61
+ Clarity id (computed from the already-validated `gaId`/`clarityId`, so an
62
+ id that fails validation never requires a banner). reCAPTCHA is excluded
63
+ on purpose because it is strictly necessary. `false` means no configured
64
+ vendor needs consent, so the SITE can skip the banner.
65
+ `<ConsentedAnalytics>` does not read this field.
66
+ - `cookiePolicyVersion: string | null`: a content hash of the tenant's cookie
67
+ policy. It is the same algorithm as `cookiePolicyVersion()` in
68
+ `@reblu/site-next/consent-server` on the same text, and a shared test
69
+ vector keeps the two identical. Pass it to
70
+ `<ConsentProvider policyVersion>`. When the tenant edits the policy in
71
+ nexus, the hash changes and visitors are asked again. `null` means no
72
+ policy is stored.
73
+ - `cookieBanner`: the banner copy the tenant edits in nexus:
74
+ `{ enabled, title, body, acceptLabel, rejectLabel, manageLabel, policyUrl }`.
75
+ A `null` text field means the SDK default applies. Map `body` to the
76
+ banner's `description` text and `policyUrl` to `policyHref`. The buttons
77
+ and categories are fixed and have no field here.
78
+ - `cookieInventory`: `{ vendor, category }[]`, where `category` is
79
+ `'necessary' | 'analytics' | 'marketing'`. It lists only the vendors this
80
+ SITE actually loads, and is meant for the cookie-policy table.
81
+
82
+ **Consumer note:** the four fields are required on the `Analytics` type.
83
+ Runtime responses always include them, but code that builds an `Analytics`
84
+ object by hand (for example test fixtures) must now set them. A neutral
85
+ fixture is `consentRequired: false`, `cookiePolicyVersion: null`,
86
+ `cookieBanner: { enabled: true, title: null, body: null, acceptLabel: null,
87
+ rejectLabel: null, manageLabel: null, policyUrl: null }`,
88
+ `cookieInventory: []`.
89
+
23
90
  ## 1.1.0
24
91
 
25
92
  ### Minor Changes
package/dist/index.d.cts CHANGED
@@ -39,7 +39,7 @@ export type BlogSlug = { slug: string; updatedAt: string;}
39
39
 
40
40
  export type BlogTag = { id: string; name: string; slug: string; color: string;}
41
41
 
42
- export type Config = { siteName: string; siteDescription: string | null; language: string | null; contactEmail: string | null; keywords: string[]; businessType: string | null; isSetupComplete: boolean; logoLightExpandedUrl: string | null; logoDarkExpandedUrl: string | null; logoLightSmallUrl: string | null; logoDarkSmallUrl: string | null; ogImageUrl: string | null; owner: { name: string | null; email: string | null; jobTitle: string | null; }; business: { name: string | null; }; address: { street: string | null; city: string | null; region: string | null; postalCode: string | null; country: string | null; telephone: string | null; lat: number | null; lng: number | null; } | null; fiscal: { taxId: string | null; taxIdKind: "NIF" | "CIF" | null; legalName: string | null; registry: { name: string; number: string; } | null; } | null; social: { linkedin: string | null; twitter: string | null; facebook: string | null; instagram: string | null; youtube: string | null; } | null; metadata: unknown; contactFormMode: "NOT_CONFIGURED" | "DISABLED" | "DB_ONLY" | "WEBHOOK" | "RESEND"; features: Record<string, unknown>;}
42
+ export type Config = { siteName: string; siteDescription: string | null; language: string | null; contactEmail: string | null; keywords: string[]; businessType: string | null; isSetupComplete: boolean; logoLightExpandedUrl: string | null; logoDarkExpandedUrl: string | null; logoLightSmallUrl: string | null; logoDarkSmallUrl: string | null; ogImageUrl: string | null; owner: { name: string | null; email: string | null; jobTitle: string | null; }; business: { name: string | null; }; address: { street: string | null; city: string | null; region: string | null; postalCode: string | null; country: string | null; telephone: string | null; lat: number | null; lng: number | null; } | null; fiscal: { taxId: string | null; taxIdKind: "NIF" | "CIF" | null; legalName: string | null; registry: { name: string; number: string; } | null; } | null; social: { linkedin: string | null; twitter: string | null; facebook: string | null; instagram: string | null; youtube: string | null; } | null; metadata: unknown; openingHours: { ranges: { days: ("MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN")[]; opens: string; closes: string; }[]; note?: string | undefined; } | null; contactFormMode: "NOT_CONFIGURED" | "DISABLED" | "DB_ONLY" | "WEBHOOK" | "RESEND"; features: Record<string, unknown>;}
43
43
 
44
44
  export type ConfigAddress = { street: string | null; city: string | null; region: string | null; postalCode: string | null; country: string | null; telephone: string | null; lat: number | null; lng: number | null;}
45
45
 
@@ -47,6 +47,12 @@ export type ConfigFiscal = { taxId: string | null; taxIdKind: "NIF" | "CIF
47
47
 
48
48
  export type ConfigFiscalRegistry = { name: string; number: string;}
49
49
 
50
+ export type ConfigOpeningHours = { ranges: { days: ("MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN")[]; opens: string; closes: string; }[]; note?: string | undefined;}
51
+
52
+ export type ConfigOpeningHoursDay = "MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN"
53
+
54
+ export type ConfigOpeningHoursRange = { days: ("MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN")[]; opens: string; closes: string;}
55
+
50
56
  export type ConfigSocial = { linkedin: string | null; twitter: string | null; facebook: string | null; instagram: string | null; youtube: string | null;}
51
57
 
52
58
  export type ConfigTaxIdKind = "NIF" | "CIF"
@@ -145,7 +151,7 @@ export type ShopDiscountApplication = { valid: boolean; discount: number;
145
151
 
146
152
  export type ShopEmailCheck = { exists: boolean;}
147
153
 
148
- 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; };}
154
+ 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; };}
149
155
 
150
156
  export type ShopInventoryRef = { stockQuantity: number; reservedQuantity: number; lowStockThreshold: number | null;}
151
157
 
@@ -171,6 +177,8 @@ export type ShopSort = "price_asc" | "price_desc" | "newest" | "on_sale"
171
177
 
172
178
  export type ShopTaxRateRef = { rate: number; type: "IVA" | "IGIC" | "RETENTION" | "OTHER"; isExempt: boolean;}
173
179
 
180
+ export type ShopTaxonomySelectionMode = "single" | "multiple"
181
+
174
182
  export type ShopTaxonomyTermRef = { taxonomyKey: string; taxonomyLabel: string; showAsBadge: boolean; slug: string; name: string; imageUrl: string | null;}
175
183
 
176
184
  export type ShopValidateCartInput = { items: { productId: string; quantity: number; variantId?: string | null | undefined; }[];}
package/dist/index.d.ts CHANGED
@@ -39,7 +39,7 @@ export type BlogSlug = { slug: string; updatedAt: string;}
39
39
 
40
40
  export type BlogTag = { id: string; name: string; slug: string; color: string;}
41
41
 
42
- export type Config = { siteName: string; siteDescription: string | null; language: string | null; contactEmail: string | null; keywords: string[]; businessType: string | null; isSetupComplete: boolean; logoLightExpandedUrl: string | null; logoDarkExpandedUrl: string | null; logoLightSmallUrl: string | null; logoDarkSmallUrl: string | null; ogImageUrl: string | null; owner: { name: string | null; email: string | null; jobTitle: string | null; }; business: { name: string | null; }; address: { street: string | null; city: string | null; region: string | null; postalCode: string | null; country: string | null; telephone: string | null; lat: number | null; lng: number | null; } | null; fiscal: { taxId: string | null; taxIdKind: "NIF" | "CIF" | null; legalName: string | null; registry: { name: string; number: string; } | null; } | null; social: { linkedin: string | null; twitter: string | null; facebook: string | null; instagram: string | null; youtube: string | null; } | null; metadata: unknown; contactFormMode: "NOT_CONFIGURED" | "DISABLED" | "DB_ONLY" | "WEBHOOK" | "RESEND"; features: Record<string, unknown>;}
42
+ export type Config = { siteName: string; siteDescription: string | null; language: string | null; contactEmail: string | null; keywords: string[]; businessType: string | null; isSetupComplete: boolean; logoLightExpandedUrl: string | null; logoDarkExpandedUrl: string | null; logoLightSmallUrl: string | null; logoDarkSmallUrl: string | null; ogImageUrl: string | null; owner: { name: string | null; email: string | null; jobTitle: string | null; }; business: { name: string | null; }; address: { street: string | null; city: string | null; region: string | null; postalCode: string | null; country: string | null; telephone: string | null; lat: number | null; lng: number | null; } | null; fiscal: { taxId: string | null; taxIdKind: "NIF" | "CIF" | null; legalName: string | null; registry: { name: string; number: string; } | null; } | null; social: { linkedin: string | null; twitter: string | null; facebook: string | null; instagram: string | null; youtube: string | null; } | null; metadata: unknown; openingHours: { ranges: { days: ("MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN")[]; opens: string; closes: string; }[]; note?: string | undefined; } | null; contactFormMode: "NOT_CONFIGURED" | "DISABLED" | "DB_ONLY" | "WEBHOOK" | "RESEND"; features: Record<string, unknown>;}
43
43
 
44
44
  export type ConfigAddress = { street: string | null; city: string | null; region: string | null; postalCode: string | null; country: string | null; telephone: string | null; lat: number | null; lng: number | null;}
45
45
 
@@ -47,6 +47,12 @@ export type ConfigFiscal = { taxId: string | null; taxIdKind: "NIF" | "CIF
47
47
 
48
48
  export type ConfigFiscalRegistry = { name: string; number: string;}
49
49
 
50
+ export type ConfigOpeningHours = { ranges: { days: ("MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN")[]; opens: string; closes: string; }[]; note?: string | undefined;}
51
+
52
+ export type ConfigOpeningHoursDay = "MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN"
53
+
54
+ export type ConfigOpeningHoursRange = { days: ("MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN")[]; opens: string; closes: string;}
55
+
50
56
  export type ConfigSocial = { linkedin: string | null; twitter: string | null; facebook: string | null; instagram: string | null; youtube: string | null;}
51
57
 
52
58
  export type ConfigTaxIdKind = "NIF" | "CIF"
@@ -145,7 +151,7 @@ export type ShopDiscountApplication = { valid: boolean; discount: number;
145
151
 
146
152
  export type ShopEmailCheck = { exists: boolean;}
147
153
 
148
- 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; };}
154
+ 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; };}
149
155
 
150
156
  export type ShopInventoryRef = { stockQuantity: number; reservedQuantity: number; lowStockThreshold: number | null;}
151
157
 
@@ -171,6 +177,8 @@ export type ShopSort = "price_asc" | "price_desc" | "newest" | "on_sale"
171
177
 
172
178
  export type ShopTaxRateRef = { rate: number; type: "IVA" | "IGIC" | "RETENTION" | "OTHER"; isExempt: boolean;}
173
179
 
180
+ export type ShopTaxonomySelectionMode = "single" | "multiple"
181
+
174
182
  export type ShopTaxonomyTermRef = { taxonomyKey: string; taxonomyLabel: string; showAsBadge: boolean; slug: string; name: string; imageUrl: string | null;}
175
183
 
176
184
  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.2.0",
3
+ "version": "1.4.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",