@rovela-ai/sdk 0.19.8 → 0.21.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.
Files changed (148) hide show
  1. package/dist/admin/api/index.d.ts +4 -2
  2. package/dist/admin/api/index.d.ts.map +1 -1
  3. package/dist/admin/api/index.js +4 -2
  4. package/dist/admin/api/index.js.map +1 -1
  5. package/dist/admin/api/integrations.d.ts +60 -0
  6. package/dist/admin/api/integrations.d.ts.map +1 -0
  7. package/dist/admin/api/integrations.js +337 -0
  8. package/dist/admin/api/integrations.js.map +1 -0
  9. package/dist/admin/api/orders.d.ts +49 -0
  10. package/dist/admin/api/orders.d.ts.map +1 -1
  11. package/dist/admin/api/orders.js +293 -30
  12. package/dist/admin/api/orders.js.map +1 -1
  13. package/dist/admin/api/settings.d.ts.map +1 -1
  14. package/dist/admin/api/settings.js +6 -1
  15. package/dist/admin/api/settings.js.map +1 -1
  16. package/dist/admin/api/shipping.d.ts +49 -0
  17. package/dist/admin/api/shipping.d.ts.map +1 -1
  18. package/dist/admin/api/shipping.js +144 -0
  19. package/dist/admin/api/shipping.js.map +1 -1
  20. package/dist/admin/components/AdminNav.d.ts.map +1 -1
  21. package/dist/admin/components/AdminNav.js +3 -1
  22. package/dist/admin/components/AdminNav.js.map +1 -1
  23. package/dist/admin/components/AdminSelect.d.ts.map +1 -1
  24. package/dist/admin/components/AdminSelect.js +2 -0
  25. package/dist/admin/components/AdminSelect.js.map +1 -1
  26. package/dist/admin/components/DiscountsManager.d.ts.map +1 -1
  27. package/dist/admin/components/DiscountsManager.js +2 -2
  28. package/dist/admin/components/DiscountsManager.js.map +1 -1
  29. package/dist/admin/components/IntegrationsManager.d.ts +5 -0
  30. package/dist/admin/components/IntegrationsManager.d.ts.map +1 -0
  31. package/dist/admin/components/IntegrationsManager.js +138 -0
  32. package/dist/admin/components/IntegrationsManager.js.map +1 -0
  33. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  34. package/dist/admin/components/OrderDetails.js +75 -3
  35. package/dist/admin/components/OrderDetails.js.map +1 -1
  36. package/dist/admin/components/ShippingSettings.d.ts.map +1 -1
  37. package/dist/admin/components/ShippingSettings.js +316 -137
  38. package/dist/admin/components/ShippingSettings.js.map +1 -1
  39. package/dist/admin/components/UsersTable.d.ts.map +1 -1
  40. package/dist/admin/components/UsersTable.js +2 -0
  41. package/dist/admin/components/UsersTable.js.map +1 -1
  42. package/dist/admin/components/VariantManager.d.ts.map +1 -1
  43. package/dist/admin/components/VariantManager.js +8 -2
  44. package/dist/admin/components/VariantManager.js.map +1 -1
  45. package/dist/admin/components/index.d.ts +2 -0
  46. package/dist/admin/components/index.d.ts.map +1 -1
  47. package/dist/admin/components/index.js +1 -0
  48. package/dist/admin/components/index.js.map +1 -1
  49. package/dist/admin/index.d.ts +1 -1
  50. package/dist/admin/index.d.ts.map +1 -1
  51. package/dist/admin/index.js +2 -0
  52. package/dist/admin/index.js.map +1 -1
  53. package/dist/admin/integrations/brevo-logo.d.ts +11 -0
  54. package/dist/admin/integrations/brevo-logo.d.ts.map +1 -0
  55. package/dist/admin/integrations/brevo-logo.js +11 -0
  56. package/dist/admin/integrations/brevo-logo.js.map +1 -0
  57. package/dist/admin/integrations/registry.d.ts +116 -0
  58. package/dist/admin/integrations/registry.d.ts.map +1 -0
  59. package/dist/admin/integrations/registry.js +389 -0
  60. package/dist/admin/integrations/registry.js.map +1 -0
  61. package/dist/admin/types.d.ts +13 -0
  62. package/dist/admin/types.d.ts.map +1 -1
  63. package/dist/checkout/api/colissimo-relays.d.ts +24 -0
  64. package/dist/checkout/api/colissimo-relays.d.ts.map +1 -0
  65. package/dist/checkout/api/colissimo-relays.js +60 -0
  66. package/dist/checkout/api/colissimo-relays.js.map +1 -0
  67. package/dist/checkout/api/manual.d.ts.map +1 -1
  68. package/dist/checkout/api/manual.js +21 -5
  69. package/dist/checkout/api/manual.js.map +1 -1
  70. package/dist/checkout/api/shipping.d.ts.map +1 -1
  71. package/dist/checkout/api/shipping.js +60 -82
  72. package/dist/checkout/api/shipping.js.map +1 -1
  73. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  74. package/dist/checkout/components/CheckoutFlow.js +141 -3
  75. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  76. package/dist/checkout/components/ShippingOptions.d.ts +8 -1
  77. package/dist/checkout/components/ShippingOptions.d.ts.map +1 -1
  78. package/dist/checkout/components/ShippingOptions.js +40 -11
  79. package/dist/checkout/components/ShippingOptions.js.map +1 -1
  80. package/dist/checkout/components/colissimo-logo.d.ts +12 -0
  81. package/dist/checkout/components/colissimo-logo.d.ts.map +1 -0
  82. package/dist/checkout/components/colissimo-logo.js +12 -0
  83. package/dist/checkout/components/colissimo-logo.js.map +1 -0
  84. package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
  85. package/dist/checkout/server/create-checkout-session.js +55 -13
  86. package/dist/checkout/server/create-checkout-session.js.map +1 -1
  87. package/dist/checkout/server/handle-webhook.js +7 -3
  88. package/dist/checkout/server/handle-webhook.js.map +1 -1
  89. package/dist/checkout/server/order-service.d.ts.map +1 -1
  90. package/dist/checkout/server/order-service.js +25 -0
  91. package/dist/checkout/server/order-service.js.map +1 -1
  92. package/dist/checkout/server/pricing.d.ts +20 -0
  93. package/dist/checkout/server/pricing.d.ts.map +1 -1
  94. package/dist/checkout/server/pricing.js +51 -5
  95. package/dist/checkout/server/pricing.js.map +1 -1
  96. package/dist/checkout/types.d.ts +57 -1
  97. package/dist/checkout/types.d.ts.map +1 -1
  98. package/dist/core/db/queries.d.ts +143 -21
  99. package/dist/core/db/queries.d.ts.map +1 -1
  100. package/dist/core/db/queries.js +228 -1
  101. package/dist/core/db/queries.js.map +1 -1
  102. package/dist/core/db/schema.d.ts +323 -5
  103. package/dist/core/db/schema.d.ts.map +1 -1
  104. package/dist/core/db/schema.js +70 -0
  105. package/dist/core/db/schema.js.map +1 -1
  106. package/dist/core/i18n/messages/en.d.ts.map +1 -1
  107. package/dist/core/i18n/messages/en.js +132 -0
  108. package/dist/core/i18n/messages/en.js.map +1 -1
  109. package/dist/core/i18n/messages/fr.d.ts.map +1 -1
  110. package/dist/core/i18n/messages/fr.js +132 -0
  111. package/dist/core/i18n/messages/fr.js.map +1 -1
  112. package/dist/products/api/products.d.ts +3 -1
  113. package/dist/products/api/products.d.ts.map +1 -1
  114. package/dist/products/api/products.js +29 -3
  115. package/dist/products/api/products.js.map +1 -1
  116. package/dist/products/components/VariantSelector.d.ts.map +1 -1
  117. package/dist/products/components/VariantSelector.js +4 -1
  118. package/dist/products/components/VariantSelector.js.map +1 -1
  119. package/dist/products/hooks/useProducts.d.ts +3 -1
  120. package/dist/products/hooks/useProducts.d.ts.map +1 -1
  121. package/dist/products/hooks/useProducts.js +4 -2
  122. package/dist/products/hooks/useProducts.js.map +1 -1
  123. package/dist/shipping/colissimo.d.ts +166 -0
  124. package/dist/shipping/colissimo.d.ts.map +1 -0
  125. package/dist/shipping/colissimo.js +424 -0
  126. package/dist/shipping/colissimo.js.map +1 -0
  127. package/dist/shipping/index.d.ts +4 -1
  128. package/dist/shipping/index.d.ts.map +1 -1
  129. package/dist/shipping/index.js +3 -1
  130. package/dist/shipping/index.js.map +1 -1
  131. package/docs/changelog/epoch-07-shipping-carriers.md +125 -0
  132. package/docs/changelog/epoch-08-integrations.md +125 -0
  133. package/package.json +6 -1
  134. package/templates/store-template/.claude/skills/build-feature/SKILL.md +19 -0
  135. package/templates/store-template/.claude/skills/manage-products/SKILL.md +1 -1
  136. package/templates/store-template/app/admin/(dashboard)/integrations/page.tsx +17 -0
  137. package/templates/store-template/app/api/admin/integrations/route.ts +9 -0
  138. package/templates/store-template/app/api/admin/orders/[id]/colissimo-defaults/route.ts +6 -0
  139. package/templates/store-template/app/api/admin/orders/[id]/label/route.ts +6 -0
  140. package/templates/store-template/app/api/admin/orders/[id]/ship-with-colissimo/route.ts +6 -0
  141. package/templates/store-template/app/api/admin/shipping/colissimo/route.ts +10 -0
  142. package/templates/store-template/app/api/checkout/colissimo-relays/route.ts +4 -0
  143. package/templates/store-template/app/faq/page.tsx +42 -56
  144. package/templates/store-template/app/privacy/page.tsx +65 -105
  145. package/templates/store-template/app/products/[slug]/ProductDetailClient.tsx +6 -3
  146. package/templates/store-template/app/products/page.tsx +63 -1
  147. package/templates/store-template/app/terms/page.tsx +70 -198
  148. package/templates/store-template/package.json +1 -1
