@rovela-ai/sdk 0.19.4 → 0.19.6

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 (236) hide show
  1. package/dist/admin/components/AdminPageHeader.d.ts.map +1 -1
  2. package/dist/admin/components/AdminPageHeader.js +9 -2
  3. package/dist/admin/components/AdminPageHeader.js.map +1 -1
  4. package/dist/admin/components/AdminUserMenu.d.ts.map +1 -1
  5. package/dist/admin/components/AdminUserMenu.js +2 -1
  6. package/dist/admin/components/AdminUserMenu.js.map +1 -1
  7. package/dist/admin/components/CustomerDetails.d.ts.map +1 -1
  8. package/dist/admin/components/CustomerDetails.js +2 -1
  9. package/dist/admin/components/CustomerDetails.js.map +1 -1
  10. package/dist/admin/components/EmailsManager.d.ts.map +1 -1
  11. package/dist/admin/components/EmailsManager.js +2 -1
  12. package/dist/admin/components/EmailsManager.js.map +1 -1
  13. package/dist/admin/components/LowStockAlert.d.ts.map +1 -1
  14. package/dist/admin/components/LowStockAlert.js +3 -2
  15. package/dist/admin/components/LowStockAlert.js.map +1 -1
  16. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  17. package/dist/admin/components/OrderDetails.js +2 -1
  18. package/dist/admin/components/OrderDetails.js.map +1 -1
  19. package/dist/admin/components/ProductTable.d.ts.map +1 -1
  20. package/dist/admin/components/ProductTable.js +4 -3
  21. package/dist/admin/components/ProductTable.js.map +1 -1
  22. package/dist/admin/components/RecentOrders.d.ts.map +1 -1
  23. package/dist/admin/components/RecentOrders.js +2 -1
  24. package/dist/admin/components/RecentOrders.js.map +1 -1
  25. package/dist/admin/components/SEOPreview.d.ts +2 -8
  26. package/dist/admin/components/SEOPreview.d.ts.map +1 -1
  27. package/dist/admin/components/SEOPreview.js +32 -5
  28. package/dist/admin/components/SEOPreview.js.map +1 -1
  29. package/dist/admin/components/SecondaryMetricsRow.d.ts.map +1 -1
  30. package/dist/admin/components/SecondaryMetricsRow.js +3 -2
  31. package/dist/admin/components/SecondaryMetricsRow.js.map +1 -1
  32. package/dist/admin/components/StatsCards.d.ts.map +1 -1
  33. package/dist/admin/components/StatsCards.js +2 -1
  34. package/dist/admin/components/StatsCards.js.map +1 -1
  35. package/docs/changelog/README.md +53 -0
  36. package/docs/changelog/epoch-01-chrome-consent.md +63 -0
  37. package/docs/changelog/epoch-02-platform-commerce.md +108 -0
  38. package/docs/changelog/epoch-03-i18n-files.md +72 -0
  39. package/docs/changelog/epoch-04-emails.md +65 -0
  40. package/docs/changelog/epoch-05-admin-experience.md +98 -0
  41. package/docs/changelog/epoch-06-mobile-integrity.md +92 -0
  42. package/docs/design/admin-ledger-visual-universe.html +1961 -0
  43. package/docs/design/admin-sidebar-and-band.html +542 -0
  44. package/package.json +3 -1
  45. package/templates/store-template/.claude/skills/add-section/SKILL.md +438 -0
  46. package/templates/store-template/.claude/skills/build-feature/SKILL.md +201 -0
  47. package/templates/store-template/.claude/skills/customize-theme/SKILL.md +167 -0
  48. package/templates/store-template/.claude/skills/debug-errors/SKILL.md +68 -0
  49. package/templates/store-template/.claude/skills/frontend-design/SKILL.md +176 -0
  50. package/templates/store-template/.claude/skills/generate-images/SKILL.md +115 -0
  51. package/templates/store-template/.claude/skills/manage-products/SKILL.md +187 -0
  52. package/templates/store-template/.claude/skills/override-sdk-component/SKILL.md +154 -0
  53. package/templates/store-template/.env.example +50 -0
  54. package/templates/store-template/.mcp.json +8 -0
  55. package/templates/store-template/.rovela/blueprint.example.json +190 -0
  56. package/templates/store-template/CLAUDE.md +14 -0
  57. package/templates/store-template/app/LayoutContent.tsx +106 -0
  58. package/templates/store-template/app/[...path]/page.tsx +79 -0
  59. package/templates/store-template/app/account/addresses/page.tsx +45 -0
  60. package/templates/store-template/app/account/layout.tsx +17 -0
  61. package/templates/store-template/app/account/orders/[id]/invoice/page.tsx +277 -0
  62. package/templates/store-template/app/account/orders/[id]/page.tsx +725 -0
  63. package/templates/store-template/app/account/orders/page.tsx +153 -0
  64. package/templates/store-template/app/account/page.tsx +184 -0
  65. package/templates/store-template/app/account/profile/page.tsx +434 -0
  66. package/templates/store-template/app/admin/(dashboard)/account/page.tsx +16 -0
  67. package/templates/store-template/app/admin/(dashboard)/analytics/events/page.tsx +14 -0
  68. package/templates/store-template/app/admin/(dashboard)/analytics/layout.tsx +48 -0
  69. package/templates/store-template/app/admin/(dashboard)/analytics/page.tsx +15 -0
  70. package/templates/store-template/app/admin/(dashboard)/analytics/visitors/page.tsx +14 -0
  71. package/templates/store-template/app/admin/(dashboard)/categories/[id]/page.tsx +38 -0
  72. package/templates/store-template/app/admin/(dashboard)/categories/new/page.tsx +35 -0
  73. package/templates/store-template/app/admin/(dashboard)/categories/page.tsx +223 -0
  74. package/templates/store-template/app/admin/(dashboard)/customers/[id]/page.tsx +26 -0
  75. package/templates/store-template/app/admin/(dashboard)/customers/page.tsx +33 -0
  76. package/templates/store-template/app/admin/(dashboard)/discounts/page.tsx +16 -0
  77. package/templates/store-template/app/admin/(dashboard)/emails/page.tsx +16 -0
  78. package/templates/store-template/app/admin/(dashboard)/files/page.tsx +16 -0
  79. package/templates/store-template/app/admin/(dashboard)/layout.tsx +44 -0
  80. package/templates/store-template/app/admin/(dashboard)/loading.tsx +88 -0
  81. package/templates/store-template/app/admin/(dashboard)/locations/page.tsx +16 -0
  82. package/templates/store-template/app/admin/(dashboard)/orders/[id]/page.tsx +66 -0
  83. package/templates/store-template/app/admin/(dashboard)/orders/page.tsx +26 -0
  84. package/templates/store-template/app/admin/(dashboard)/page.tsx +146 -0
  85. package/templates/store-template/app/admin/(dashboard)/payments/page.tsx +15 -0
  86. package/templates/store-template/app/admin/(dashboard)/products/[id]/page.tsx +38 -0
  87. package/templates/store-template/app/admin/(dashboard)/products/new/page.tsx +35 -0
  88. package/templates/store-template/app/admin/(dashboard)/products/page.tsx +31 -0
  89. package/templates/store-template/app/admin/(dashboard)/settings/page.tsx +46 -0
  90. package/templates/store-template/app/admin/(dashboard)/shipping/page.tsx +15 -0
  91. package/templates/store-template/app/admin/(dashboard)/tax/page.tsx +15 -0
  92. package/templates/store-template/app/admin/(dashboard)/users/[id]/page.tsx +114 -0
  93. package/templates/store-template/app/admin/(dashboard)/users/page.tsx +16 -0
  94. package/templates/store-template/app/admin/accept-invite/page.tsx +36 -0
  95. package/templates/store-template/app/admin/forgot-password/page.tsx +13 -0
  96. package/templates/store-template/app/admin/layout.tsx +47 -0
  97. package/templates/store-template/app/admin/login/page.tsx +528 -0
  98. package/templates/store-template/app/admin/reset-password/page.tsx +36 -0
  99. package/templates/store-template/app/api/account/addresses/[id]/route.ts +10 -0
  100. package/templates/store-template/app/api/account/addresses/route.ts +10 -0
  101. package/templates/store-template/app/api/account/change-password/route.ts +107 -0
  102. package/templates/store-template/app/api/account/orders/[id]/refund/route.ts +11 -0
  103. package/templates/store-template/app/api/account/orders/[id]/return/route.ts +14 -0
  104. package/templates/store-template/app/api/account/orders/[id]/route.ts +42 -0
  105. package/templates/store-template/app/api/account/orders/route.ts +29 -0
  106. package/templates/store-template/app/api/account/profile/route.ts +165 -0
  107. package/templates/store-template/app/api/admin/accept-invite/route.ts +11 -0
  108. package/templates/store-template/app/api/admin/analytics/events/route.ts +9 -0
  109. package/templates/store-template/app/api/admin/analytics/route.ts +10 -0
  110. package/templates/store-template/app/api/admin/analytics/visitors/route.ts +9 -0
  111. package/templates/store-template/app/api/admin/branding/favicon/route.ts +9 -0
  112. package/templates/store-template/app/api/admin/branding/logo/route.ts +9 -0
  113. package/templates/store-template/app/api/admin/categories/[id]/route.ts +9 -0
  114. package/templates/store-template/app/api/admin/categories/route.ts +9 -0
  115. package/templates/store-template/app/api/admin/check/route.ts +10 -0
  116. package/templates/store-template/app/api/admin/customer-groups/[id]/route.ts +10 -0
  117. package/templates/store-template/app/api/admin/customer-groups/route.ts +10 -0
  118. package/templates/store-template/app/api/admin/customers/[id]/addresses/[addressId]/route.ts +15 -0
  119. package/templates/store-template/app/api/admin/customers/[id]/addresses/route.ts +15 -0
  120. package/templates/store-template/app/api/admin/customers/[id]/route.ts +11 -0
  121. package/templates/store-template/app/api/admin/customers/bulk/route.ts +8 -0
  122. package/templates/store-template/app/api/admin/customers/route.ts +9 -0
  123. package/templates/store-template/app/api/admin/discounts/[id]/route.ts +11 -0
  124. package/templates/store-template/app/api/admin/discounts/route.ts +10 -0
  125. package/templates/store-template/app/api/admin/emails/preview/route.ts +4 -0
  126. package/templates/store-template/app/api/admin/emails/route.ts +5 -0
  127. package/templates/store-template/app/api/admin/emails/test/route.ts +4 -0
  128. package/templates/store-template/app/api/admin/files/route.ts +8 -0
  129. package/templates/store-template/app/api/admin/forgot-password/route.ts +7 -0
  130. package/templates/store-template/app/api/admin/linked-customer/route.ts +11 -0
  131. package/templates/store-template/app/api/admin/locations/[id]/route.ts +10 -0
  132. package/templates/store-template/app/api/admin/locations/route.ts +10 -0
  133. package/templates/store-template/app/api/admin/me/password/route.ts +13 -0
  134. package/templates/store-template/app/api/admin/me/route.ts +11 -0
  135. package/templates/store-template/app/api/admin/orders/[id]/refund/route.ts +7 -0
  136. package/templates/store-template/app/api/admin/orders/[id]/return/approve/route.ts +7 -0
  137. package/templates/store-template/app/api/admin/orders/[id]/return/reject/route.ts +7 -0
  138. package/templates/store-template/app/api/admin/orders/[id]/route.ts +9 -0
  139. package/templates/store-template/app/api/admin/orders/[id]/ship-with-shippo/route.ts +6 -0
  140. package/templates/store-template/app/api/admin/orders/[id]/shippo-rates/route.ts +6 -0
  141. package/templates/store-template/app/api/admin/orders/bulk/route.ts +8 -0
  142. package/templates/store-template/app/api/admin/orders/route.ts +9 -0
  143. package/templates/store-template/app/api/admin/payment-methods/[id]/route.ts +10 -0
  144. package/templates/store-template/app/api/admin/payment-methods/route.ts +10 -0
  145. package/templates/store-template/app/api/admin/products/[id]/route.ts +9 -0
  146. package/templates/store-template/app/api/admin/products/bulk/route.ts +9 -0
  147. package/templates/store-template/app/api/admin/products/route.ts +9 -0
  148. package/templates/store-template/app/api/admin/products/stats/route.ts +10 -0
  149. package/templates/store-template/app/api/admin/reset-password/route.ts +10 -0
  150. package/templates/store-template/app/api/admin/settings/route.ts +11 -0
  151. package/templates/store-template/app/api/admin/setup/route.ts +10 -0
  152. package/templates/store-template/app/api/admin/setup-guide/manual-mark/route.ts +13 -0
  153. package/templates/store-template/app/api/admin/setup-guide/route.ts +10 -0
  154. package/templates/store-template/app/api/admin/shipping/carriers/[id]/route.ts +9 -0
  155. package/templates/store-template/app/api/admin/shipping/carriers/route.ts +9 -0
  156. package/templates/store-template/app/api/admin/shipping/rates/[id]/route.ts +9 -0
  157. package/templates/store-template/app/api/admin/shipping/rates/route.ts +9 -0
  158. package/templates/store-template/app/api/admin/shipping/shippo/api-key/route.ts +1 -0
  159. package/templates/store-template/app/api/admin/shipping/shippo/validate/route.ts +1 -0
  160. package/templates/store-template/app/api/admin/shipping/zones/[id]/route.ts +9 -0
  161. package/templates/store-template/app/api/admin/shipping/zones/route.ts +9 -0
  162. package/templates/store-template/app/api/admin/stats/route.ts +9 -0
  163. package/templates/store-template/app/api/admin/stripe-status/route.ts +1 -0
  164. package/templates/store-template/app/api/admin/tax-zones/[id]/route.ts +7 -0
  165. package/templates/store-template/app/api/admin/tax-zones/route.ts +7 -0
  166. package/templates/store-template/app/api/admin/users/[id]/route.ts +14 -0
  167. package/templates/store-template/app/api/admin/users/route.ts +12 -0
  168. package/templates/store-template/app/api/admin-auth/[...nextauth]/route.ts +17 -0
  169. package/templates/store-template/app/api/analytics/track/route.ts +12 -0
  170. package/templates/store-template/app/api/auth/[...nextauth]/route.ts +19 -0
  171. package/templates/store-template/app/api/auth/forgot-password/route.ts +9 -0
  172. package/templates/store-template/app/api/auth/register/route.ts +9 -0
  173. package/templates/store-template/app/api/auth/resend-verification/route.ts +9 -0
  174. package/templates/store-template/app/api/auth/reset-password/route.ts +10 -0
  175. package/templates/store-template/app/api/auth/verify-email/route.ts +10 -0
  176. package/templates/store-template/app/api/categories/route.ts +10 -0
  177. package/templates/store-template/app/api/checkout/countries/route.ts +7 -0
  178. package/templates/store-template/app/api/checkout/discount/route.ts +9 -0
  179. package/templates/store-template/app/api/checkout/manual/route.ts +10 -0
  180. package/templates/store-template/app/api/checkout/payment-methods/route.ts +8 -0
  181. package/templates/store-template/app/api/checkout/pickup-locations/route.ts +8 -0
  182. package/templates/store-template/app/api/checkout/route.ts +10 -0
  183. package/templates/store-template/app/api/checkout/shipping/route.ts +21 -0
  184. package/templates/store-template/app/api/checkout/status/route.ts +10 -0
  185. package/templates/store-template/app/api/contact/route.ts +164 -0
  186. package/templates/store-template/app/api/media/presign/route.ts +7 -0
  187. package/templates/store-template/app/api/media/route.ts +7 -0
  188. package/templates/store-template/app/api/products/[slug]/route.ts +10 -0
  189. package/templates/store-template/app/api/products/route.ts +10 -0
  190. package/templates/store-template/app/api/store/settings/route.ts +12 -0
  191. package/templates/store-template/app/api/webhooks/stripe/route.ts +63 -0
  192. package/templates/store-template/app/auth/forgot-password/page.tsx +38 -0
  193. package/templates/store-template/app/auth/layout.tsx +17 -0
  194. package/templates/store-template/app/auth/reset-password/page.tsx +81 -0
  195. package/templates/store-template/app/auth/signin/page.tsx +62 -0
  196. package/templates/store-template/app/auth/signup/page.tsx +38 -0
  197. package/templates/store-template/app/auth/verify-email/page.tsx +185 -0
  198. package/templates/store-template/app/cart/page.tsx +137 -0
  199. package/templates/store-template/app/checkout/page.tsx +88 -0
  200. package/templates/store-template/app/checkout/success/page.tsx +50 -0
  201. package/templates/store-template/app/contact/layout.tsx +24 -0
  202. package/templates/store-template/app/contact/page.tsx +321 -0
  203. package/templates/store-template/app/faq/page.tsx +153 -0
  204. package/templates/store-template/app/globals.css +99 -0
  205. package/templates/store-template/app/layout.tsx +269 -0
  206. package/templates/store-template/app/not-found.tsx +35 -0
  207. package/templates/store-template/app/page.tsx +23 -0
  208. package/templates/store-template/app/privacy/page.tsx +281 -0
  209. package/templates/store-template/app/products/[slug]/ProductDetailClient.tsx +179 -0
  210. package/templates/store-template/app/products/[slug]/page.tsx +149 -0
  211. package/templates/store-template/app/products/layout.tsx +26 -0
  212. package/templates/store-template/app/products/page.tsx +102 -0
  213. package/templates/store-template/app/robots.ts +25 -0
  214. package/templates/store-template/app/sitemap.ts +76 -0
  215. package/templates/store-template/app/terms/page.tsx +313 -0
  216. package/templates/store-template/components/PathSyncBridge.tsx +61 -0
  217. package/templates/store-template/components/Providers.tsx +37 -0
  218. package/templates/store-template/components/layout/Footer.tsx +150 -0
  219. package/templates/store-template/components/layout/Header.tsx +162 -0
  220. package/templates/store-template/components/sections/.gitkeep +7 -0
  221. package/templates/store-template/components/sections/CTA.tsx +51 -0
  222. package/templates/store-template/components/sections/Features.tsx +91 -0
  223. package/templates/store-template/components/sections/Hero.tsx +85 -0
  224. package/templates/store-template/eslint.config.mjs +18 -0
  225. package/templates/store-template/lib/store-config.ts +77 -0
  226. package/templates/store-template/lib/store-policy.ts +68 -0
  227. package/templates/store-template/lib/utils.ts +21 -0
  228. package/templates/store-template/next-env.d.ts +6 -0
  229. package/templates/store-template/next.config.js +105 -0
  230. package/templates/store-template/package.json +32 -0
  231. package/templates/store-template/postcss.config.js +6 -0
  232. package/templates/store-template/proxy.ts +150 -0
  233. package/templates/store-template/public/rovela-logo.png +0 -0
  234. package/templates/store-template/tailwind.config.ts +74 -0
  235. package/templates/store-template/tsconfig.json +41 -0
  236. package/templates/store-template/types/next-auth.d.ts +32 -0
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Global CSS Styles
3
+ *
4
+ * NOTE: This file is a placeholder. Claude Agent will regenerate it using:
5
+ * import { generateGlobalsCss } from '@rovela-ai/sdk/theme'
6
+ * const css = generateGlobalsCss(blueprint.theme)
7
+ *
8
+ * This default configuration provides basic styling for the initial build.
9
+ */
10
+
11
+ @tailwind base;
12
+ @tailwind components;
13
+ @tailwind utilities;
14
+
15
+ @layer base {
16
+ :root {
17
+ /* Default color palette (neutral gray) */
18
+ --background: 0 0% 100%;
19
+ --foreground: 0 0% 3.9%;
20
+
21
+ --card: 0 0% 100%;
22
+ --card-foreground: 0 0% 3.9%;
23
+
24
+ --popover: 0 0% 100%;
25
+ --popover-foreground: 0 0% 3.9%;
26
+
27
+ --primary: 0 0% 9%;
28
+ --primary-foreground: 0 0% 98%;
29
+
30
+ --secondary: 0 0% 96.1%;
31
+ --secondary-foreground: 0 0% 9%;
32
+
33
+ --muted: 0 0% 96.1%;
34
+ --muted-foreground: 0 0% 45.1%;
35
+
36
+ --accent: 0 0% 96.1%;
37
+ --accent-foreground: 0 0% 9%;
38
+
39
+ --destructive: 0 84.2% 60.2%;
40
+ --destructive-foreground: 0 0% 98%;
41
+
42
+ --border: 0 0% 89.8%;
43
+ --input: 0 0% 89.8%;
44
+ --ring: 0 0% 3.9%;
45
+
46
+ --radius: 0.5rem;
47
+
48
+ /* Font variables */
49
+ --font-heading: 'Inter', system-ui, sans-serif;
50
+ --font-body: 'Inter', system-ui, sans-serif;
51
+ --font-mono: 'JetBrains Mono', monospace;
52
+ }
53
+
54
+ .dark {
55
+ --background: 0 0% 3.9%;
56
+ --foreground: 0 0% 98%;
57
+
58
+ --card: 0 0% 3.9%;
59
+ --card-foreground: 0 0% 98%;
60
+
61
+ --popover: 0 0% 3.9%;
62
+ --popover-foreground: 0 0% 98%;
63
+
64
+ --primary: 0 0% 98%;
65
+ --primary-foreground: 0 0% 9%;
66
+
67
+ --secondary: 0 0% 14.9%;
68
+ --secondary-foreground: 0 0% 98%;
69
+
70
+ --muted: 0 0% 14.9%;
71
+ --muted-foreground: 0 0% 63.9%;
72
+
73
+ --accent: 0 0% 14.9%;
74
+ --accent-foreground: 0 0% 98%;
75
+
76
+ --destructive: 0 62.8% 30.6%;
77
+ --destructive-foreground: 0 0% 98%;
78
+
79
+ --border: 0 0% 14.9%;
80
+ --input: 0 0% 14.9%;
81
+ --ring: 0 0% 83.1%;
82
+ }
83
+ }
84
+
85
+ @layer base {
86
+ * {
87
+ @apply border-border;
88
+ }
89
+ body {
90
+ @apply bg-background text-foreground;
91
+ font-family: var(--font-body);
92
+ }
93
+ h1, h2, h3, h4, h5, h6 {
94
+ font-family: var(--font-heading);
95
+ }
96
+ code, pre {
97
+ font-family: var(--font-mono);
98
+ }
99
+ }
@@ -0,0 +1,269 @@
1
+ import type { Metadata } from 'next'
2
+ import { Inter } from 'next/font/google'
3
+ import './globals.css'
4
+
5
+ import { headers, cookies } from 'next/headers'
6
+ import { redirect, permanentRedirect } from 'next/navigation'
7
+ import { Providers } from '@/components/Providers'
8
+ import { LayoutContent } from './LayoutContent'
9
+ import { getBlueprint, findSettings } from '@rovela-ai/sdk/core/server'
10
+ import {
11
+ resolveStoreLocaleConfig,
12
+ resolveRequestLocale,
13
+ resolveLocaleRedirect,
14
+ stripLocalePrefix,
15
+ LOCALE_HEADER,
16
+ GEO_LOCALE_HEADER,
17
+ PATHNAME_HEADER,
18
+ LOCALE_COOKIE,
19
+ } from '@rovela-ai/sdk/core'
20
+ import { localizedAlternates } from '@rovela-ai/sdk/core/server'
21
+ import { buildStoreJsonLdGraph, serializeJsonLd } from '@rovela-ai/sdk/core'
22
+ import { supportEmail, returnPolicyInput, shipsToCountries } from '@/lib/store-policy'
23
+ import { sanitizeCookieConsentConfig, consentRegionExempt } from '@rovela-ai/sdk/core/cookie-consent'
24
+
25
+ /**
26
+ * Font Configuration
27
+ *
28
+ * NOTE: Claude Agent will modify this based on blueprint.theme.typography
29
+ * The fonts below are defaults that work for most stores.
30
+ */
31
+ const fontBody = Inter({
32
+ subsets: ['latin'],
33
+ variable: '--font-body',
34
+ display: 'swap',
35
+ })
36
+
37
+ const fontHeading = Inter({
38
+ subsets: ['latin'],
39
+ variable: '--font-heading',
40
+ weight: ['500', '600', '700'],
41
+ display: 'swap',
42
+ })
43
+
44
+ /**
45
+ * Dynamic Metadata Configuration
46
+ *
47
+ * Reads store name from STORE_NAME env var (updated by branding editor)
48
+ * Favicon: prefers NEXT_PUBLIC_STORE_FAVICON_URL (custom upload),
49
+ * falls back to NEXT_PUBLIC_STORE_LOGO_URL (logo doubles as favicon).
50
+ * Merchants who don't upload a separate favicon get their logo as favicon
51
+ * by default.
52
+ * Falls back to blueprint data for SEO description and keywords.
53
+ */
54
+ export async function generateMetadata(): Promise<Metadata> {
55
+ // Try to load blueprint for rich SEO data
56
+ let blueprint = null
57
+ try {
58
+ blueprint = getBlueprint()
59
+ } catch {
60
+ // Blueprint might not exist during initial setup
61
+ }
62
+
63
+ // Live store settings — settings-first identity (name, favicon, logo).
64
+ // What the merchant edits in /admin must reach metadata without a redeploy.
65
+ // Fail-open: a DB hiccup falls back to env/blueprint.
66
+ const brandingSettings = await findSettings().catch(() => null)
67
+
68
+ // Store name: LIVE settings first, then the build-time env var, then blueprint.
69
+ const storeName =
70
+ brandingSettings?.storeName || process.env.STORE_NAME || blueprint?.store?.name || 'Store'
71
+
72
+ // SEO data from blueprint with fallbacks
73
+ const seoTitle = blueprint?.seo?.title || storeName
74
+ const seoDescription =
75
+ blueprint?.seo?.description ||
76
+ blueprint?.store?.description ||
77
+ `Shop at ${storeName}`
78
+ const seoKeywords = blueprint?.seo?.keywords || []
79
+
80
+ // Favicon: live store settings first (favicon_url / logo_url — updated
81
+ // instantly by the /admin branding page AND mirrored by the platform
82
+ // branding routes), then the build-time env vars for legacy stores whose
83
+ // settings row predates the mirror. Custom favicon wins; logo is the
84
+ // fallback mark. Fail-open — a DB hiccup falls back to env.
85
+ const faviconUrl =
86
+ brandingSettings?.faviconUrl ||
87
+ process.env.NEXT_PUBLIC_STORE_FAVICON_URL ||
88
+ brandingSettings?.logoUrl ||
89
+ process.env.NEXT_PUBLIC_STORE_LOGO_URL ||
90
+ null
91
+
92
+ // OpenGraph / Twitter share image: prefer a blueprint-supplied OG image,
93
+ // fall back to the store logo. Closes the "no OG image on storefronts" gap.
94
+ const storeUrl = process.env.NEXT_PUBLIC_APP_URL || ''
95
+ const ogImage =
96
+ blueprint?.seo?.ogImage ||
97
+ brandingSettings?.logoUrl ||
98
+ process.env.NEXT_PUBLIC_STORE_LOGO_URL ||
99
+ faviconUrl ||
100
+ null
101
+ let metadataBase: URL | undefined
102
+ try {
103
+ metadataBase = storeUrl ? new URL(storeUrl) : undefined
104
+ } catch {
105
+ metadataBase = undefined
106
+ }
107
+
108
+ // Locale alternates (self-canonical + hreflang) for every page that does
109
+ // not set its own — pages exporting `alternates` (PDP, listing, contact,
110
+ // legal, FAQ) override this field. Covers the homepage and any future page
111
+ // with zero per-page work. The header carries the ORIGINAL request path
112
+ // (locale prefix included); strip it to get the bare path.
113
+ let alternates: Metadata['alternates'] | undefined
114
+ try {
115
+ const hdrs = await headers()
116
+ const originalPath = hdrs.get(PATHNAME_HEADER) || '/'
117
+ alternates = await localizedAlternates(stripLocalePrefix(originalPath))
118
+ } catch {
119
+ // headers() unavailable (build-time) — omit alternates.
120
+ }
121
+
122
+ return {
123
+ ...(metadataBase && { metadataBase }),
124
+ ...(alternates && { alternates }),
125
+ title: {
126
+ default: seoTitle,
127
+ template: `%s | ${storeName}`,
128
+ },
129
+ description: seoDescription,
130
+ keywords: seoKeywords,
131
+ // Maximum rich-result + AI Overview eligibility: allow full snippets and
132
+ // large image previews for every search + AI shopping crawler.
133
+ robots: {
134
+ index: true,
135
+ follow: true,
136
+ googleBot: {
137
+ index: true,
138
+ follow: true,
139
+ 'max-image-preview': 'large',
140
+ 'max-snippet': -1,
141
+ 'max-video-preview': -1,
142
+ },
143
+ },
144
+ ...(faviconUrl && {
145
+ icons: {
146
+ icon: faviconUrl,
147
+ shortcut: faviconUrl,
148
+ apple: faviconUrl,
149
+ },
150
+ }),
151
+ openGraph: {
152
+ siteName: storeName,
153
+ type: 'website',
154
+ title: seoTitle,
155
+ description: seoDescription,
156
+ ...(storeUrl && { url: storeUrl }),
157
+ ...(ogImage && { images: [{ url: ogImage }] }),
158
+ },
159
+ twitter: {
160
+ card: 'summary_large_image',
161
+ title: seoTitle,
162
+ description: seoDescription,
163
+ ...(ogImage && { images: [ogImage] }),
164
+ },
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Root Layout
170
+ *
171
+ * This layout wraps all pages and provides:
172
+ * - Providers for SessionProvider (NextAuth)
173
+ * - LayoutContent for route-aware rendering (separates admin vs customer)
174
+ *
175
+ * Admin routes (/admin/*) get minimal wrapper - AdminLayout handles its own chrome.
176
+ * Customer routes get full store experience with Header, Footer, Cart.
177
+ *
178
+ * This separation ensures clear disconnection between admin panel and store.
179
+ */
180
+ export default async function RootLayout({
181
+ children,
182
+ }: {
183
+ children: React.ReactNode
184
+ }) {
185
+ // R021: read the merchant's cookie-banner master toggle once per
186
+ // request. Defaults to `true` for safety — if settings aren't seeded
187
+ // yet (very first render on a brand-new store) the banner shows and
188
+ // the merchant can turn it off in admin Settings.
189
+ let cookieBannerEnabled = true
190
+ let cookieConsentConfig: ReturnType<typeof sanitizeCookieConsentConfig> = {}
191
+ let settings: Awaited<ReturnType<typeof findSettings>> = null
192
+ try {
193
+ settings = await findSettings()
194
+ if (settings) {
195
+ cookieBannerEnabled = !!settings.cookieBannerEnabled
196
+ cookieConsentConfig = sanitizeCookieConsentConfig(settings.cookieConsent)
197
+ }
198
+ } catch {
199
+ // Settings table not reachable — fail open (banner shows). Safer
200
+ // than failing closed in EU jurisdictions.
201
+ }
202
+
203
+ // ── Locale (i18n core) ────────────────────────────────────────────────────
204
+ // The proxy parsed any /fr prefix into headers; here (where settings are
205
+ // available) we pick the render locale and apply locale-correctness
206
+ // redirects: strip prefixes the store doesn't serve, and route a visitor
207
+ // preference (explicit cookie, else IP-country hint) from a bare URL to
208
+ // its prefixed equivalent. See SDK core/i18n/resolve for the contract.
209
+ const hdrs = await headers()
210
+ const cookieStore = await cookies()
211
+ const localeConfig = resolveStoreLocaleConfig(settings)
212
+ const localeInput = {
213
+ prefixLocale: hdrs.get(LOCALE_HEADER),
214
+ cookieLocale: cookieStore.get(LOCALE_COOKIE)?.value,
215
+ geoLocale: hdrs.get(GEO_LOCALE_HEADER),
216
+ config: localeConfig,
217
+ }
218
+ const localeRedirect = resolveLocaleRedirect({
219
+ ...localeInput,
220
+ pathname: hdrs.get(PATHNAME_HEADER),
221
+ })
222
+ if (localeRedirect) {
223
+ // 308 for structural strips (URL shapes the store never serves), 307 for
224
+ // visitor-preference hops (cookie/geo dependent — never cacheable).
225
+ if (localeRedirect.permanent) permanentRedirect(localeRedirect.path)
226
+ redirect(localeRedirect.path)
227
+ }
228
+ const locale = resolveRequestLocale(localeInput)
229
+
230
+ // Site-wide Organization + WebSite JSON-LD (brand identity for AI assistants).
231
+ // Server-rendered so non-JS crawlers (GPTBot / OAI-SearchBot) can read it.
232
+ const storeJsonLd = buildStoreJsonLdGraph({
233
+ storeUrl: process.env.NEXT_PUBLIC_APP_URL || '',
234
+ storeName: settings?.storeName || process.env.STORE_NAME || 'Store',
235
+ logoUrl: settings?.logoUrl || process.env.NEXT_PUBLIC_STORE_LOGO_URL || null,
236
+ // Enriched entity signals (contactPoint + return policy + areaServed) from
237
+ // real configured data — see lib/store-policy. All optional in the builder,
238
+ // so a brand-new store with no settings still emits a valid base graph.
239
+ contactEmail: supportEmail(settings),
240
+ returnPolicy: returnPolicyInput(settings),
241
+ shipsToCountries: shipsToCountries(settings),
242
+ })
243
+
244
+ return (
245
+ <html lang={locale} className={`${fontBody.variable} ${fontHeading.variable}`}>
246
+ <body className="min-h-screen flex flex-col antialiased">
247
+ <script
248
+ type="application/ld+json"
249
+ dangerouslySetInnerHTML={{ __html: serializeJsonLd(storeJsonLd) }}
250
+ />
251
+ <Providers>
252
+ <LayoutContent
253
+ cookieBannerEnabled={cookieBannerEnabled}
254
+ cookieConsentConfig={cookieConsentConfig}
255
+ consentRegionExempt={consentRegionExempt(
256
+ cookieConsentConfig,
257
+ hdrs.get('x-rovela-country')
258
+ )}
259
+ locale={locale}
260
+ defaultLocale={localeConfig.defaultLocale}
261
+ enabledLocales={localeConfig.enabledLocales}
262
+ >
263
+ {children}
264
+ </LayoutContent>
265
+ </Providers>
266
+ </body>
267
+ </html>
268
+ )
269
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Localized 404 page — server component. Without this file Next.js serves
3
+ * its default English, unbranded 404. Locale comes from the proxy header via
4
+ * getRequestLocale (fail-open to the store default), so /fr/unknown-path
5
+ * renders French chrome.
6
+ */
7
+
8
+ import { LocaleLink as Link, getT } from '@rovela-ai/sdk/core'
9
+ import { getRequestLocale } from '@rovela-ai/sdk/core/server'
10
+
11
+ export default async function NotFound() {
12
+ const { locale } = await getRequestLocale().catch(() => ({ locale: 'en' as const }))
13
+ const t = getT(locale)
14
+
15
+ return (
16
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-24">
17
+ <div className="mx-auto max-w-md text-center">
18
+ <p className="text-sm font-semibold text-primary">404</p>
19
+ <h1 className="mt-2 text-3xl font-bold font-heading">{t('notFound.title')}</h1>
20
+ <p className="mt-4 text-muted-foreground">{t('notFound.body')}</p>
21
+ <div className="mt-8 flex items-center justify-center gap-4">
22
+ <Link
23
+ href="/"
24
+ className="rounded-md bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
25
+ >
26
+ {t('notFound.backHome')}
27
+ </Link>
28
+ <Link href="/products" className="text-sm font-medium text-primary hover:underline">
29
+ {t('footer.allProducts')}
30
+ </Link>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ )
35
+ }
@@ -0,0 +1,23 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Homepage
5
+ *
6
+ * Pre-baked with theme-aware sections for instant branded preview.
7
+ * Claude Code will replace these section components with custom versions
8
+ * that include business-specific content from the questionnaire and brand identity.
9
+ */
10
+
11
+ import Hero from '@/components/sections/Hero'
12
+ import Features from '@/components/sections/Features'
13
+ import CTA from '@/components/sections/CTA'
14
+
15
+ export default function HomePage() {
16
+ return (
17
+ <>
18
+ <Hero />
19
+ <Features />
20
+ <CTA />
21
+ </>
22
+ )
23
+ }