@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,102 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Products Listing Page
5
+ *
6
+ * Uses SDK components for product display:
7
+ * - ProductGrid: Responsive grid of product cards
8
+ * - ProductSearch: Search input with debouncing
9
+ * - ProductSort: Sort dropdown
10
+ * - useProducts: Data fetching hook
11
+ */
12
+
13
+ import { useState } from 'react'
14
+ import {
15
+ ProductGrid,
16
+ ProductSearch,
17
+ ProductSort,
18
+ useProducts,
19
+ } from '@rovela-ai/sdk/products'
20
+ import type { SortOption } from '@rovela-ai/sdk/products'
21
+ import { useT } from '@rovela-ai/sdk/core'
22
+
23
+ export default function ProductsPage() {
24
+ const t = useT()
25
+ const [search, setSearch] = useState('')
26
+ const [sort, setSort] = useState<SortOption>('newest')
27
+
28
+ const {
29
+ products,
30
+ isLoading,
31
+ error,
32
+ hasMore,
33
+ loadMore,
34
+ } = useProducts({
35
+ search,
36
+ sort,
37
+ limit: 12,
38
+ })
39
+
40
+ return (
41
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
42
+ {/* Page Header */}
43
+ <div className="mb-8">
44
+ <h1 className="text-3xl font-bold font-heading">{t('listing.title')}</h1>
45
+ <p className="mt-2 text-muted-foreground">
46
+ {t('listing.subtitle')}
47
+ </p>
48
+ </div>
49
+
50
+ {/* Filters Bar */}
51
+ <div className="flex flex-col sm:flex-row gap-4 mb-8">
52
+ <div className="flex-1 max-w-md">
53
+ <ProductSearch
54
+ value={search}
55
+ onSearch={setSearch}
56
+ />
57
+ </div>
58
+ <ProductSort
59
+ value={sort}
60
+ onChange={setSort}
61
+ />
62
+ </div>
63
+
64
+ {/* One message per state: on error, ONLY the error renders (the grid
65
+ is skipped — its built-in empty state would double the message);
66
+ when empty, the page's copy rides the grid's emptyState slot. */}
67
+ {error ? (
68
+ <div className="text-center py-12">
69
+ <p className="text-destructive">{t('listing.loadFailed')}</p>
70
+ </div>
71
+ ) : (
72
+ <ProductGrid
73
+ products={products}
74
+ isLoading={isLoading}
75
+ columns={{ sm: 2, md: 3, lg: 4 }}
76
+ cardProps={{
77
+ showQuickAdd: true,
78
+ }}
79
+ emptyState={
80
+ <div className="text-center py-12">
81
+ <p className="text-muted-foreground">
82
+ {search ? t('listing.noneMatching') : t('listing.noneAvailable')}
83
+ </p>
84
+ </div>
85
+ }
86
+ />
87
+ )}
88
+
89
+ {/* Load More */}
90
+ {hasMore && !isLoading && !error && (
91
+ <div className="mt-12 text-center">
92
+ <button
93
+ onClick={loadMore}
94
+ className="rounded-md bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
95
+ >
96
+ {t('listing.loadMore')}
97
+ </button>
98
+ </div>
99
+ )}
100
+ </div>
101
+ )
102
+ }
@@ -0,0 +1,25 @@
1
+ import type { MetadataRoute } from 'next'
2
+
3
+ const STORE_URL = process.env.NEXT_PUBLIC_APP_URL || ''
4
+
5
+ /**
6
+ * Storefront robots rules.
7
+ *
8
+ * Merchant stores default to MAXIMUM discoverability for every search + AI
9
+ * shopping crawler (Google, Bing, ChatGPT, Perplexity, Google AI Mode, etc.) —
10
+ * AI shopping visibility is the goal. Only private / transactional routes are
11
+ * disallowed.
12
+ */
13
+ export default function robots(): MetadataRoute.Robots {
14
+ const base = STORE_URL.replace(/\/$/, '')
15
+ return {
16
+ rules: [
17
+ {
18
+ userAgent: '*',
19
+ allow: '/',
20
+ disallow: ['/admin', '/api/', '/account', '/cart', '/checkout'],
21
+ },
22
+ ],
23
+ ...(base && { sitemap: `${base}/sitemap.xml` }),
24
+ }
25
+ }
@@ -0,0 +1,76 @@
1
+ import type { MetadataRoute } from 'next'
2
+ import { findProducts, findSettings } from '@rovela-ai/sdk/core/server'
3
+ import { resolveStoreLocaleConfig } from '@rovela-ai/sdk/core'
4
+
5
+ // ISR: the sitemap reads live data (products + locale config) but Next
6
+ // statically caches metadata routes at build — hourly revalidation keeps
7
+ // new products and language toggles from being frozen until the next deploy.
8
+ export const revalidate = 3600
9
+
10
+ const STORE_URL = process.env.NEXT_PUBLIC_APP_URL || ''
11
+
12
+ /**
13
+ * Storefront sitemap: static public pages + every active product detail page.
14
+ * Gives search + AI shopping crawlers a discovery path to the catalog.
15
+ * Transactional / private routes (cart, checkout, account, admin) are omitted
16
+ * by design — they mirror the robots.ts disallow list.
17
+ */
18
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
19
+ const base = STORE_URL.replace(/\/$/, '')
20
+ if (!base) return []
21
+
22
+ let products: Awaited<ReturnType<typeof findProducts>> = []
23
+ try {
24
+ products = await findProducts({ status: 'active', limit: 5000 })
25
+ } catch {
26
+ products = []
27
+ }
28
+
29
+ // hreflang via sitemap (i18n core): when the store serves more than one
30
+ // locale, every URL declares its alternates so crawlers index /fr pages as
31
+ // translations, not duplicates. Sitemap-level hreflang is fully supported
32
+ // by Google and avoids fragile per-page head injection.
33
+ let localeAlternates: ((path: string) => MetadataRoute.Sitemap[number]['alternates']) | null = null
34
+ try {
35
+ const settings = await findSettings()
36
+ const { defaultLocale, enabledLocales } = resolveStoreLocaleConfig(settings)
37
+ if (enabledLocales.length > 1) {
38
+ localeAlternates = (path: string) => ({
39
+ languages: Object.fromEntries(
40
+ enabledLocales.map((l) => [
41
+ l,
42
+ l === defaultLocale ? `${base}${path}` : `${base}/${l}${path === '/' ? '' : path}`,
43
+ ])
44
+ ),
45
+ })
46
+ }
47
+ } catch {
48
+ // settings unreadable → single-locale sitemap (safe default)
49
+ }
50
+
51
+ const staticDefs: Array<{ path: string; changeFrequency: 'weekly' | 'daily' | 'monthly' | 'yearly'; priority: number }> = [
52
+ { path: '/', changeFrequency: 'weekly', priority: 1.0 },
53
+ { path: '/products', changeFrequency: 'daily', priority: 0.9 },
54
+ { path: '/faq', changeFrequency: 'monthly', priority: 0.5 },
55
+ { path: '/contact', changeFrequency: 'monthly', priority: 0.4 },
56
+ { path: '/privacy', changeFrequency: 'yearly', priority: 0.2 },
57
+ { path: '/terms', changeFrequency: 'yearly', priority: 0.2 },
58
+ ]
59
+
60
+ const staticPages: MetadataRoute.Sitemap = staticDefs.map((d) => ({
61
+ url: `${base}${d.path === '/' ? '/' : d.path}`,
62
+ changeFrequency: d.changeFrequency,
63
+ priority: d.priority,
64
+ ...(localeAlternates && { alternates: localeAlternates(d.path) }),
65
+ }))
66
+
67
+ const productPages: MetadataRoute.Sitemap = products.map((p) => ({
68
+ url: `${base}/products/${p.slug}`,
69
+ lastModified: p.updatedAt ?? undefined,
70
+ changeFrequency: 'weekly',
71
+ priority: 0.8,
72
+ ...(localeAlternates && { alternates: localeAlternates(`/products/${p.slug}`) }),
73
+ }))
74
+
75
+ return [...staticPages, ...productPages]
76
+ }
@@ -0,0 +1,313 @@
1
+ import type { Metadata } from 'next'
2
+ import { LocaleLink as Link } from '@rovela-ai/sdk/core'
3
+ import { getBlueprint, localizedAlternates, findSettings } from '@rovela-ai/sdk/core/server'
4
+
5
+ /**
6
+ * Terms of Service Page
7
+ *
8
+ * Static terms of service page with standard e-commerce sections.
9
+ * Store name is pulled from blueprint for personalization.
10
+ */
11
+
12
+ // =============================================================================
13
+ // Metadata
14
+ // =============================================================================
15
+
16
+ export async function generateMetadata(): Promise<Metadata> {
17
+ let storeName = 'Our Store'
18
+ try {
19
+ const blueprint = getBlueprint()
20
+ storeName = blueprint?.store?.name || process.env.STORE_NAME || storeName
21
+ } catch {
22
+ // Blueprint might not exist
23
+ }
24
+
25
+ return {
26
+ title: 'Terms of Service',
27
+ description: `Terms of Service for ${storeName}. Read our terms and conditions for using our website and services.`,
28
+ alternates: await localizedAlternates('/terms'),
29
+ }
30
+ }
31
+
32
+ // =============================================================================
33
+ // Page Component
34
+ // =============================================================================
35
+
36
+ export default async function TermsPage() {
37
+ // Get store name for display — LIVE settings first (store_settings is what
38
+ // the merchant edits in /admin; blueprint + env are build-time fallbacks
39
+ // that go stale after a rename). Fail-open on DB errors.
40
+ let storeName = 'Our Store'
41
+ let supportEmail = ''
42
+ const liveSettings = await findSettings().catch(() => null)
43
+
44
+ try {
45
+ const blueprint = getBlueprint()
46
+ storeName =
47
+ liveSettings?.storeName || blueprint?.store?.name || process.env.STORE_NAME || storeName
48
+ } catch {
49
+ storeName = liveSettings?.storeName || process.env.STORE_NAME || storeName
50
+ }
51
+
52
+ // Public contact email = the merchant's own address only. NEVER the platform
53
+ // transactional sender (RESEND_FROM_EMAIL): surfacing that put
54
+ // "contact@rovela.ai" on storefront legal pages and read as a template
55
+ // artifact. Falls back to support@<your-domain> derived from the live host;
56
+ // if only a *.rovela.app / *.vercel.app / localhost host is available it stays
57
+ // empty and the email line is hidden (the contact-page link still renders).
58
+ const storeHost = (process.env.NEXT_PUBLIC_APP_URL || '')
59
+ .replace(/^https?:\/\//, '')
60
+ .replace(/\/.*$/, '')
61
+ .trim()
62
+ const hasOwnDomain =
63
+ !!storeHost &&
64
+ !storeHost.endsWith('rovela.app') &&
65
+ !storeHost.endsWith('vercel.app') &&
66
+ !storeHost.startsWith('localhost')
67
+ // Live store email first — this is the address the merchant actually set
68
+ // (emails, contact page and JSON-LD already read it); SUPPORT_EMAIL env is
69
+ // the build-time fallback, support@<domain> the last resort.
70
+ supportEmail =
71
+ liveSettings?.storeEmail?.trim() ||
72
+ process.env.SUPPORT_EMAIL?.trim() ||
73
+ (hasOwnDomain ? `support@${storeHost}` : '')
74
+
75
+ return (
76
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
77
+ <div className="max-w-3xl mx-auto">
78
+ {/* Header */}
79
+ <div className="mb-12">
80
+ <h1 className="text-3xl font-bold font-heading">Terms of Service</h1>
81
+ </div>
82
+
83
+ {/* Content */}
84
+ <div className="prose prose-neutral dark:prose-invert max-w-none">
85
+ {/* Introduction */}
86
+ <section className="mb-10">
87
+ <p className="text-muted-foreground leading-relaxed">
88
+ Welcome to {storeName}. By accessing or using our website, you agree to be bound
89
+ by these Terms of Service. Please read them carefully before making any purchases
90
+ or using our services.
91
+ </p>
92
+ </section>
93
+
94
+ {/* Acceptance of Terms */}
95
+ <section className="mb-10">
96
+ <h2 className="text-xl font-semibold font-heading mb-4">
97
+ 1. Acceptance of Terms
98
+ </h2>
99
+ <p className="text-muted-foreground">
100
+ By accessing and using this website, you accept and agree to be bound by these
101
+ Terms of Service and our Privacy Policy. If you do not agree to these terms,
102
+ please do not use our website or services.
103
+ </p>
104
+ </section>
105
+
106
+ {/* Use of Website */}
107
+ <section className="mb-10">
108
+ <h2 className="text-xl font-semibold font-heading mb-4">
109
+ 2. Use of Website
110
+ </h2>
111
+ <p className="text-muted-foreground mb-4">
112
+ You agree to use this website only for lawful purposes and in accordance with
113
+ these Terms. You agree not to:
114
+ </p>
115
+ <ul className="list-disc pl-6 space-y-2 text-muted-foreground">
116
+ <li>Use the website in any way that violates applicable laws or regulations</li>
117
+ <li>Attempt to interfere with the proper functioning of the website</li>
118
+ <li>Use automated systems or software to extract data from the website</li>
119
+ <li>Impersonate any person or entity or misrepresent your affiliation</li>
120
+ <li>Transmit any viruses, malware, or other harmful code</li>
121
+ </ul>
122
+ </section>
123
+
124
+ {/* Account Registration */}
125
+ <section className="mb-10">
126
+ <h2 className="text-xl font-semibold font-heading mb-4">
127
+ 3. Account Registration
128
+ </h2>
129
+ <p className="text-muted-foreground">
130
+ If you create an account with us, you are responsible for maintaining the
131
+ confidentiality of your account credentials and for all activities that occur
132
+ under your account. You must provide accurate and complete information when
133
+ creating an account. You agree to notify us immediately of any unauthorized
134
+ use of your account.
135
+ </p>
136
+ </section>
137
+
138
+ {/* Products and Pricing */}
139
+ <section className="mb-10">
140
+ <h2 className="text-xl font-semibold font-heading mb-4">
141
+ 4. Products and Pricing
142
+ </h2>
143
+ <p className="text-muted-foreground mb-4">
144
+ We strive to provide accurate product descriptions and pricing. However:
145
+ </p>
146
+ <ul className="list-disc pl-6 space-y-2 text-muted-foreground">
147
+ <li>Product images are for illustration purposes and may vary slightly from actual products</li>
148
+ <li>We reserve the right to correct pricing errors</li>
149
+ <li>Prices are subject to change without notice</li>
150
+ <li>Availability of products is not guaranteed and may change</li>
151
+ </ul>
152
+ </section>
153
+
154
+ {/* Orders and Payment */}
155
+ <section className="mb-10">
156
+ <h2 className="text-xl font-semibold font-heading mb-4">
157
+ 5. Orders and Payment
158
+ </h2>
159
+ <p className="text-muted-foreground mb-4">
160
+ When you place an order:
161
+ </p>
162
+ <ul className="list-disc pl-6 space-y-2 text-muted-foreground">
163
+ <li>You are making an offer to purchase products at the listed price</li>
164
+ <li>We reserve the right to accept or decline any order</li>
165
+ <li>Payment must be made at the time of purchase</li>
166
+ <li>All payments are processed securely through our payment provider</li>
167
+ <li>You are responsible for any applicable taxes and shipping fees</li>
168
+ </ul>
169
+ </section>
170
+
171
+ {/* Shipping and Delivery */}
172
+ <section className="mb-10">
173
+ <h2 className="text-xl font-semibold font-heading mb-4">
174
+ 6. Shipping and Delivery
175
+ </h2>
176
+ <p className="text-muted-foreground">
177
+ Shipping times and costs are estimates and may vary based on your location and
178
+ the shipping method selected. We are not responsible for delays caused by
179
+ shipping carriers or customs processing. Risk of loss and title for products
180
+ pass to you upon delivery to the carrier.
181
+ </p>
182
+ </section>
183
+
184
+ {/* Returns and Refunds */}
185
+ <section className="mb-10">
186
+ <h2 className="text-xl font-semibold font-heading mb-4">
187
+ 7. Returns and Refunds
188
+ </h2>
189
+ <p className="text-muted-foreground">
190
+ We want you to be satisfied with your purchase. If you are not happy with your
191
+ order, please contact us within 30 days of delivery. Return eligibility depends
192
+ on the product condition and type. Refunds will be processed to the original
193
+ payment method within 5-10 business days after we receive the returned item.
194
+ </p>
195
+ </section>
196
+
197
+ {/* Intellectual Property */}
198
+ <section className="mb-10">
199
+ <h2 className="text-xl font-semibold font-heading mb-4">
200
+ 8. Intellectual Property
201
+ </h2>
202
+ <p className="text-muted-foreground">
203
+ All content on this website, including text, graphics, logos, images, and software,
204
+ is the property of {storeName} or its content suppliers and is protected by
205
+ intellectual property laws. You may not use, reproduce, or distribute any content
206
+ without our prior written permission.
207
+ </p>
208
+ </section>
209
+
210
+ {/* Limitation of Liability */}
211
+ <section className="mb-10">
212
+ <h2 className="text-xl font-semibold font-heading mb-4">
213
+ 9. Limitation of Liability
214
+ </h2>
215
+ <p className="text-muted-foreground">
216
+ To the fullest extent permitted by law, {storeName} shall not be liable for any
217
+ indirect, incidental, special, consequential, or punitive damages arising from
218
+ your use of the website or purchase of products. Our total liability shall not
219
+ exceed the amount you paid for the products in question.
220
+ </p>
221
+ </section>
222
+
223
+ {/* Disclaimer of Warranties */}
224
+ <section className="mb-10">
225
+ <h2 className="text-xl font-semibold font-heading mb-4">
226
+ 10. Disclaimer of Warranties
227
+ </h2>
228
+ <p className="text-muted-foreground">
229
+ This website and all products are provided &quot;as is&quot; without warranties of any kind,
230
+ either express or implied. We do not warrant that the website will be
231
+ uninterrupted, error-free, or free of viruses or other harmful components.
232
+ </p>
233
+ </section>
234
+
235
+ {/* Indemnification */}
236
+ <section className="mb-10">
237
+ <h2 className="text-xl font-semibold font-heading mb-4">
238
+ 11. Indemnification
239
+ </h2>
240
+ <p className="text-muted-foreground">
241
+ You agree to indemnify and hold harmless {storeName} and its affiliates, officers,
242
+ directors, employees, and agents from any claims, damages, losses, or expenses
243
+ arising from your use of the website or violation of these Terms.
244
+ </p>
245
+ </section>
246
+
247
+ {/* Governing Law */}
248
+ <section className="mb-10">
249
+ <h2 className="text-xl font-semibold font-heading mb-4">
250
+ 12. Governing Law
251
+ </h2>
252
+ <p className="text-muted-foreground">
253
+ These Terms of Service shall be governed by and construed in accordance with
254
+ applicable laws, without regard to conflict of law principles. Any disputes
255
+ arising from these terms shall be resolved through binding arbitration or in
256
+ the courts of competent jurisdiction.
257
+ </p>
258
+ </section>
259
+
260
+ {/* Changes to Terms */}
261
+ <section className="mb-10">
262
+ <h2 className="text-xl font-semibold font-heading mb-4">
263
+ 13. Changes to Terms
264
+ </h2>
265
+ <p className="text-muted-foreground">
266
+ We reserve the right to modify these Terms of Service at any time. Changes will
267
+ be effective immediately upon posting on this page. Your continued use of the
268
+ website after any changes constitutes acceptance of the new terms.
269
+ </p>
270
+ </section>
271
+
272
+ {/* Contact Information */}
273
+ <section className="mb-10">
274
+ <h2 className="text-xl font-semibold font-heading mb-4">
275
+ 14. Contact Information
276
+ </h2>
277
+ <p className="text-muted-foreground">
278
+ If you have any questions about these Terms of Service, please
279
+ {supportEmail ? (
280
+ <>
281
+ {' '}contact us at{' '}
282
+ <a
283
+ href={`mailto:${supportEmail}`}
284
+ className="text-primary hover:underline"
285
+ >
286
+ {supportEmail}
287
+ </a>{' '}
288
+ or visit our{' '}
289
+ </>
290
+ ) : (
291
+ <>{' '}reach us through our{' '}</>
292
+ )}
293
+ <Link href="/contact" className="text-primary hover:underline">
294
+ contact page
295
+ </Link>
296
+ .
297
+ </p>
298
+ </section>
299
+ </div>
300
+
301
+ {/* Back to Home */}
302
+ <div className="mt-12 pt-8 border-t">
303
+ <Link
304
+ href="/"
305
+ className="text-sm text-primary hover:underline"
306
+ >
307
+ &larr; Back to Home
308
+ </Link>
309
+ </div>
310
+ </div>
311
+ </div>
312
+ )
313
+ }
@@ -0,0 +1,61 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Path-sync bridge — posts the storefront's current pathname (+ search)
5
+ * to its parent every time it changes. Powers the Rovela editor's path
6
+ * bar so it stays in lock-step with whatever URL the iframe is showing,
7
+ * including:
8
+ *
9
+ * - Direct link clicks inside the storefront (header / footer / cards)
10
+ * - Browser back / forward (popstate fires usePathname re-renders)
11
+ * - Dynamic-route renders (/products/[slug] etc.)
12
+ * - Programmatic router.push from any component
13
+ *
14
+ * Cross-origin safe:
15
+ * - postMessage with '*' target — the payload is a public pathname,
16
+ * no sensitive data
17
+ * - Parent validates `event.origin` against its known iframe origin
18
+ * before reading (anti-spoofing defense in depth)
19
+ *
20
+ * Renders nothing — pure side-effect component. Mount it once in the
21
+ * customer + admin branches of LayoutContent so coverage matches the
22
+ * actual route tree.
23
+ *
24
+ * No-op outside iframe context: when `window.parent === window` the
25
+ * component returns immediately. Safe to ship in production builds
26
+ * served on a domain (no postMessage spam to the same window).
27
+ */
28
+
29
+ import { usePathname } from 'next/navigation'
30
+ import { useEffect } from 'react'
31
+
32
+ /*
33
+ * IMPORTANT — DO NOT add `useSearchParams()` to this component.
34
+ *
35
+ * `useSearchParams` opts every page that contains it out of static
36
+ * prerendering unless wrapped in a Suspense boundary. Because this
37
+ * component is mounted in LayoutContent (which wraps EVERY route),
38
+ * adding it would force a Suspense wrap at every mount site AND
39
+ * dynamic-render every prerenderable page (/auth/*, /privacy, /terms,
40
+ * marketing pages, etc.) — Vercel hard-fails the build for any page
41
+ * that lacks the Suspense wrap.
42
+ *
43
+ * `usePathname` carries no such restriction. Reading `window.location.search`
44
+ * synchronously inside the effect gives us the current query string
45
+ * without the reactivity (which we don't need — we only sync on
46
+ * pathname change, which is the dominant navigation event).
47
+ *
48
+ * Trade-off: pure ?query-only changes without pathname change won't
49
+ * sync. Acceptable v1.
50
+ */
51
+ export function PathSyncBridge() {
52
+ const pathname = usePathname()
53
+ useEffect(() => {
54
+ if (typeof window === 'undefined') return
55
+ if (window.parent === window) return
56
+ const search = window.location.search
57
+ const path = pathname + (search || '')
58
+ window.parent.postMessage({ type: 'rovela:navigation', path }, '*')
59
+ }, [pathname])
60
+ return null
61
+ }
@@ -0,0 +1,37 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * App Providers
5
+ *
6
+ * Client component wrapper for all context providers.
7
+ * Required because SessionProvider and StoreSettingsProvider are client components.
8
+ *
9
+ * Provider hierarchy:
10
+ * - SessionProvider: NextAuth authentication state
11
+ * - StoreSettingsProvider: Dynamic store settings from database
12
+ */
13
+
14
+ import { SessionProvider } from 'next-auth/react'
15
+ import { StoreSettingsProvider } from '@rovela-ai/sdk/core'
16
+ import { storeConfig } from '@/lib/store-config'
17
+
18
+ interface ProvidersProps {
19
+ children: React.ReactNode
20
+ }
21
+
22
+ export function Providers({ children }: ProvidersProps) {
23
+ return (
24
+ <SessionProvider>
25
+ <StoreSettingsProvider
26
+ fallback={{
27
+ storeName: storeConfig.store.name,
28
+ currency: storeConfig.store.currency,
29
+ locale: storeConfig.store.locale,
30
+ supportEmail: storeConfig.store.supportEmail,
31
+ }}
32
+ >
33
+ {children}
34
+ </StoreSettingsProvider>
35
+ </SessionProvider>
36
+ )
37
+ }