@@ -0,0 +1,125 @@
1
+ # E8 Integrations hub — 0.21.0..0.21.0
2
+
3
+ ## What landed
4
+ The admin gained `/admin/integrations` — ONE surface where the merchant
5
+ manages credentials for every external service connected to the store
6
+ (Brevo, PayPal, Turnstile, GA4, Google Ads, Meta, TikTok, Klaviyo, Intercom,
7
+ Trustpilot, plus chat-installed custom integrations). Before this, rotating
8
+ a key meant re-running a Feature Store card (a paid AI turn) or a per-store
9
+ custom settings page; now it is a 20-second admin edit. Installing or
10
+ removing an integration stays a Features Store / chat action — this page
11
+ manages what is installed, it is not a catalog.
12
+
13
+ ## SDK-internal (free via pin bump)
14
+ - `IntegrationsManager` (exported from `@rovela-ai/sdk/admin`) — the page
15
+ component; renders its own `AdminPageHeader` (bare-mount page wrapper).
16
+ - `admin/integrations/registry.ts` — the static registry of known providers.
17
+ Each entry names its storage: dedicated `store_settings` columns for
18
+ secrets (brevo_*, paypal_*, turnstile_*) or a slot in the `analytics`
19
+ JSONB (ga4, google_ads, meta, tiktok, klaviyo, intercom, trustpilot).
20
+ These storage locations are a CONTRACT with the platform's Feature Store
21
+ card prompts — never rename one side alone.
22
+ - Presence semantics: an entry renders only when its storage is detectable
23
+ on the store (column exists / JSONB key present) — a store never sees
24
+ integrations it does not have.
25
+ - Secrets: GET returns `{ hasValue, masked }` only; PUT is write-only
26
+ (blank keeps the stored value). Brevo (`GET /v3/account`) and PayPal
27
+ (OAuth token grant) are validated server-side BEFORE persisting — a dead
28
+ key is never saved.
29
+ - AdminNav Configuration group gained the `integrations` item (after Tax).
30
+
31
+ ## Template contract
32
+ - `app/admin/(dashboard)/integrations/page.tsx` (NEW) — bare mount of
33
+ `IntegrationsManager`.
34
+ - `app/api/admin/integrations/route.ts` (NEW) — re-export
35
+ `getIntegrations as GET, saveIntegration as PUT` from
36
+ `@rovela-ai/sdk/admin/api`. Without it the page shows a permanent load
37
+ error.
38
+
39
+ ## Behavior contracts
40
+ - NEW self-healed column `store_settings.custom_integrations jsonb NOT NULL
41
+ DEFAULT '[]'` — descriptor registry for integrations the SDK does not
42
+ know. Deliberately NOT in the drizzle schema (raw-SQL only, like
43
+ `analytics`), so `findSettings` full-row selects never depend on it; the
44
+ integrations API self-heals it at its own chokepoint. Descriptors carry
45
+ `{key, label, fields:[{id = store_settings column name, label, secret}]}`;
46
+ values ALWAYS live in dedicated store_settings columns (merchant-secret
47
+ rule). Malformed descriptors are skipped, never rendered.
48
+ - Custom-integration writes validate every field id against live
49
+ `information_schema` before a quoted-identifier UPDATE.
50
+ - Store-side Claude Code contract (sandbox CLAUDE.md §7.6 + build-feature
51
+ skill): NEVER build a settings page / credentials form / nav entry for
52
+ integration keys — persist to canonical storage and (for novel
53
+ integrations) register a descriptor so this page renders the form.
54
+
55
+ ## Custom-code hazards (audit greps for CC)
56
+ - Grep `app/admin` for hand-built integration settings pages (e.g. a
57
+ `marketing/page.tsx` managing an API key) — they are superseded by
58
+ `/admin/integrations` when their storage matches a registry entry
59
+ (brevo_api_key/brevo_list_id/brevo_enabled for Brevo). Presentational-only
60
+ duplicates should be REMOVED (page + its API route + its `navItems`
61
+ entry); keep any business logic that is not credential management.
62
+ - Grep `navItems` in `app/admin/(dashboard)/layout.tsx` for entries whose
63
+ page only managed credentials — drop them alongside the page.
64
+
65
+ ## MERCHANT-DATA HAZARD
66
+ None — no existing data changes meaning. The page is a view/editor over
67
+ storage that already exists; runtime readers are untouched.
68
+
69
+ ## Also in 0.21.0 — storefront + admin correctness batch
70
+
71
+ SDK-internal (free via pin bump):
72
+ - Products API `?category=` accepts a SLUG as well as a UUID (store-authored
73
+ mega-menu links carry slugs; pre-0.21 a slug silently matched nothing and
74
+ the link showed the FULL catalog). Unknown slug → honest empty result.
75
+ - Products API + `useProducts` gained `onSale` (comparePrice > price), with
76
+ `countProducts` pagination parity — no more raw-SQL sale pages.
77
+ - `VariantSelector` renders attribute values in FIRST-SEEN order, not
78
+ alphabetical (sizes read S / M / L again, not L / M / S / XL).
79
+ - `VariantManager` sync compares variant combinations as KEY SETS, not
80
+ index-by-index — an order-only mismatch used to fire a normalizing
81
+ onChange right after ProductForm captured its dirty baseline, raising a
82
+ phantom "Unsaved changes" save bar on EVERY variant product open.
83
+ - Discounts page empty state got the full first-run treatment (icon +
84
+ title); `compact` stays for its nested sub-panels only.
85
+ - Admin conventions pass (shipping + integrations + order details):
86
+ Connected states are `admin-badge-success` pills (bare green text + icon
87
+ medallions removed), `admin-radio`/`admin-helper-text`/
88
+ `admin-label-required` adopted, `admin-spinner` replaces the animated
89
+ RefreshCw, sentence-case sweep, 16px icons, responsive form grids,
90
+ aria-labels on icon buttons, native select → AdminSelect and the
91
+ signature checkbox → AdminSwitch on the order ship card.
92
+ - i18n catalogs gained `faqPage.q/a.*`, `terms.*`, `privacy.*`,
93
+ `legal.*`, and `listing.{category,onSale,clearFilter}` keys (en + fr).
94
+
95
+ Template contract (new stores + SDK-update flow):
96
+ - `app/products/page.tsx` now READS `?category=` and `?sale=1` from the URL
97
+ (with the required Suspense boundary around `useSearchParams`) and shows
98
+ an active-filter chip. This is the page half of the category-links fix.
99
+ - `app/products/[slug]/ProductDetailClient.tsx` coerces prices to numbers
100
+ before the strikethrough comparison ('185.00' > '55.50' was
101
+ lexicographically false — the compare-at price never showed).
102
+ - `app/{faq,terms,privacy}/page.tsx` body copy now renders from the i18n
103
+ catalogs (was English literals even on French-default stores); FAQ
104
+ country names/lists localize via Intl.DisplayNames/ListFormat.
105
+
106
+ ## Custom-code hazards (0.21.0 batch — audit greps for CC)
107
+ - Grep custom pages/sections for hand-rolled category or sale filtering
108
+ (raw SQL, client-side filters) — `/api/products?category=<slug>` and
109
+ `?onSale=1` now do it natively.
110
+ - Grep for links to `/products?category=` — they WORK now; stores that
111
+ worked around the bug with custom category pages can keep or migrate them.
112
+
113
+ ## Verify
114
+ - `/admin/integrations` renders under Configuration in the admin nav.
115
+ - A store with Brevo columns shows the Brevo card; entering a bad key
116
+ surfaces Brevo's rejection inline and persists nothing.
117
+ - A saved secret displays as "ends in …xxxx"; saving with the secret blank
118
+ keeps the stored value.
119
+ - `SELECT custom_integrations FROM store_settings` returns `[]` (or the
120
+ registered descriptors) — never an error.
121
+ - `/products?category=<existing slug>` shows only that category;
122
+ `/products?sale=1` shows only discounted products (chip + clear visible).
123
+ - A product with comparePrice > price shows the strikethrough on its PDP.
124
+ - Opening a variant product in the admin does NOT raise the save bar.
125
+ - `/faq`, `/terms`, `/privacy` render in French on a fr-default store.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovela-ai/sdk",
3
- "version": "0.19.8",
3
+ "version": "0.21.0",
4
4
  "description": "Rovela SDK - Pre-built e-commerce components for AI-powered store generation",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -90,6 +90,10 @@
