@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,153 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Order History Page
5
+ *
6
+ * Shows CUSTOMER's past orders. Admin identity cannot reach this page:
7
+ * `useAuth()` role-filters the session (returns null for admin JWTs) and
8
+ * `AuthGuard` redirects anything that's not a signed-in customer to
9
+ * `/auth/signin`.
10
+ */
11
+
12
+ import { useState, useEffect } from 'react'
13
+ import { LocaleLink as Link, useStoreFormatting } from '@rovela-ai/sdk/core'
14
+ import { ChevronLeft, Package } from 'lucide-react'
15
+ import { AuthGuard } from '@rovela-ai/sdk/auth'
16
+ import { useStoreSettings } from '@rovela-ai/sdk/core'
17
+ import { useT, translateDynamic } from '@rovela-ai/sdk/core'
18
+
19
+ interface Order {
20
+ id: string
21
+ status: string
22
+ total: number
23
+ createdAt: string
24
+ items?: Array<{
25
+ name: string
26
+ quantity: number
27
+ price: number
28
+ }>
29
+ }
30
+
31
+ function OrdersContent() {
32
+ const t = useT()
33
+ // Locale-aware price and date shapes (active i18n locale + store currency).
34
+ const { formatPrice, formatDate } = useStoreFormatting()
35
+ const [orders, setOrders] = useState<Order[]>([])
36
+ const [isLoading, setIsLoading] = useState(true)
37
+
38
+ useEffect(() => {
39
+ async function fetchOrders() {
40
+ try {
41
+ const res = await fetch('/api/account/orders')
42
+ if (res.ok) {
43
+ const data = await res.json()
44
+ setOrders(data.orders || [])
45
+ }
46
+ } catch (error) {
47
+ console.error('Failed to fetch orders:', error)
48
+ } finally {
49
+ setIsLoading(false)
50
+ }
51
+ }
52
+ fetchOrders()
53
+ }, [])
54
+
55
+ if (isLoading) {
56
+ return (
57
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
58
+ <div className="animate-pulse space-y-4">
59
+ <div className="h-8 w-48 bg-muted rounded" />
60
+ <div className="h-24 bg-muted rounded" />
61
+ <div className="h-24 bg-muted rounded" />
62
+ </div>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ return (
68
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
69
+ <nav className="mb-8">
70
+ <Link
71
+ href="/account"
72
+ className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors"
73
+ >
74
+ <ChevronLeft className="h-4 w-4 mr-1" />
75
+ {t('account.backToAccount')}
76
+ </Link>
77
+ </nav>
78
+
79
+ <h1 className="text-3xl font-bold font-heading mb-8">{t('account.orderHistory')}</h1>
80
+
81
+ {orders.length === 0 ? (
82
+ <div className="text-center py-12">
83
+ <Package className="h-16 w-16 mx-auto text-muted-foreground" />
84
+ <h2 className="mt-6 text-xl font-semibold">{t('account.noOrders')}</h2>
85
+ <p className="mt-2 text-muted-foreground">
86
+ {t('account.noOrdersHint')}
87
+ </p>
88
+ <Link
89
+ href="/products"
90
+ 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"
91
+ >
92
+ {t('account.startShopping')}
93
+ </Link>
94
+ </div>
95
+ ) : (
96
+ <div className="space-y-4">
97
+ {orders.map((order) => (
98
+ <div
99
+ key={order.id}
100
+ className="rounded-lg border bg-card p-6"
101
+ >
102
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
103
+ <div>
104
+ <p className="text-sm text-muted-foreground">
105
+ {t('account.orderRefNo', { ref: order.id.slice(0, 8).toUpperCase() })}
106
+ </p>
107
+ <p className="font-semibold mt-1">
108
+ {formatPrice(order.total, (order as { currency?: string | null }).currency)}
109
+ </p>
110
+ <p className="text-sm text-muted-foreground mt-1">
111
+ {formatDate(new Date(order.createdAt))}
112
+ </p>
113
+ </div>
114
+ <div className="flex items-center gap-4">
115
+ <span className={`
116
+ inline-flex items-center rounded-full px-3 py-1 text-xs font-medium
117
+ ${order.status === 'delivered' ? 'bg-green-100 text-green-800' : ''}
118
+ ${order.status === 'shipped' ? 'bg-blue-100 text-blue-800' : ''}
119
+ ${order.status === 'paid' ? 'bg-yellow-100 text-yellow-800' : ''}
120
+ ${order.status === 'pending' ? 'bg-gray-100 text-gray-800' : ''}
121
+ ${order.status === 'cancelled' ? 'bg-red-100 text-red-800' : ''}
122
+ `}>
123
+ {(() => { const l = translateDynamic(t, 'status', order.status); return l === order.status ? order.status.charAt(0).toUpperCase() + order.status.slice(1) : l })()}
124
+ </span>
125
+ <Link
126
+ href={`/account/orders/${order.id}`}
127
+ className="text-sm text-primary hover:underline"
128
+ >
129
+ {t('account.viewDetails')}
130
+ </Link>
131
+ </div>
132
+ </div>
133
+ <div className="mt-4 pt-4 border-t">
134
+ <p className="text-sm text-muted-foreground">
135
+ {(order.items ?? []).length} item{(order.items ?? []).length !== 1 ? 's' : ''}:{' '}
136
+ {(order.items ?? []).map(item => item.name).join(', ')}
137
+ </p>
138
+ </div>
139
+ </div>
140
+ ))}
141
+ </div>
142
+ )}
143
+ </div>
144
+ )
145
+ }
146
+
147
+ export default function OrdersPage() {
148
+ return (
149
+ <AuthGuard redirectTo="/auth/signin">
150
+ <OrdersContent />
151
+ </AuthGuard>
152
+ )
153
+ }
@@ -0,0 +1,184 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Account Dashboard (overview hub)
5
+ *
6
+ * Protected page showing a CUSTOMER account overview: a greeting, the most
7
+ * recent order with its status, and quick actions. Admin identity cannot reach
8
+ * this page — `useAuth()` role-filters the session (returns null for admin JWTs)
9
+ * and `AuthGuard` redirects non-customers to `/auth/signin`.
10
+ */
11
+
12
+ import { useState, useEffect } from 'react'
13
+ import { LocaleLink as Link, useStoreFormatting } from '@rovela-ai/sdk/core'
14
+ import { Package, User, MapPin, LogOut, ChevronRight } from 'lucide-react'
15
+ import { AuthGuard, useAuth } from '@rovela-ai/sdk/auth'
16
+ import { useStoreSettings } from '@rovela-ai/sdk/core'
17
+ import { useT, translateDynamic, type Translator } from '@rovela-ai/sdk/core'
18
+
19
+ interface RecentOrder {
20
+ id: string
21
+ status: string
22
+ total: number
23
+ createdAt: string
24
+ }
25
+
26
+ function greeting(t: Translator): string {
27
+ const h = new Date().getHours()
28
+ if (h < 12) return t('account.goodMorning')
29
+ if (h < 18) return t('account.goodAfternoon')
30
+ return t('account.goodEvening')
31
+ }
32
+
33
+ const statusStyles: Record<string, string> = {
34
+ pending: 'bg-amber-100 text-amber-700',
35
+ paid: 'bg-blue-100 text-blue-700',
36
+ shipped: 'bg-indigo-100 text-indigo-700',
37
+ delivered: 'bg-green-100 text-green-700',
38
+ cancelled: 'bg-red-100 text-red-700',
39
+ refunded: 'bg-muted text-muted-foreground',
40
+ return_requested: 'bg-amber-100 text-amber-700',
41
+ }
42
+
43
+ function statusLabel(t: Translator, s: string): string {
44
+ const label = translateDynamic(t, 'status', s)
45
+ return label === s ? s.charAt(0).toUpperCase() + s.slice(1) : label
46
+ }
47
+
48
+ function AccountContent() {
49
+ const t = useT()
50
+ const { customer, signOut } = useAuth()
51
+ // Locale-aware price and date shapes (active i18n locale + store currency).
52
+ const { formatPrice, formatDate } = useStoreFormatting()
53
+ const [latestOrder, setLatestOrder] = useState<RecentOrder | null>(null)
54
+ const [loadingOrder, setLoadingOrder] = useState(true)
55
+
56
+ // Pattern C — populate state from async-loaded data.
57
+ useEffect(() => {
58
+ let active = true
59
+ async function load() {
60
+ try {
61
+ const res = await fetch('/api/account/orders')
62
+ if (res.ok) {
63
+ const data = await res.json()
64
+ const orders: RecentOrder[] = data.orders ?? data ?? []
65
+ const newest = orders.reduce<RecentOrder | null>((acc, o) => {
66
+ if (!acc) return o
67
+ return new Date(o.createdAt).getTime() > new Date(acc.createdAt).getTime() ? o : acc
68
+ }, null)
69
+ if (active) setLatestOrder(newest)
70
+ }
71
+ } catch {
72
+ // non-fatal — the hub still renders without the latest-order card
73
+ } finally {
74
+ if (active) setLoadingOrder(false)
75
+ }
76
+ }
77
+ load()
78
+ return () => {
79
+ active = false
80
+ }
81
+ }, [])
82
+
83
+ const firstName = customer?.name?.split(' ')[0] || null
84
+
85
+ return (
86
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-8">
87
+ {/* Greeting */}
88
+ <div className="mb-8">
89
+ <h1 className="text-3xl font-bold font-heading">
90
+ {greeting(t)}
91
+ {firstName ? `, ${firstName}` : ''}
92
+ </h1>
93
+ <p className="mt-1 text-muted-foreground">{customer?.email}</p>
94
+ </div>
95
+
96
+ {/* Latest order */}
97
+ {!loadingOrder && latestOrder && (
98
+ <Link
99
+ href={`/account/orders/${latestOrder.id}`}
100
+ className="mb-8 flex items-center justify-between gap-4 rounded-lg border bg-card p-5 hover:border-primary transition-colors"
101
+ >
102
+ <div className="flex items-center gap-4">
103
+ <div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
104
+ <Package className="h-6 w-6 text-primary" />
105
+ </div>
106
+ <div>
107
+ <div className="flex items-center gap-2">
108
+ <p className="font-semibold">
109
+ {t('account.orderRef', { ref: latestOrder.id.slice(0, 8).toUpperCase() })}
110
+ </p>
111
+ <span
112
+ className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${
113
+ statusStyles[latestOrder.status] || 'bg-muted text-muted-foreground'
114
+ }`}
115
+ >
116
+ {statusLabel(t, latestOrder.status)}
117
+ </span>
118
+ </div>
119
+ <p className="text-sm text-muted-foreground">
120
+ {formatDate(new Date(latestOrder.createdAt))} · {formatPrice(latestOrder.total)}
121
+ </p>
122
+ </div>
123
+ </div>
124
+ <ChevronRight className="h-5 w-5 text-muted-foreground" />
125
+ </Link>
126
+ )}
127
+
128
+ {/* Quick actions */}
129
+ <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
130
+ <Link
131
+ href="/account/orders"
132
+ className="rounded-lg border bg-card p-6 hover:border-primary transition-colors"
133
+ >
134
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
135
+ <Package className="h-6 w-6 text-primary" />
136
+ </div>
137
+ <h2 className="font-semibold">{t('account.orders')}</h2>
138
+ <p className="text-sm text-muted-foreground">{t('account.viewOrderHistory')}</p>
139
+ </Link>
140
+
141
+ <Link
142
+ href="/account/addresses"
143
+ className="rounded-lg border bg-card p-6 hover:border-primary transition-colors"
144
+ >
145
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
146
+ <MapPin className="h-6 w-6 text-primary" />
147
+ </div>
148
+ <h2 className="font-semibold">{t('account.addresses')}</h2>
149
+ <p className="text-sm text-muted-foreground">{t('account.manageAddresses')}</p>
150
+ </Link>
151
+
152
+ <Link
153
+ href="/account/profile"
154
+ className="rounded-lg border bg-card p-6 hover:border-primary transition-colors"
155
+ >
156
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
157
+ <User className="h-6 w-6 text-primary" />
158
+ </div>
159
+ <h2 className="font-semibold">{t('account.profile')}</h2>
160
+ <p className="text-sm text-muted-foreground">{t('account.profileDesc')}</p>
161
+ </Link>
162
+
163
+ <button
164
+ onClick={() => signOut()}
165
+ className="rounded-lg border bg-card p-6 text-left hover:border-destructive transition-colors"
166
+ >
167
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-destructive/10">
168
+ <LogOut className="h-6 w-6 text-destructive" />
169
+ </div>
170
+ <h2 className="font-semibold">{t('account.signOut')}</h2>
171
+ <p className="text-sm text-muted-foreground">{t('account.signOutDesc')}</p>
172
+ </button>
173
+ </div>
174
+ </div>
175
+ )
176
+ }
177
+
178
+ export default function AccountPage() {
179
+ return (
180
+ <AuthGuard redirectTo="/auth/signin">
181
+ <AccountContent />
182
+ </AuthGuard>
183
+ )
184
+ }