@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,223 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Categories Page
5
+ *
6
+ * Category management with tree display.
7
+ * Uses SDK admin useAdminCategories hook.
8
+ */
9
+
10
+ import { useState, useEffect } from 'react'
11
+ import Link from 'next/link'
12
+ import { Plus, Pencil, Trash2, FolderOpen, ChevronRight, ChevronDown } from 'lucide-react'
13
+ import { AdminPageHeader, useAdminCategories, DeleteConfirmDialog, type CategoryTreeItem, adminToast } from '@rovela-ai/sdk/admin'
14
+ import { AdminEmptyState } from '@rovela-ai/sdk/admin/components'
15
+
16
+ export default function AdminCategoriesPage() {
17
+ const { categoryTree, isLoading, error, deleteCategory } = useAdminCategories()
18
+ const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
19
+ const [deletingId, setDeletingId] = useState<string | null>(null)
20
+
21
+ // Auto-expand every parent on first load so newly-created subcategories are
22
+ // visible without the merchant hunting for the chevron. Only seeds once
23
+ // (prev.size === 0 guard) so manual collapse state survives subsequent
24
+ // tree updates.
25
+ // Pattern C — populate state from async-loaded data.
26
+ useEffect(() => {
27
+ if (categoryTree.length === 0) return
28
+ const ids = new Set<string>()
29
+ const walk = (nodes: CategoryTreeItem[]) => nodes.forEach(n => {
30
+ if (n.children.length > 0) {
31
+ ids.add(n.id)
32
+ walk(n.children)
33
+ }
34
+ })
35
+ walk(categoryTree)
36
+ // eslint-disable-next-line react-hooks/set-state-in-effect
37
+ setExpandedIds(prev => (prev.size === 0 ? ids : prev))
38
+ }, [categoryTree])
39
+
40
+ // Delete confirmation state
41
+ const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string } | null>(null)
42
+ const [deleteError, setDeleteError] = useState<string | null>(null)
43
+
44
+ const toggleExpand = (id: string) => {
45
+ setExpandedIds(prev => {
46
+ const next = new Set(prev)
47
+ if (next.has(id)) {
48
+ next.delete(id)
49
+ } else {
50
+ next.add(id)
51
+ }
52
+ return next
53
+ })
54
+ }
55
+
56
+ const handleDeleteClick = (id: string, name: string) => {
57
+ setDeleteTarget({ id, name })
58
+ setDeleteError(null)
59
+ }
60
+
61
+ const handleDeleteConfirm = async () => {
62
+ if (!deleteTarget) return
63
+
64
+ setDeletingId(deleteTarget.id)
65
+ const success = await deleteCategory(deleteTarget.id)
66
+ setDeletingId(null)
67
+
68
+ if (success) {
69
+ setDeleteTarget(null)
70
+ adminToast.success('Category deleted')
71
+ } else {
72
+ setDeleteError('Failed to delete category. It may have products assigned to it.')
73
+ }
74
+ }
75
+
76
+ const handleDeleteCancel = () => {
77
+ if (!deletingId) {
78
+ setDeleteTarget(null)
79
+ setDeleteError(null)
80
+ }
81
+ }
82
+
83
+ const renderCategory = (category: CategoryTreeItem, depth: number = 0) => {
84
+ const hasChildren = category.children.length > 0
85
+ const isExpanded = expandedIds.has(category.id)
86
+ const isDeleting = deletingId === category.id
87
+
88
+ return (
89
+ <div key={category.id}>
90
+ <div
91
+ className={`flex items-center gap-3 border-b border-border px-4 py-3 hover:bg-accent/50 transition-colors ${isDeleting ? 'opacity-50' : ''}`}
92
+ style={{ paddingLeft: `${16 + depth * 24}px` }}
93
+ >
94
+ {/* Expand/collapse toggle */}
95
+ <button
96
+ type="button"
97
+ onClick={() => toggleExpand(category.id)}
98
+ className={`p-1 rounded hover:bg-accent ${!hasChildren ? 'invisible' : ''}`}
99
+ disabled={!hasChildren}
100
+ >
101
+ {isExpanded ? (
102
+ <ChevronDown className="h-4 w-4 text-muted-foreground" />
103
+ ) : (
104
+ <ChevronRight className="h-4 w-4 text-muted-foreground" />
105
+ )}
106
+ </button>
107
+
108
+ {/* Category icon */}
109
+ <FolderOpen className="h-5 w-5 text-muted-foreground flex-shrink-0" />
110
+
111
+ {/* Category info */}
112
+ <div className="flex-1 min-w-0">
113
+ <div className="flex items-center gap-2">
114
+ <span className="font-medium text-foreground truncate">{category.name}</span>
115
+ {hasChildren && (
116
+ <span
117
+ className="text-xs text-muted-foreground tabular-nums"
118
+ aria-label={`${category.children.length} subcategor${category.children.length === 1 ? 'y' : 'ies'}`}
119
+ >
120
+ {category.children.length}
121
+ </span>
122
+ )}
123
+ </div>
124
+ <div className="text-sm text-muted-foreground truncate">/{category.slug}</div>
125
+ </div>
126
+
127
+ {/* Actions */}
128
+ <div className="flex items-center gap-2">
129
+ <Link
130
+ href={`/admin/categories/${category.id}`}
131
+ className="p-2 rounded-md hover:bg-accent text-muted-foreground hover:text-foreground transition-colors"
132
+ >
133
+ <Pencil className="h-4 w-4" />
134
+ </Link>
135
+ <button
136
+ type="button"
137
+ onClick={() => handleDeleteClick(category.id, category.name)}
138
+ disabled={isDeleting}
139
+ className="p-2 rounded-md hover:bg-destructive/10 text-muted-foreground hover:text-destructive transition-colors disabled:opacity-50"
140
+ >
141
+ <Trash2 className="h-4 w-4" />
142
+ </button>
143
+ </div>
144
+ </div>
145
+
146
+ {/* Children */}
147
+ {hasChildren && isExpanded && (
148
+ <div>
149
+ {category.children.map(child => renderCategory(child, depth + 1))}
150
+ </div>
151
+ )}
152
+ </div>
153
+ )
154
+ }
155
+
156
+ return (
157
+ <div className="space-y-6">
158
+ <AdminPageHeader
159
+ title="Categories"
160
+ subtitle="Organize your catalog into collections your customers browse."
161
+ actions={
162
+ <Link href="/admin/categories/new" className="admin-btn admin-btn-primary">
163
+ <Plus className="h-4 w-4" />
164
+ Add category
165
+ </Link>
166
+ }
167
+ />
168
+
169
+ {/* Error state */}
170
+ {error && (
171
+ <div className="rounded-md bg-destructive/10 px-4 py-3 text-sm text-destructive">
172
+ {error}
173
+ </div>
174
+ )}
175
+
176
+ {/* Loading state */}
177
+ {isLoading && (
178
+ <div className="flex items-center justify-center py-12">
179
+ <span className="admin-spinner admin-spinner-lg" />
180
+ </div>
181
+ )}
182
+
183
+ {/* Empty state */}
184
+ {!isLoading && !error && categoryTree.length === 0 && (
185
+ <div className="rounded-lg border border-border bg-card">
186
+ <AdminEmptyState
187
+ icon={<FolderOpen />}
188
+ title="No categories yet"
189
+ description="Get started by creating your first category."
190
+ action={
191
+ <Link
192
+ href="/admin/categories/new"
193
+ className="admin-btn admin-btn-primary"
194
+ >
195
+ <Plus className="h-4 w-4" />
196
+ Add category
197
+ </Link>
198
+ }
199
+ />
200
+ </div>
201
+ )}
202
+
203
+ {/* Category tree */}
204
+ {!isLoading && !error && categoryTree.length > 0 && (
205
+ <div className="rounded-lg border border-border bg-card overflow-hidden">
206
+ {categoryTree.map(category => renderCategory(category, 0))}
207
+ </div>
208
+ )}
209
+
210
+ {/* Delete Confirmation Dialog */}
211
+ <DeleteConfirmDialog
212
+ isOpen={!!deleteTarget}
213
+ title="Delete Category"
214
+ itemName={deleteTarget?.name || ''}
215
+ itemType="category"
216
+ isDeleting={!!deletingId}
217
+ error={deleteError}
218
+ onConfirm={handleDeleteConfirm}
219
+ onCancel={handleDeleteCancel}
220
+ />
221
+ </div>
222
+ )
223
+ }
@@ -0,0 +1,26 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Customer Detail Page
5
+ *
6
+ * View and manage a single customer using the SDK CustomerDetails
7
+ * component, which renders the page header itself (back + eyebrow +
8
+ * name + verification badge) — LEDGER page architecture.
9
+ */
10
+
11
+ import { useParams, useRouter } from 'next/navigation'
12
+ import { CustomerDetails } from '@rovela-ai/sdk/admin'
13
+
14
+ export default function CustomerDetailPage() {
15
+ const params = useParams()
16
+ const id = params.id as string
17
+ const router = useRouter()
18
+
19
+ return (
20
+ <CustomerDetails
21
+ customerId={id}
22
+ onBack={() => router.push('/admin/customers')}
23
+ onDelete={() => router.push('/admin/customers')}
24
+ />
25
+ )
26
+ }
@@ -0,0 +1,33 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Customers Page
5
+ *
6
+ * Customer management view using SDK CustomerTable component.
7
+ * Displays ALL registered customers, not just those who ordered.
8
+ */
9
+
10
+ import { useRouter } from 'next/navigation'
11
+ import { AdminPageHeader, CustomerTable } from '@rovela-ai/sdk/admin'
12
+
13
+ export default function AdminCustomersPage() {
14
+ const router = useRouter()
15
+
16
+ const handleCustomerClick = (customerId: string) => {
17
+ router.push(`/admin/customers/${customerId}`)
18
+ }
19
+
20
+ return (
21
+ <div className="space-y-6">
22
+ <AdminPageHeader
23
+ title="Customers"
24
+ subtitle="Everyone with an account or an order in your store."
25
+ />
26
+
27
+ <CustomerTable
28
+ pageSize={20}
29
+ onCustomerClick={handleCustomerClick}
30
+ />
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,16 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Discounts Page
5
+ *
6
+ * The NATIVE discounts engine — discount codes and automatic promotions
7
+ * created and applied inside the store's own checkout. The DiscountsManager
8
+ * renders the page header itself (LEDGER page architecture): this wrapper
9
+ * stays a bare mount so the title and CTAs exist exactly once.
10
+ */
11
+
12
+ import { DiscountsManager } from '@rovela-ai/sdk/admin'
13
+
14
+ export default function AdminDiscountsPage() {
15
+ return <DiscountsManager />
16
+ }
@@ -0,0 +1,16 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Emails Page
5
+ *
6
+ * Every transactional email the store sends, on one shared brand theme.
7
+ * The EmailsManager renders the page header itself (LEDGER page
8
+ * architecture): this wrapper stays a bare mount so the title and CTAs
9
+ * exist exactly once.
10
+ */
11
+
12
+ import { EmailsManager } from '@rovela-ai/sdk/admin'
13
+
14
+ export default function AdminEmailsPage() {
15
+ return <EmailsManager />
16
+ }
@@ -0,0 +1,16 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Files Page
5
+ *
6
+ * The store's media library — every image and video under the store's R2
7
+ * prefix. Same live storage the Rovela platform's Assets Library lists.
8
+ * The FilesManager renders the page header itself (LEDGER page
9
+ * architecture): this wrapper stays a bare mount.
10
+ */
11
+
12
+ import { FilesManager } from '@rovela-ai/sdk/admin'
13
+
14
+ export default function AdminFilesPage() {
15
+ return <FilesManager />
16
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Admin Layout
3
+ *
4
+ * Protected admin area with sidebar navigation.
5
+ *
6
+ * Server-side gate: the layout is an async Server Component
7
+ * that calls `getCurrentAdmin()` and 307-redirects unauthenticated users to
8
+ * `/admin/login` BEFORE any HTML reaches the client. Eliminates the flash
9
+ * of admin chrome that previously appeared during the ~200–400ms session
10
+ * fetch window for unauthenticated visitors.
11
+ *
12
+ * Layered defense:
13
+ * - Server Component layout (this file): the primary gate. Stops cold
14
+ * loads of `/admin/*` from rendering any chrome for unauth visitors.
15
+ * - AdminGuard (client component, kept inside): catches mid-session
16
+ * expirations after the page is already rendered (e.g., JWT TTL
17
+ * elapses while the user is idle on /admin/products).
18
+ * - requireAdmin on every API route: the actual security boundary.
19
+ *
20
+ * AdminLayout is a client component (uses `rovela-admin-theme` chrome and
21
+ * the AdminNav with hooks) so it stays client-rendered — but it now only
22
+ * renders for authenticated users.
23
+ */
24
+
25
+ import { redirect } from 'next/navigation'
26
+ import { AdminGuard, AdminLayout } from '@rovela-ai/sdk/admin'
27
+ import { getCurrentAdmin } from '@rovela-ai/sdk/admin/server'
28
+
29
+ export default async function AdminRootLayout({
30
+ children,
31
+ }: {
32
+ children: React.ReactNode
33
+ }) {
34
+ const session = await getCurrentAdmin()
35
+ if (!session) {
36
+ redirect('/admin/login')
37
+ }
38
+
39
+ return (
40
+ <AdminLayout>
41
+ <AdminGuard redirectTo="/admin/login">{children}</AdminGuard>
42
+ </AdminLayout>
43
+ )
44
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Admin Dashboard Loading State
3
+ *
4
+ * Shows skeleton UI during page transitions within the admin dashboard.
5
+ * Matches the dashboard layout structure for smooth visual continuity.
6
+ */
7
+
8
+ export default function AdminLoading() {
9
+ return (
10
+ <div className="space-y-8">
11
+ {/* Welcome Header Skeleton */}
12
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
13
+ <div>
14
+ <div className="admin-skeleton h-8 w-64 rounded" />
15
+ <div className="admin-skeleton h-4 w-40 mt-2 rounded" />
16
+ </div>
17
+ <div className="flex gap-2">
18
+ <div className="admin-skeleton h-10 w-28 rounded-lg" />
19
+ <div className="admin-skeleton h-10 w-28 rounded-lg" />
20
+ <div className="admin-skeleton h-10 w-10 rounded-lg" />
21
+ </div>
22
+ </div>
23
+
24
+ {/* Stats Cards Skeleton */}
25
+ <section>
26
+ <div className="admin-skeleton h-4 w-20 mb-4 rounded" />
27
+ <div className="admin-stats-grid">
28
+ {[...Array(4)].map((_, i) => (
29
+ <div key={i} className="admin-card p-6">
30
+ <div className="admin-skeleton h-4 w-24 rounded" />
31
+ <div className="admin-skeleton h-9 w-32 mt-2 rounded" />
32
+ <div className="admin-skeleton h-3 w-20 mt-4 rounded" />
33
+ </div>
34
+ ))}
35
+ </div>
36
+ </section>
37
+
38
+ {/* Charts Skeleton */}
39
+ <section className="grid grid-cols-1 gap-6 xl:grid-cols-3">
40
+ <div className="xl:col-span-2 admin-card p-6">
41
+ <div className="admin-skeleton h-5 w-32 mb-4 rounded" />
42
+ <div className="admin-skeleton h-64 rounded" />
43
+ </div>
44
+ <div className="admin-card p-6">
45
+ <div className="admin-skeleton h-5 w-28 mb-4 rounded" />
46
+ <div className="admin-skeleton h-64 rounded" />
47
+ </div>
48
+ </section>
49
+
50
+ {/* Activity Section Skeleton */}
51
+ <section>
52
+ <div className="admin-skeleton h-4 w-16 mb-4 rounded" />
53
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
54
+ <div className="admin-card p-6">
55
+ <div className="admin-skeleton h-5 w-28 mb-4 rounded" />
56
+ <div className="space-y-3">
57
+ {[...Array(5)].map((_, i) => (
58
+ <div key={i} className="flex items-center gap-3">
59
+ <div className="admin-skeleton h-10 w-10 rounded" />
60
+ <div className="flex-1">
61
+ <div className="admin-skeleton h-4 w-32 rounded" />
62
+ <div className="admin-skeleton h-3 w-24 mt-1 rounded" />
63
+ </div>
64
+ <div className="admin-skeleton h-6 w-16 rounded" />
65
+ </div>
66
+ ))}
67
+ </div>
68
+ </div>
69
+ <div className="admin-card p-6">
70
+ <div className="admin-skeleton h-5 w-24 mb-4 rounded" />
71
+ <div className="space-y-3">
72
+ {[...Array(5)].map((_, i) => (
73
+ <div key={i} className="flex items-center gap-3">
74
+ <div className="admin-skeleton h-10 w-10 rounded" />
75
+ <div className="flex-1">
76
+ <div className="admin-skeleton h-4 w-28 rounded" />
77
+ <div className="admin-skeleton h-3 w-20 mt-1 rounded" />
78
+ </div>
79
+ <div className="admin-skeleton h-6 w-12 rounded" />
80
+ </div>
81
+ ))}
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </section>
86
+ </div>
87
+ )
88
+ }
@@ -0,0 +1,16 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Locations Page
5
+ *
6
+ * Physical shops (a standalone entity, Shopify's Locations model). Pickup
7
+ * enabled locations appear at checkout as "Pick up in store". The
8
+ * LocationsManager renders the page header itself (LEDGER page
9
+ * architecture): this wrapper stays a bare mount.
10
+ */
11
+
12
+ import { LocationsManager } from '@rovela-ai/sdk/admin'
13
+
14
+ export default function AdminLocationsPage() {
15
+ return <LocationsManager />
16
+ }
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Order Detail Page
5
+ *
6
+ * View and manage individual order details.
7
+ * Uses SDK admin OrderDetails and RefundDialog components.
8
+ */
9
+
10
+ import { useState, useCallback } from 'react'
11
+ import { useParams, useRouter } from 'next/navigation'
12
+ import { OrderDetails, RefundDialog } from '@rovela-ai/sdk/admin'
13
+
14
+ export default function AdminOrderDetailPage() {
15
+ const params = useParams()
16
+ const id = params.id as string
17
+ const router = useRouter()
18
+
19
+ // Refund dialog state
20
+ const [showRefundDialog, setShowRefundDialog] = useState(false)
21
+ const [refundOrderData, setRefundOrderData] = useState<{
22
+ orderId: string
23
+ orderTotal: string | number
24
+ orderCurrency?: string | null
25
+ } | null>(null)
26
+
27
+ // Handle refund button click from OrderDetails
28
+ const handleRefund = useCallback(
29
+ (orderId: string, orderTotal: string | number, orderCurrency?: string | null) => {
30
+ setRefundOrderData({ orderId, orderTotal, orderCurrency })
31
+ setShowRefundDialog(true)
32
+ },
33
+ []
34
+ )
35
+
36
+ // Handle refund success - refresh the page to show updated status
37
+ const handleRefundSuccess = useCallback(() => {
38
+ // Force a refresh by navigating to the same page
39
+ router.refresh()
40
+ }, [router])
41
+
42
+ return (
43
+ <div className="space-y-6">
44
+ <OrderDetails
45
+ orderId={id}
46
+ onBack={() => router.push('/admin/orders')}
47
+ onRefund={handleRefund}
48
+ />
49
+
50
+ {/* Refund Dialog */}
51
+ {refundOrderData && (
52
+ <RefundDialog
53
+ orderId={refundOrderData.orderId}
54
+ orderTotal={refundOrderData.orderTotal}
55
+ currency={refundOrderData.orderCurrency}
56
+ isOpen={showRefundDialog}
57
+ onClose={() => {
58
+ setShowRefundDialog(false)
59
+ setRefundOrderData(null)
60
+ }}
61
+ onSuccess={handleRefundSuccess}
62
+ />
63
+ )}
64
+ </div>
65
+ )
66
+ }
@@ -0,0 +1,26 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Admin Orders Page
5
+ *
6
+ * Order management with table and filters.
7
+ * Uses SDK admin OrderTable component.
8
+ */
9
+
10
+ import { useRouter } from 'next/navigation'
11
+ import { AdminPageHeader, OrderTable } from '@rovela-ai/sdk/admin'
12
+
13
+ export default function AdminOrdersPage() {
14
+ const router = useRouter()
15
+
16
+ return (
17
+ <div className="space-y-6">
18
+ <AdminPageHeader
19
+ title="Orders"
20
+ subtitle="Track, fulfill and refund your customers' orders."
21
+ />
22
+
23
+ <OrderTable onOrderClick={(id) => router.push(`/admin/orders/${id}`)} />
24
+ </div>
25
+ )
26
+ }