@reblu/site-contracts 1.11.0 → 1.13.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,17 @@
1
1
  # @reblu/site-contracts
2
2
 
3
+ ## 1.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7a00471: Featured pages per page category (REB-869). `@reblu/site-contracts` adds `PagesListQuery`, the query of `GET /api/site/pages`, including the new `featured` flag. `@reblu/site-client`: `pages.list({ category, featured: true })` returns that category's featured pages (max 3) in the tenant's order, skipping unpublished, deleted or moved pages, and an empty list when nothing is featured; the new `pages.featured(category, { limit?, fallback?, cache? })` helper returns the featured pages, or the category's first `limit` pages in normal order when none are featured, so a landing teaser is never empty. `limit` must be an integer >= 1: above 3 it is clamped to 3; 0, negative, fractional or NaN throw a `RangeError` before any request. Against an API that predates `featured`, the flag is ignored and `pages.featured()` silently degrades to the category's first `limit` pages. `@reblu/site-blocks` adds `PageCardGrid`, the page-card grid `ParentPage` already rendered for its child pages, as a drop-in (`pages`, `pageHref`, `heading`); cards are keyed by category + slug, an empty `heading` renders no `<h2>`. `ParentPage` now renders through it with unchanged markup (including the empty `<h2>` for `childPagesLabel=''`).
8
+
9
+ ## 1.12.0
10
+
11
+ ### Minor Changes
12
+
13
+ - fd17765: Site-level SEO (REB-651). `Config` gains optional `seoTitle`, `seoDescription` and `canonicalUrl`, all `string | null`. `seoTitle` and `seoDescription` are the site's own search title and description (the home page), apart from `siteName` and `siteDescription`; `null` means never set, so fall back to `siteName` and `siteDescription`. A set `seoTitle` is used as-is and never gets the template suffix. `canonicalUrl` is the site's https origin (no trailing slash), to use as Next's `metadataBase`. Purely additive.
14
+
3
15
  ## 1.11.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -132,6 +132,16 @@ may exist in both `tratamientos` and `especialidades`. Consequences for a SITE:
132
132
  legacy slug-only reference **only when that slug is unique** — otherwise
133
133
  `resolved` is `null`. Build the embed's href from `resolved.category`.
134
134
 
135
+ ### Featured pages per category (REB-869)
136
+
137
+ `PagesListQuery` is the query of `GET /api/site/pages`. With `category` set,
138
+ `featured: true` returns that category's featured pages (max 3) in the order
139
+ the tenant chose in nexus, instead of the normal listing. Unpublished,
140
+ deleted or moved pages are skipped; nothing featured → empty `data` (the API
141
+ never falls back). For a teaser that falls back to the category's first pages,
142
+ use `@reblu/site-client`'s `pages.featured(category)` with
143
+ `@reblu/site-blocks`' `PageCardGrid`.
144
+
135
145
  ### Shop — product trust items (REB-858)
136
146
 
137
147
  `ShopConfig.productTrustItems` is the store-wide list of short trust messages
@@ -169,6 +179,21 @@ another `"… · Brand"` template. For list/index routes, or an older API withou
169
179
  `@reblu/site-next/seo`. Both fields are optional in the types so the minor stays
170
180
  additive; `seoTitle`/`metaTitle` are unchanged.
171
181
 
182
+ ### Site SEO title, description and canonical URL (REB-651)
183
+
184
+ `Config` carries the site's own search fields, apart from the identity fields
185
+ `siteName`/`siteDescription` (which stay short for breadcrumbs and chrome):
186
+
187
+ - `seoTitle` — the home page title. When set it is used **as-is**, never
188
+ suffixed by `seoTitleTemplate`. `null` → use `siteName`.
189
+ - `seoDescription` — the home page description. `null` → `siteDescription`.
190
+ - `canonicalUrl` — the site origin, `https://…` with no trailing slash, or
191
+ `null`. Use it as Next's `metadataBase` so relative Open Graph paths resolve.
192
+
193
+ They are served raw (`null` = never set). `buildMetadata({ config, home: true })`
194
+ from `@reblu/site-next/seo` applies the fallbacks for you. All three are
195
+ optional in the types so the minor stays additive.
196
+
172
197
  ## How the types stay correct
173
198
 
174
199
  The types are re-derived from the response schemas that live in the Reblu API
package/dist/index.d.cts CHANGED
@@ -45,7 +45,7 @@ export type BlogSlug = { slug: string; updatedAt: string;}
45
45
 
46
46
  export type BlogTag = { id: string; name: string; slug: string; color: string;}
47
47
 
48
- 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>; seoTitleTemplate?: string | undefined;}
48
+ 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>; seoTitleTemplate?: string | undefined; seoTitle?: string | null | undefined; seoDescription?: string | null | undefined; canonicalUrl?: string | null | undefined;}
49
49
 
50
50
  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;}
51
51
 
@@ -111,6 +111,8 @@ export type PageSlug = { slug: string; type: "SERVICE" | "CUSTOM"; upda
111
111
 
112
112
  export type PageType = "custom" | "service" | "treatments" | "specialties" | "team" | "generic"
113
113
 
114
+ export type PagesListQuery = { type?: ("custom" | "service" | "treatments" | "specialties" | "team" | "generic")[] | undefined; category?: string | undefined; page?: unknown; pageSize?: unknown; sort?: "order" | "-publishedAt" | undefined; featured?: boolean | undefined;}
115
+
114
116
  export type PagesListSort = "order" | "-publishedAt"
115
117
 
116
118
  export type PaginationMeta = { total?: number | undefined; page?: number | undefined; limit?: number | undefined; pages?: number | undefined;}
package/dist/index.d.ts CHANGED
@@ -45,7 +45,7 @@ export type BlogSlug = { slug: string; updatedAt: string;}
45
45
 
46
46
  export type BlogTag = { id: string; name: string; slug: string; color: string;}
47
47
 
48
- 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>; seoTitleTemplate?: string | undefined;}
48
+ 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>; seoTitleTemplate?: string | undefined; seoTitle?: string | null | undefined; seoDescription?: string | null | undefined; canonicalUrl?: string | null | undefined;}
49
49
 
50
50
  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;}
51
51
 
@@ -111,6 +111,8 @@ export type PageSlug = { slug: string; type: "SERVICE" | "CUSTOM"; upda
111
111
 
112
112
  export type PageType = "custom" | "service" | "treatments" | "specialties" | "team" | "generic"
113
113
 
114
+ export type PagesListQuery = { type?: ("custom" | "service" | "treatments" | "specialties" | "team" | "generic")[] | undefined; category?: string | undefined; page?: unknown; pageSize?: unknown; sort?: "order" | "-publishedAt" | undefined; featured?: boolean | undefined;}
115
+
114
116
  export type PagesListSort = "order" | "-publishedAt"
115
117
 
116
118
  export type PaginationMeta = { total?: number | undefined; page?: number | undefined; limit?: number | undefined; pages?: number | undefined;}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reblu/site-contracts",
3
- "version": "1.11.0",
3
+ "version": "1.13.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",