90
90
  "types": "./dist/checkout/api/pickup-locations.d.ts",
91
91
  "import": "./dist/checkout/api/pickup-locations.js"
92
92
  },
93
+ "./checkout/api/colissimo-relays": {
94
+ "types": "./dist/checkout/api/colissimo-relays.d.ts",
95
+ "import": "./dist/checkout/api/colissimo-relays.js"
96
+ },
93
97
  "./checkout/server": {
94
98
  "types": "./dist/checkout/server/index.d.ts",
95
99
  "import": "./dist/checkout/server/index.js"
@@ -292,6 +296,7 @@
292
296
  },
293
297
  "scripts": {
294
298
  "build": "tsc && cp -r src/admin/styles dist/admin/",
299
+ "prepublishOnly": "npm run build && cd ../.. && bash scripts/check-prompts.sh",
295
300
  "dev": "tsc --watch",
296
301
  "clean": "rm -rf dist tsconfig.tsbuildinfo",
297
302
  "db:push": "drizzle-kit push",
@@ -191,6 +191,25 @@ Active state is auto-detected from the URL — no extra config needed.
191
191
  | Admin API auth check with `getServerSession` | Admin pages are already protected by `AdminGuard` layout |
192
192
  | Forgot admin sidebar item | If feature has admin page, MUST add `navItems` to AdminLayout in layout.tsx |
193
193
 
194
+ ## External-service credentials (integrations)
195
+
196
+ If the feature connects to an external service (API key, secret, tracking ID):
197
+
198
+ - Persist credentials to `store_settings` — dedicated columns for secrets (`ALTER TABLE store_settings ADD COLUMN IF NOT EXISTS …`, read server-side at request time; NEVER `.env.local`, NEVER `NEXT_PUBLIC_`), or the `analytics` JSONB for public tracking IDs.
199
+ - The merchant manages them at `/admin/integrations` (SDK page, already mounted). **Do NOT build a settings page, credentials form, or nav entry for keys.**
200
+ - Known keys are auto-rendered (brevo, paypal, turnstile, ga4, google_ads, meta, tiktok, klaviyo, intercom, trustpilot). A NOVEL integration registers a descriptor (dedup by key, then append):
201
+
202
+ ```sql
203
+ ALTER TABLE store_settings ADD COLUMN IF NOT EXISTS custom_integrations jsonb NOT NULL DEFAULT '[]'::jsonb;
204
+ UPDATE store_settings SET custom_integrations =
205
+ (SELECT COALESCE(jsonb_agg(e), '[]'::jsonb)
206
+ FROM jsonb_array_elements(custom_integrations) e
207
+ WHERE e->>'key' <> '<key>')
208
+ || jsonb_build_array('{"key":"<key>","label":"<Label>","fields":[{"id":"<store_settings_column>","label":"API key","secret":true,"required":true}]}'::jsonb);
209
+ ```
210
+
211
+ Field `id`s = the store_settings column names (ALTERed first); mark secrets `"secret": true`. Tell the merchant credentials live under **Configuration → Integrations**.
212
+
194
213
  ## Checklist
195
214
 
196
215
  - [ ] Tables created and verified with `list_objects`
@@ -33,7 +33,7 @@ Usage: `nextjs_call` with `port="3000"` and `toolName="execute_sql"`
33
33
  | `orders` | id, customerId, email, status, total, currency (ISO snapshot — display orders in THIS currency), refundAmount, returnReason, shippingAddress (JSONB) |
34
34
  | `order_items` | id, orderId (CASCADE), productId, variantId, name, price, quantity |
35
35
  | `customers` | id, email (unique), name, passwordHash, phone, groupId, locale, stripeCustomerId, emailVerified |
36
- | `store_settings` | id, storeName, storeEmail, storeCurrency, storeTimezone, storeCountry, defaultLocale, enabledLocales, taxIncludedInPrices, shippingMode |
36
+ | `store_settings` | id, storeName, storeEmail, storeCurrency, storeTimezone, storeCountry, defaultLocale, enabledLocales, taxIncludedInPrices, shippingMode (retired — the shipping_carriers registry decides; carriers have provider manual/shippo/colissimo, rates have fulfillment home/relay) |
37
37
 
38
38
  **Money contract (load-bearing)**: `price`, `total` etc. are DECIMAL CURRENCY UNITS — `29.99`, NEVER cents (`2999`). No ×100/÷100 anywhere except the Stripe API boundary. On multilingual stores (`enabled_locales`), catalog content can carry per-locale `translations` — when editing names/descriptions, keep every enabled locale in sync (CLAUDE.md §5.5).
39
39
 
@@ -0,0 +1,17 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Integrations Page
5
+ *
6
+ * Credentials for the external services connected to this store (Brevo,
7
+ * PayPal, analytics pixels, chat-installed custom integrations…). The
8
+ * IntegrationsManager renders the page header itself — this wrapper stays a
9
+ * bare mount. Installing or removing an integration is a Features Store /
10
+ * chat action on the Rovela platform; this page manages what is installed.
11
+ */
12
+
13
+ import { IntegrationsManager } from '@rovela-ai/sdk/admin'
14
+
15
+ export default function AdminIntegrationsPage() {
16
+ return <IntegrationsManager />
17
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Admin Integrations API Route
3
+ *
4
+ * GET /api/admin/integrations - Installed integrations with masked secrets
5
+ * PUT /api/admin/integrations - Save one integration's credentials/settings
6
+ * (provider-validated before persisting)
7
+ */
8
+
9
+ export { getIntegrations as GET, saveIntegration as PUT } from '@rovela-ai/sdk/admin/api'
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Pre-fill data for the "Ship with Colissimo" card (weight suggestion, relay)
3
+ */
4
+ import { getColissimoDefaults } from '@rovela-ai/sdk/admin/api'
5
+
6
+ export { getColissimoDefaults as GET }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Download the stored carrier label for an order (admin-gated — labels carry PII)
3
+ */
4
+ import { downloadOrderLabel } from '@rovela-ai/sdk/admin/api'
5
+
6
+ export { downloadOrderLabel as GET }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Generate a Colissimo shipping label for an order
3
+ */
4
+ import { shipWithColissimo } from '@rovela-ai/sdk/admin/api'
5
+
6
+ export { shipWithColissimo as POST }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Colissimo configuration (connection, sender address, relay delivery)
3
+ */
4
+ import {
5
+ getColissimoConfig,
6
+ saveColissimoSettings,
7
+ deleteColissimoSettings,
8
+ } from '@rovela-ai/sdk/admin/api'
9
+
10
+ export { getColissimoConfig as GET, saveColissimoSettings as POST, deleteColissimoSettings as DELETE }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Colissimo pickup-point search (checkout relay picker)
3
+ */
4
+ export { POST } from '@rovela-ai/sdk/checkout/api/colissimo-relays'
@@ -1,6 +1,6 @@
1
- import { localizedAlternates, getRequestLocale } from '@rovela-ai/sdk/core/server'
2
1
  import type { Metadata } from 'next'
3
- import { LocaleLink as Link, getT } from '@rovela-ai/sdk/core'
2
+ import { localizedAlternates, getRequestLocale } from '@rovela-ai/sdk/core/server'
3
+ import { LocaleLink as Link, getT, intlLocaleFor } from '@rovela-ai/sdk/core'
4
4
  import { findSettings } from '@rovela-ai/sdk/core/server'
5
5
  import { buildFaqJsonLd, serializeJsonLd } from '@rovela-ai/sdk/core'
6
6
  import { storeConfig } from '@/lib/store-config'
@@ -19,9 +19,14 @@ import {
19
19
  *
20
20
  * Server Component (no 'use client') so the schema + content are in the initial
21
21
  * HTML — AI crawlers don't execute JavaScript.
22
+ *
23
+ * Every Q&A body comes from the i18n catalogs (they were English literals
24
+ * pre-0.21, which shipped an English FAQ on French-default stores). Country
25
+ * names + list joining use Intl.DisplayNames / Intl.ListFormat so they
26
+ * localize without a hand-maintained map.
22
27
  */
23
28
 
24
- export async function generateMetadata() {
29
+ export async function generateMetadata(): Promise<Metadata> {
25
30
  const { locale } = await getRequestLocale()
26
31
  const t = getT(locale)
27
32
  return {
@@ -31,30 +36,22 @@ export async function generateMetadata() {
31
36
  }
32
37
  }
33
38
 
34
- const COUNTRY_NAMES: Record<string, string> = {
35
- US: 'the United States',
36
- CA: 'Canada',
37
- GB: 'the United Kingdom',
38
- AU: 'Australia',
39
- NZ: 'New Zealand',
40
- IE: 'Ireland',
41
- DE: 'Germany',
42
- FR: 'France',
43
- ES: 'Spain',
44
- IT: 'Italy',
45
- NL: 'the Netherlands',
46
- }
47
-
48
- function formatCountries(codes: string[]): string {
49
- const names = codes.map((c) => COUNTRY_NAMES[c] || c)
50
- if (names.length === 1) return names[0]
51
- if (names.length === 2) return `${names[0]} and ${names[1]}`
52
- return `${names.slice(0, -1).join(', ')}, and ${names[names.length - 1]}`
39
+ function formatCountries(codes: string[], intlLocale: string): string {
40
+ const display = new Intl.DisplayNames([intlLocale], { type: 'region' })
41
+ const names = codes.map((c) => {
42
+ try {
43
+ return display.of(c) || c
44
+ } catch {
45
+ return c
46
+ }
47
+ })
48
+ return new Intl.ListFormat(intlLocale, { style: 'long', type: 'conjunction' }).format(names)
53
49
  }
54
50
 
55
51
  export default async function FaqPage() {
56
52
  const { locale } = await getRequestLocale()
57
53
  const t = getT(locale)
54
+ const intlLocale = intlLocaleFor(locale)
58
55
  const settings = await findSettings().catch(() => null)
59
56
 
60
57
  const countries = shipsToCountries(settings)
@@ -67,49 +64,38 @@ export default async function FaqPage() {
67
64
 
68
65
  const faqs: Array<{ question: string; answer: string }> = [
69
66
  {
70
- question: 'What payment methods do you accept?',
67
+ question: t('faqPage.q.payment'),
68
+ answer: t('faqPage.a.payment'),
69
+ },
70
+ {
71
+ question: t('faqPage.q.countries'),
72
+ answer: t('faqPage.a.countries', { countries: formatCountries(countries, intlLocale) }),
73
+ },
74
+ {
75
+ question: t('faqPage.q.shippingCost'),
71
76
  answer:
72
- 'We accept major credit and debit cards through our secure checkout. Your payment details are encrypted and handled by our payment provider — we never store full card numbers.',
77
+ freeThreshold > 0
78
+ ? t('faqPage.a.shippingCostFree', { threshold: freeThreshold, currency })
79
+ : t('faqPage.a.shippingCost'),
73
80
  },
74
81
  {
75
- question: 'Which countries do you ship to?',
76
- answer: `We currently ship to ${formatCountries(countries)}.`,
82
+ question: t('faqPage.q.delivery'),
83
+ answer: t('faqPage.a.delivery', { processing }),
77
84
  },
78
- freeThreshold > 0
79
- ? {
80
- question: 'How much does shipping cost?',
81
- answer: `Shipping is calculated at checkout based on your address. Orders over ${freeThreshold} ${currency} qualify for free shipping.`,
82
- }
83
- : {
84
- question: 'How much does shipping cost?',
85
- answer:
86
- 'Shipping is calculated at checkout based on your delivery address and the items in your order.',
87
- },
88
85
  {
89
- question: 'How long will my order take to arrive?',
90
- answer: `Orders are typically processed within ${processing} business days and then shipped. Delivery time depends on your location and the carrier.`,
86
+ question: t('faqPage.q.returns'),
87
+ answer:
88
+ returnDays > 0
89
+ ? t('faqPage.a.returns', { days: returnDays })
90
+ : t('faqPage.a.noReturns'),
91
91
  },
92
- returnDays > 0
93
- ? {
94
- question: 'What is your return policy?',
95
- answer: `You can return unused items in their original condition within ${returnDays} days of delivery. Return shipping is the customer's responsibility unless the item arrived damaged or incorrect.`,
96
- }
97
- : {
98
- question: 'What is your return policy?',
99
- answer:
100
- 'All sales are final. If your item arrives damaged or incorrect, contact us right away and we will make it right.',
101
- },
102
92
  {
103
- question: 'Do I need an account to place an order?',
104
- answer: guestCheckout
105
- ? 'No account is required — you can check out as a guest. Creating an account lets you track orders and check out faster next time.'
106
- : 'Please create an account at checkout so you can track your order and manage returns.',
93
+ question: t('faqPage.q.account'),
94
+ answer: guestCheckout ? t('faqPage.a.accountGuest') : t('faqPage.a.accountRequired'),
107
95
  },
108
96
  {
109
- question: 'How can I contact you?',
110
- answer: email
111
- ? `Email us at ${email} and we'll get back to you as soon as we can. You can also use our contact page.`
112
- : `Reach us through our contact page and we'll get back to you as soon as we can.`,
97
+ question: t('faqPage.q.contact'),
98
+ answer: email ? t('faqPage.a.contactEmail', { email }) : t('faqPage.a.contactPage'),
113
99
  },
114
100
  ]
115
101