@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,7 @@
1
+ /**
2
+ * Media API Route
3
+ *
4
+ * DELETE /api/media - Delete a file from storage
5
+ */
6
+
7
+ export { DELETE } from '@rovela-ai/sdk/media/api'
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Single Product API Route
3
+ *
4
+ * GET /api/products/[slug] - Get product by slug with variants
5
+ * Wires directly to SDK handler.
6
+ */
7
+
8
+ import { getProduct } from '@rovela-ai/sdk/products'
9
+
10
+ export const GET = getProduct
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Products API Route
3
+ *
4
+ * GET /api/products - List products with filtering, sorting, pagination
5
+ * Wires directly to SDK handler.
6
+ */
7
+
8
+ import { getProducts } from '@rovela-ai/sdk/products'
9
+
10
+ export const GET = getProducts
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Public Store Settings API
3
+ *
4
+ * Returns public (non-sensitive) store settings.
5
+ * Used by StoreSettingsProvider to get dynamic store configuration.
6
+ *
7
+ * This endpoint is PUBLIC - no authentication required.
8
+ */
9
+
10
+ import { getPublicSettings } from '@rovela-ai/sdk/core/api'
11
+
12
+ export { getPublicSettings as GET }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Stripe Webhook Route
3
+ *
4
+ * POST /api/webhooks/stripe - Handle Stripe webhook events
5
+ *
6
+ * The SDK's handleStripeWebhook() already:
7
+ * - Verifies webhook signature
8
+ * - Creates orders on checkout.session.completed
9
+ * - Sends order confirmation emails
10
+ * - Handles refunds
11
+ *
12
+ * §46 wrapper-and-extend: when the SDK returns analytics context from a
13
+ * successful checkout completion, fire a server-side `purchase` event tied
14
+ * to the same visitor session that browsed the storefront. The partial
15
+ * unique index on (order_id) WHERE event='purchase' absorbs Stripe's
16
+ * at-least-once delivery — duplicate webhooks ON CONFLICT DO NOTHING.
17
+ */
18
+
19
+ import { handleStripeWebhook } from '@rovela-ai/sdk/checkout/server'
20
+ import { recordEvent } from '@rovela-ai/sdk/analytics/server'
21
+
22
+ export async function POST(request: Request) {
23
+ const payload = await request.text()
24
+ const signature = request.headers.get('stripe-signature')
25
+
26
+ if (!signature) {
27
+ return Response.json(
28
+ { error: 'Missing stripe-signature header' },
29
+ { status: 400 }
30
+ )
31
+ }
32
+
33
+ const result = await handleStripeWebhook(payload, signature)
34
+
35
+ // Fire server-side `purchase` analytics event when the order was created
36
+ // AND the storefront passed visitor identity through Stripe metadata
37
+ // (CheckoutFlow does this automatically via useCheckout). Fire-and-forget;
38
+ // recordEvent swallows errors so analytics CAN'T break webhook delivery.
39
+ if (
40
+ result?.event === 'checkout.session.completed' &&
41
+ result.orderId &&
42
+ result.analytics?.visitorId &&
43
+ result.analytics?.sessionId &&
44
+ typeof result.analytics.valueCents === 'number' &&
45
+ result.analytics.currency
46
+ ) {
47
+ void recordEvent({
48
+ event: {
49
+ event: 'purchase',
50
+ visitor_id: result.analytics.visitorId,
51
+ session_id: result.analytics.sessionId,
52
+ order_id: result.orderId,
53
+ value_cents: result.analytics.valueCents,
54
+ currency: result.analytics.currency,
55
+ path: '/checkout/success',
56
+ },
57
+ device: null,
58
+ country: null,
59
+ })
60
+ }
61
+
62
+ return Response.json(result)
63
+ }
@@ -0,0 +1,38 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Forgot Password Page
5
+ *
6
+ * Request password reset email.
7
+ */
8
+
9
+ import { LocaleLink as Link } from '@rovela-ai/sdk/core'
10
+ import { ForgotPasswordForm } from '@rovela-ai/sdk/auth'
11
+ import { useT } from '@rovela-ai/sdk/core'
12
+
13
+ export default function ForgotPasswordPage() {
14
+ const t = useT()
15
+ return (
16
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
17
+ <div className="max-w-md mx-auto">
18
+ <div className="text-center mb-8">
19
+ <h1 className="text-2xl font-bold font-heading">{t('authPage.resetTitle')}</h1>
20
+ <p className="mt-2 text-muted-foreground">
21
+ {t('authPage.resetSubtitle')}
22
+ </p>
23
+ </div>
24
+
25
+ <div className="rounded-lg border bg-card p-6">
26
+ <ForgotPasswordForm />
27
+ </div>
28
+
29
+ <p className="mt-6 text-center text-sm text-muted-foreground">
30
+ {t('authPage.rememberPassword')}{' '}
31
+ <Link href="/auth/signin" className="text-primary hover:underline">
32
+ {t('authPage.signIn')}
33
+ </Link>
34
+ </p>
35
+ </div>
36
+ </div>
37
+ )
38
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Auth area gate — when the merchant sets Customer accounts to Off
3
+ * (admin Settings → Checkout Settings), every auth page (sign in, sign up,
4
+ * forgot/reset password, verify email) redirects home BEFORE any HTML
5
+ * ships. Fail-open: a settings-read error never blocks a normal store.
6
+ */
7
+
8
+ import { redirect } from 'next/navigation'
9
+ import { findSettings } from '@rovela-ai/sdk/core/server'
10
+
11
+ export const dynamic = 'force-dynamic'
12
+
13
+ export default async function AuthLayout({ children }: { children: React.ReactNode }) {
14
+ const settings = await findSettings().catch(() => null)
15
+ if (settings?.customerAccounts === false) redirect('/')
16
+ return <>{children}</>
17
+ }
@@ -0,0 +1,81 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Reset Password Page
5
+ *
6
+ * Set new password using reset token from email.
7
+ */
8
+
9
+ import { Suspense } from 'react'
10
+ import { useSearchParams, useRouter } from 'next/navigation'
11
+ import { LocaleLink as Link, useLocalePath } from '@rovela-ai/sdk/core'
12
+ import { ResetPasswordForm } from '@rovela-ai/sdk/auth'
13
+ import { useT } from '@rovela-ai/sdk/core'
14
+
15
+ function ResetPasswordContent() {
16
+ const t = useT()
17
+ const localePath = useLocalePath()
18
+ const router = useRouter()
19
+ const searchParams = useSearchParams()
20
+ const token = searchParams.get('token')
21
+
22
+ if (!token) {
23
+ return (
24
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
25
+ <div className="max-w-md mx-auto text-center">
26
+ <h1 className="text-2xl font-bold font-heading text-destructive">
27
+ {t('authPage.invalidResetLink')}
28
+ </h1>
29
+ <p className="mt-4 text-muted-foreground">
30
+ {t('authPage.invalidResetLinkBody')}
31
+ </p>
32
+ <Link
33
+ href="/auth/forgot-password"
34
+ className="mt-8 inline-block rounded-md bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
35
+ >
36
+ {t('authPage.requestNewLink')}
37
+ </Link>
38
+ </div>
39
+ </div>
40
+ )
41
+ }
42
+
43
+ return (
44
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
45
+ <div className="max-w-md mx-auto">
46
+ <div className="text-center mb-8">
47
+ <h1 className="text-2xl font-bold font-heading">{t('authPage.setNewPassword')}</h1>
48
+ <p className="mt-2 text-muted-foreground">
49
+ Enter your new password below
50
+ </p>
51
+ </div>
52
+
53
+ <div className="rounded-lg border bg-card p-6">
54
+ <ResetPasswordForm
55
+ token={token}
56
+ onSuccess={() => router.push(localePath('/auth/signin?reset=success'))}
57
+ />
58
+ </div>
59
+ </div>
60
+ </div>
61
+ )
62
+ }
63
+
64
+ function LoadingFallback() {
65
+ return (
66
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
67
+ <div className="max-w-md mx-auto text-center">
68
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto" />
69
+ <p className="mt-4 text-muted-foreground">Loading...</p>
70
+ </div>
71
+ </div>
72
+ )
73
+ }
74
+
75
+ export default function ResetPasswordPage() {
76
+ return (
77
+ <Suspense fallback={<LoadingFallback />}>
78
+ <ResetPasswordContent />
79
+ </Suspense>
80
+ )
81
+ }
@@ -0,0 +1,62 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Sign In Page
5
+ *
6
+ * Customer sign in using SDK auth components.
7
+ */
8
+
9
+ import { Suspense } from 'react'
10
+ import { useRouter, useSearchParams } from 'next/navigation'
11
+ import { SignInForm } from '@rovela-ai/sdk/auth'
12
+ import { useStoreSettings } from '@rovela-ai/sdk/core'
13
+ import { useT } from '@rovela-ai/sdk/core'
14
+
15
+ function SignInContent() {
16
+ const t = useT()
17
+ const router = useRouter()
18
+ const searchParams = useSearchParams()
19
+ const callbackUrl = searchParams.get('callbackUrl') || '/account'
20
+ const { storeName } = useStoreSettings()
21
+
22
+ return (
23
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
24
+ <div className="max-w-md mx-auto">
25
+ <div className="text-center mb-8">
26
+ <h1 className="text-2xl font-bold font-heading">{t('authPage.welcomeBack')}</h1>
27
+ <p className="mt-2 text-muted-foreground">
28
+ {t('authPage.signInSubtitle', { storeName })}
29
+ </p>
30
+ </div>
31
+
32
+ <div className="rounded-lg border bg-card p-6">
33
+ <SignInForm
34
+ onSuccess={() => router.push(callbackUrl)}
35
+ forgotPasswordHref="/auth/forgot-password"
36
+ signUpHref="/auth/signup"
37
+ />
38
+ </div>
39
+ </div>
40
+ </div>
41
+ )
42
+ }
43
+
44
+ function LoadingFallback() {
45
+ const t = useT()
46
+ return (
47
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
48
+ <div className="max-w-md mx-auto text-center">
49
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto" />
50
+ <p className="mt-4 text-muted-foreground">{t('authPage.loading')}</p>
51
+ </div>
52
+ </div>
53
+ )
54
+ }
55
+
56
+ export default function SignInPage() {
57
+ return (
58
+ <Suspense fallback={<LoadingFallback />}>
59
+ <SignInContent />
60
+ </Suspense>
61
+ )
62
+ }
@@ -0,0 +1,38 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Sign Up Page
5
+ *
6
+ * Customer registration using SDK auth components.
7
+ */
8
+
9
+ import { useRouter } from 'next/navigation'
10
+ import { SignUpForm } from '@rovela-ai/sdk/auth'
11
+ import { useStoreSettings, useLocalePath } from '@rovela-ai/sdk/core'
12
+ import { useT } from '@rovela-ai/sdk/core'
13
+
14
+ export default function SignUpPage() {
15
+ const t = useT()
16
+ const localePath = useLocalePath()
17
+ const router = useRouter()
18
+ const { storeName } = useStoreSettings()
19
+
20
+ return (
21
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
22
+ <div className="max-w-md mx-auto">
23
+ <div className="text-center mb-8">
24
+ <h1 className="text-2xl font-bold font-heading">{t('authPage.createAccount')}</h1>
25
+ <p className="mt-2 text-muted-foreground">
26
+ {t('authPage.signUpSubtitle', { storeName })}
27
+ </p>
28
+ </div>
29
+
30
+ <div className="rounded-lg border bg-card p-6">
31
+ <SignUpForm
32
+ onSuccess={() => router.push(localePath('/auth/verify-email'))}
33
+ />
34
+ </div>
35
+ </div>
36
+ </div>
37
+ )
38
+ }
@@ -0,0 +1,185 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Verify Email Page
5
+ *
6
+ * Shows verification notice or handles token verification.
7
+ */
8
+
9
+ import { Suspense, useEffect, useState, useCallback } from 'react'
10
+ import { useSearchParams, useRouter } from 'next/navigation'
11
+ import { LocaleLink as Link, useLocalePath } from '@rovela-ai/sdk/core'
12
+ import { CheckCircle, XCircle, Mail, RefreshCw } from 'lucide-react'
13
+ import { useT } from '@rovela-ai/sdk/core'
14
+
15
+ function VerifyEmailContent() {
16
+ const t = useT()
17
+ const localePath = useLocalePath()
18
+ const router = useRouter()
19
+ const searchParams = useSearchParams()
20
+ const token = searchParams.get('token')
21
+ const email = searchParams.get('email') || ''
22
+
23
+ const [status, setStatus] = useState<'pending' | 'verifying' | 'success' | 'error'>('pending')
24
+ const [error, setError] = useState<string | null>(null)
25
+ const [isResending, setIsResending] = useState(false)
26
+ const [resent, setResent] = useState(false)
27
+
28
+ // If token present, verify it
29
+ useEffect(() => {
30
+ if (token) {
31
+ setStatus('verifying')
32
+ fetch(`/api/auth/verify-email?token=${token}`)
33
+ .then(res => res.json())
34
+ .then(data => {
35
+ if (data.success) {
36
+ setStatus('success')
37
+ // Redirect to signin after 3 seconds
38
+ setTimeout(() => router.push(localePath('/auth/signin')), 3000)
39
+ } else {
40
+ setStatus('error')
41
+ setError(data.error || t('authPage.verificationFailed'))
42
+ }
43
+ })
44
+ .catch(() => {
45
+ setStatus('error')
46
+ setError(t('authPage.verificationError'))
47
+ })
48
+ }
49
+ }, [token, router])
50
+
51
+ const handleResend = useCallback(async () => {
52
+ if (!email || isResending) return
53
+ setIsResending(true)
54
+ try {
55
+ const res = await fetch('/api/auth/resend-verification', {
56
+ method: 'POST',
57
+ headers: { 'Content-Type': 'application/json' },
58
+ body: JSON.stringify({ email }),
59
+ })
60
+ if (res.ok) {
61
+ setResent(true)
62
+ }
63
+ } finally {
64
+ setIsResending(false)
65
+ }
66
+ }, [email, isResending])
67
+
68
+ // Token verification flow
69
+ if (token) {
70
+ if (status === 'verifying') {
71
+ return (
72
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
73
+ <div className="max-w-md mx-auto text-center">
74
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto" />
75
+ <h1 className="mt-6 text-2xl font-bold font-heading">
76
+ {t('authPage.verifyingEmail')}
77
+ </h1>
78
+ </div>
79
+ </div>
80
+ )
81
+ }
82
+
83
+ if (status === 'success') {
84
+ return (
85
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
86
+ <div className="max-w-md mx-auto text-center">
87
+ <CheckCircle className="h-16 w-16 mx-auto text-green-500" />
88
+ <h1 className="mt-6 text-2xl font-bold font-heading">
89
+ {t('authPage.emailVerified')}
90
+ </h1>
91
+ <p className="mt-4 text-muted-foreground">
92
+ Your email has been verified. Redirecting to sign in...
93
+ </p>
94
+ <Link
95
+ href="/auth/signin"
96
+ className="mt-8 inline-block rounded-md bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
97
+ >
98
+ Sign In Now
99
+ </Link>
100
+ </div>
101
+ </div>
102
+ )
103
+ }
104
+
105
+ if (status === 'error') {
106
+ return (
107
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
108
+ <div className="max-w-md mx-auto text-center">
109
+ <XCircle className="h-16 w-16 mx-auto text-destructive" />
110
+ <h1 className="mt-6 text-2xl font-bold font-heading text-destructive">
111
+ Verification Failed
112
+ </h1>
113
+ <p className="mt-4 text-muted-foreground">
114
+ {error}
115
+ </p>
116
+ <Link
117
+ href="/auth/signin"
118
+ className="mt-8 inline-block rounded-md bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
119
+ >
120
+ Try Again
121
+ </Link>
122
+ </div>
123
+ </div>
124
+ )
125
+ }
126
+ }
127
+
128
+ // No token - show verification notice
129
+ return (
130
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
131
+ <div className="max-w-md mx-auto text-center">
132
+ <Mail className="h-16 w-16 mx-auto text-primary" />
133
+ <h1 className="mt-6 text-2xl font-bold font-heading">
134
+ Check your email
135
+ </h1>
136
+ <p className="mt-4 text-muted-foreground">
137
+ We&apos;ve sent you a verification link. Please check your inbox and click the link to verify your email address.
138
+ </p>
139
+
140
+ {email && !resent && (
141
+ <button
142
+ onClick={handleResend}
143
+ disabled={isResending}
144
+ className="mt-8 inline-flex items-center gap-2 text-sm text-primary hover:underline disabled:opacity-50"
145
+ >
146
+ <RefreshCw className={`h-4 w-4 ${isResending ? 'animate-spin' : ''}`} />
147
+ {isResending ? 'Sending...' : 'Resend verification email'}
148
+ </button>
149
+ )}
150
+
151
+ {resent && (
152
+ <p className="mt-8 text-sm text-green-600">
153
+ Verification email sent! Check your inbox.
154
+ </p>
155
+ )}
156
+
157
+ <p className="mt-6 text-sm text-muted-foreground">
158
+ Already verified?{' '}
159
+ <Link href="/auth/signin" className="text-primary hover:underline">
160
+ Sign in
161
+ </Link>
162
+ </p>
163
+ </div>
164
+ </div>
165
+ )
166
+ }
167
+
168
+ function LoadingFallback() {
169
+ return (
170
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
171
+ <div className="max-w-md mx-auto text-center">
172
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto" />
173
+ <p className="mt-4 text-muted-foreground">Loading...</p>
174
+ </div>
175
+ </div>
176
+ )
177
+ }
178
+
179
+ export default function VerifyEmailPage() {
180
+ return (
181
+ <Suspense fallback={<LoadingFallback />}>
182
+ <VerifyEmailContent />
183
+ </Suspense>
184
+ )
185
+ }
@@ -0,0 +1,137 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Cart Page
5
+ *
6
+ * Uses SDK components for cart display:
7
+ * - CartItem: Individual cart line item
8
+ * - CartSummary: Subtotal and totals
9
+ * - useCart: Cart state management
10
+ *
11
+ * Checkout behavior depends on store settings:
12
+ * - Shipping enabled: Link to /checkout for pre-checkout flow (Address → Shipping → Review)
13
+ * - Shipping disabled: Direct CheckoutButton to Stripe (no pre-checkout needed)
14
+ */
15
+
16
+ import { LocaleLink as Link } from '@rovela-ai/sdk/core'
17
+ import { ShoppingBag } from 'lucide-react'
18
+ import { useCart, CartItem, CartSummary, getCartItemsWithFallback } from '@rovela-ai/sdk/cart'
19
+ import { CheckoutButton } from '@rovela-ai/sdk/checkout'
20
+ import { useStoreSettings, useT, useStoreFormatting } from '@rovela-ai/sdk/core'
21
+
22
+ export default function CartPage() {
23
+ // Use fallback to handle module duplication issues where this route chunk
24
+ // may have a different store instance than the one CartProvider hydrated
25
+ const items = getCartItemsWithFallback()
26
+ const itemCount = items.reduce((sum, item) => sum + item.quantity, 0)
27
+ const t = useT()
28
+ const { clearCart } = useCart()
29
+ const { settings } = useStoreSettings()
30
+ // Locale-aware price shapes (active i18n locale + store currency).
31
+ const { formatPrice } = useStoreFormatting()
32
+
33
+ // Check if shipping is enabled (determines checkout flow)
34
+ const shippingEnabled = settings?.shippingEnabled ?? true
35
+
36
+ // Calculate subtotal for free shipping message
37
+ const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
38
+
39
+ // Empty cart
40
+ if (itemCount === 0) {
41
+ return (
42
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-16">
43
+ <div className="text-center max-w-md mx-auto">
44
+ <ShoppingBag className="h-16 w-16 mx-auto text-muted-foreground" />
45
+ <h1 className="mt-6 text-2xl font-bold font-heading">{t('cart.empty')}</h1>
46
+ <p className="mt-4 text-muted-foreground">
47
+ {t('cartPage.emptyHint')}
48
+ </p>
49
+ <Link
50
+ href="/products"
51
+ className="mt-8 inline-block rounded-md bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
52
+ >
53
+ {t('checkout.continueShopping')}
54
+ </Link>
55
+ </div>
56
+ </div>
57
+ )
58
+ }
59
+
60
+ return (
61
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
62
+ <div className="flex items-center justify-between mb-8">
63
+ <h1 className="text-3xl font-bold font-heading">{t('cart.title')}</h1>
64
+ <button
65
+ onClick={clearCart}
66
+ className="text-sm text-muted-foreground hover:text-destructive transition-colors"
67
+ >
68
+ {t('cartPage.clearCart')}
69
+ </button>
70
+ </div>
71
+
72
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-12">
73
+ {/* Cart Items */}
74
+ <div className="lg:col-span-2 space-y-4">
75
+ {items.map((item) => (
76
+ <CartItem
77
+ key={item.variantId ? `${item.id}-${item.variantId}` : item.id}
78
+ item={item}
79
+ />
80
+ ))}
81
+ </div>
82
+
83
+ {/* Cart Summary */}
84
+ <div className="lg:col-span-1">
85
+ <div className="sticky top-24 rounded-lg border bg-card p-6">
86
+ <h2 className="text-lg font-semibold mb-4">{t('checkout.orderSummary')}</h2>
87
+
88
+ <CartSummary />
89
+
90
+ <div className="mt-6">
91
+ {shippingEnabled ? (
92
+ <Link
93
+ href="/checkout"
94
+ className="w-full inline-flex items-center justify-center rounded-md bg-primary text-primary-foreground hover:bg-primary/90 h-11 px-8 text-base font-medium transition-colors"
95
+ >
96
+ {t('cartPage.proceedToCheckout')}
97
+ </Link>
98
+ ) : (
99
+ <CheckoutButton size="lg" className="w-full">
100
+ {t('cartPage.proceedToCheckout')}
101
+ </CheckoutButton>
102
+ )}
103
+ </div>
104
+
105
+ <p className="mt-4 text-center text-sm text-muted-foreground">
106
+ {shippingEnabled ? t('cart.shippingTaxesAtCheckout') : t('cartPage.taxesAtCheckout')}
107
+ </p>
108
+
109
+ {/* Free Shipping Message */}
110
+ {settings?.shippingEnabled && settings?.freeShippingThreshold > 0 && (
111
+ <div className="mt-4 text-center">
112
+ {subtotal < settings.freeShippingThreshold ? (
113
+ <p className="text-sm text-primary font-medium">
114
+ {t('cartPage.addMoreForFreeShipping', { amount: formatPrice(settings.freeShippingThreshold - subtotal) })}
115
+ </p>
116
+ ) : (
117
+ <p className="text-sm text-green-600 font-medium">
118
+ ✓ {t('cartPage.qualifyFreeShipping')}
119
+ </p>
120
+ )}
121
+ </div>
122
+ )}
123
+
124
+ <div className="mt-6 pt-6 border-t">
125
+ <Link
126
+ href="/products"
127
+ className="text-sm text-primary hover:underline"
128
+ >
129
+ &larr; {t('checkout.continueShopping')}
130
+ </Link>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ )
137
+ }