@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.
- package/dist/admin/components/AdminPageHeader.d.ts.map +1 -1
- package/dist/admin/components/AdminPageHeader.js +9 -2
- package/dist/admin/components/AdminPageHeader.js.map +1 -1
- package/dist/admin/components/AdminUserMenu.d.ts.map +1 -1
- package/dist/admin/components/AdminUserMenu.js +2 -1
- package/dist/admin/components/AdminUserMenu.js.map +1 -1
- package/dist/admin/components/CustomerDetails.d.ts.map +1 -1
- package/dist/admin/components/CustomerDetails.js +2 -1
- package/dist/admin/components/CustomerDetails.js.map +1 -1
- package/dist/admin/components/EmailsManager.d.ts.map +1 -1
- package/dist/admin/components/EmailsManager.js +2 -1
- package/dist/admin/components/EmailsManager.js.map +1 -1
- package/dist/admin/components/LowStockAlert.d.ts.map +1 -1
- package/dist/admin/components/LowStockAlert.js +3 -2
- package/dist/admin/components/LowStockAlert.js.map +1 -1
- package/dist/admin/components/OrderDetails.d.ts.map +1 -1
- package/dist/admin/components/OrderDetails.js +2 -1
- package/dist/admin/components/OrderDetails.js.map +1 -1
- package/dist/admin/components/ProductTable.d.ts.map +1 -1
- package/dist/admin/components/ProductTable.js +4 -3
- package/dist/admin/components/ProductTable.js.map +1 -1
- package/dist/admin/components/RecentOrders.d.ts.map +1 -1
- package/dist/admin/components/RecentOrders.js +2 -1
- package/dist/admin/components/RecentOrders.js.map +1 -1
- package/dist/admin/components/SEOPreview.d.ts +2 -8
- package/dist/admin/components/SEOPreview.d.ts.map +1 -1
- package/dist/admin/components/SEOPreview.js +32 -5
- package/dist/admin/components/SEOPreview.js.map +1 -1
- package/dist/admin/components/SecondaryMetricsRow.d.ts.map +1 -1
- package/dist/admin/components/SecondaryMetricsRow.js +3 -2
- package/dist/admin/components/SecondaryMetricsRow.js.map +1 -1
- package/dist/admin/components/StatsCards.d.ts.map +1 -1
- package/dist/admin/components/StatsCards.js +2 -1
- package/dist/admin/components/StatsCards.js.map +1 -1
- package/docs/changelog/README.md +53 -0
- package/docs/changelog/epoch-01-chrome-consent.md +63 -0
- package/docs/changelog/epoch-02-platform-commerce.md +108 -0
- package/docs/changelog/epoch-03-i18n-files.md +72 -0
- package/docs/changelog/epoch-04-emails.md +65 -0
- package/docs/changelog/epoch-05-admin-experience.md +98 -0
- package/docs/changelog/epoch-06-mobile-integrity.md +92 -0
- package/docs/design/admin-ledger-visual-universe.html +1961 -0
- package/docs/design/admin-sidebar-and-band.html +542 -0
- package/package.json +3 -1
- package/templates/store-template/.claude/skills/add-section/SKILL.md +438 -0
- package/templates/store-template/.claude/skills/build-feature/SKILL.md +201 -0
- package/templates/store-template/.claude/skills/customize-theme/SKILL.md +167 -0
- package/templates/store-template/.claude/skills/debug-errors/SKILL.md +68 -0
- package/templates/store-template/.claude/skills/frontend-design/SKILL.md +176 -0
- package/templates/store-template/.claude/skills/generate-images/SKILL.md +115 -0
- package/templates/store-template/.claude/skills/manage-products/SKILL.md +187 -0
- package/templates/store-template/.claude/skills/override-sdk-component/SKILL.md +154 -0
- package/templates/store-template/.env.example +50 -0
- package/templates/store-template/.mcp.json +8 -0
- package/templates/store-template/.rovela/blueprint.example.json +190 -0
- package/templates/store-template/CLAUDE.md +14 -0
- package/templates/store-template/app/LayoutContent.tsx +106 -0
- package/templates/store-template/app/[...path]/page.tsx +79 -0
- package/templates/store-template/app/account/addresses/page.tsx +45 -0
- package/templates/store-template/app/account/layout.tsx +17 -0
- package/templates/store-template/app/account/orders/[id]/invoice/page.tsx +277 -0
- package/templates/store-template/app/account/orders/[id]/page.tsx +725 -0
- package/templates/store-template/app/account/orders/page.tsx +153 -0
- package/templates/store-template/app/account/page.tsx +184 -0
- package/templates/store-template/app/account/profile/page.tsx +434 -0
- package/templates/store-template/app/admin/(dashboard)/account/page.tsx +16 -0
- package/templates/store-template/app/admin/(dashboard)/analytics/events/page.tsx +14 -0
- package/templates/store-template/app/admin/(dashboard)/analytics/layout.tsx +48 -0
- package/templates/store-template/app/admin/(dashboard)/analytics/page.tsx +15 -0
- package/templates/store-template/app/admin/(dashboard)/analytics/visitors/page.tsx +14 -0
- package/templates/store-template/app/admin/(dashboard)/categories/[id]/page.tsx +38 -0
- package/templates/store-template/app/admin/(dashboard)/categories/new/page.tsx +35 -0
- package/templates/store-template/app/admin/(dashboard)/categories/page.tsx +223 -0
- package/templates/store-template/app/admin/(dashboard)/customers/[id]/page.tsx +26 -0
- package/templates/store-template/app/admin/(dashboard)/customers/page.tsx +33 -0
- package/templates/store-template/app/admin/(dashboard)/discounts/page.tsx +16 -0
- package/templates/store-template/app/admin/(dashboard)/emails/page.tsx +16 -0
- package/templates/store-template/app/admin/(dashboard)/files/page.tsx +16 -0
- package/templates/store-template/app/admin/(dashboard)/layout.tsx +44 -0
- package/templates/store-template/app/admin/(dashboard)/loading.tsx +88 -0
- package/templates/store-template/app/admin/(dashboard)/locations/page.tsx +16 -0
- package/templates/store-template/app/admin/(dashboard)/orders/[id]/page.tsx +66 -0
- package/templates/store-template/app/admin/(dashboard)/orders/page.tsx +26 -0
- package/templates/store-template/app/admin/(dashboard)/page.tsx +146 -0
- package/templates/store-template/app/admin/(dashboard)/payments/page.tsx +15 -0
- package/templates/store-template/app/admin/(dashboard)/products/[id]/page.tsx +38 -0
- package/templates/store-template/app/admin/(dashboard)/products/new/page.tsx +35 -0
- package/templates/store-template/app/admin/(dashboard)/products/page.tsx +31 -0
- package/templates/store-template/app/admin/(dashboard)/settings/page.tsx +46 -0
- package/templates/store-template/app/admin/(dashboard)/shipping/page.tsx +15 -0
- package/templates/store-template/app/admin/(dashboard)/tax/page.tsx +15 -0
- package/templates/store-template/app/admin/(dashboard)/users/[id]/page.tsx +114 -0
- package/templates/store-template/app/admin/(dashboard)/users/page.tsx +16 -0
- package/templates/store-template/app/admin/accept-invite/page.tsx +36 -0
- package/templates/store-template/app/admin/forgot-password/page.tsx +13 -0
- package/templates/store-template/app/admin/layout.tsx +47 -0
- package/templates/store-template/app/admin/login/page.tsx +528 -0
- package/templates/store-template/app/admin/reset-password/page.tsx +36 -0
- package/templates/store-template/app/api/account/addresses/[id]/route.ts +10 -0
- package/templates/store-template/app/api/account/addresses/route.ts +10 -0
- package/templates/store-template/app/api/account/change-password/route.ts +107 -0
- package/templates/store-template/app/api/account/orders/[id]/refund/route.ts +11 -0
- package/templates/store-template/app/api/account/orders/[id]/return/route.ts +14 -0
- package/templates/store-template/app/api/account/orders/[id]/route.ts +42 -0
- package/templates/store-template/app/api/account/orders/route.ts +29 -0
- package/templates/store-template/app/api/account/profile/route.ts +165 -0
- package/templates/store-template/app/api/admin/accept-invite/route.ts +11 -0
- package/templates/store-template/app/api/admin/analytics/events/route.ts +9 -0
- package/templates/store-template/app/api/admin/analytics/route.ts +10 -0
- package/templates/store-template/app/api/admin/analytics/visitors/route.ts +9 -0
- package/templates/store-template/app/api/admin/branding/favicon/route.ts +9 -0
- package/templates/store-template/app/api/admin/branding/logo/route.ts +9 -0
- package/templates/store-template/app/api/admin/categories/[id]/route.ts +9 -0
- package/templates/store-template/app/api/admin/categories/route.ts +9 -0
- package/templates/store-template/app/api/admin/check/route.ts +10 -0
- package/templates/store-template/app/api/admin/customer-groups/[id]/route.ts +10 -0
- package/templates/store-template/app/api/admin/customer-groups/route.ts +10 -0
- package/templates/store-template/app/api/admin/customers/[id]/addresses/[addressId]/route.ts +15 -0
- package/templates/store-template/app/api/admin/customers/[id]/addresses/route.ts +15 -0
- package/templates/store-template/app/api/admin/customers/[id]/route.ts +11 -0
- package/templates/store-template/app/api/admin/customers/bulk/route.ts +8 -0
- package/templates/store-template/app/api/admin/customers/route.ts +9 -0
- package/templates/store-template/app/api/admin/discounts/[id]/route.ts +11 -0
- package/templates/store-template/app/api/admin/discounts/route.ts +10 -0
- package/templates/store-template/app/api/admin/emails/preview/route.ts +4 -0
- package/templates/store-template/app/api/admin/emails/route.ts +5 -0
- package/templates/store-template/app/api/admin/emails/test/route.ts +4 -0
- package/templates/store-template/app/api/admin/files/route.ts +8 -0
- package/templates/store-template/app/api/admin/forgot-password/route.ts +7 -0
- package/templates/store-template/app/api/admin/linked-customer/route.ts +11 -0
- package/templates/store-template/app/api/admin/locations/[id]/route.ts +10 -0
- package/templates/store-template/app/api/admin/locations/route.ts +10 -0
- package/templates/store-template/app/api/admin/me/password/route.ts +13 -0
- package/templates/store-template/app/api/admin/me/route.ts +11 -0
- package/templates/store-template/app/api/admin/orders/[id]/refund/route.ts +7 -0
- package/templates/store-template/app/api/admin/orders/[id]/return/approve/route.ts +7 -0
- package/templates/store-template/app/api/admin/orders/[id]/return/reject/route.ts +7 -0
- package/templates/store-template/app/api/admin/orders/[id]/route.ts +9 -0
- package/templates/store-template/app/api/admin/orders/[id]/ship-with-shippo/route.ts +6 -0
- package/templates/store-template/app/api/admin/orders/[id]/shippo-rates/route.ts +6 -0
- package/templates/store-template/app/api/admin/orders/bulk/route.ts +8 -0
- package/templates/store-template/app/api/admin/orders/route.ts +9 -0
- package/templates/store-template/app/api/admin/payment-methods/[id]/route.ts +10 -0
- package/templates/store-template/app/api/admin/payment-methods/route.ts +10 -0
- package/templates/store-template/app/api/admin/products/[id]/route.ts +9 -0
- package/templates/store-template/app/api/admin/products/bulk/route.ts +9 -0
- package/templates/store-template/app/api/admin/products/route.ts +9 -0
- package/templates/store-template/app/api/admin/products/stats/route.ts +10 -0
- package/templates/store-template/app/api/admin/reset-password/route.ts +10 -0
- package/templates/store-template/app/api/admin/settings/route.ts +11 -0
- package/templates/store-template/app/api/admin/setup/route.ts +10 -0
- package/templates/store-template/app/api/admin/setup-guide/manual-mark/route.ts +13 -0
- package/templates/store-template/app/api/admin/setup-guide/route.ts +10 -0
- package/templates/store-template/app/api/admin/shipping/carriers/[id]/route.ts +9 -0
- package/templates/store-template/app/api/admin/shipping/carriers/route.ts +9 -0
- package/templates/store-template/app/api/admin/shipping/rates/[id]/route.ts +9 -0
- package/templates/store-template/app/api/admin/shipping/rates/route.ts +9 -0
- package/templates/store-template/app/api/admin/shipping/shippo/api-key/route.ts +1 -0
- package/templates/store-template/app/api/admin/shipping/shippo/validate/route.ts +1 -0
- package/templates/store-template/app/api/admin/shipping/zones/[id]/route.ts +9 -0
- package/templates/store-template/app/api/admin/shipping/zones/route.ts +9 -0
- package/templates/store-template/app/api/admin/stats/route.ts +9 -0
- package/templates/store-template/app/api/admin/stripe-status/route.ts +1 -0
- package/templates/store-template/app/api/admin/tax-zones/[id]/route.ts +7 -0
- package/templates/store-template/app/api/admin/tax-zones/route.ts +7 -0
- package/templates/store-template/app/api/admin/users/[id]/route.ts +14 -0
- package/templates/store-template/app/api/admin/users/route.ts +12 -0
- package/templates/store-template/app/api/admin-auth/[...nextauth]/route.ts +17 -0
- package/templates/store-template/app/api/analytics/track/route.ts +12 -0
- package/templates/store-template/app/api/auth/[...nextauth]/route.ts +19 -0
- package/templates/store-template/app/api/auth/forgot-password/route.ts +9 -0
- package/templates/store-template/app/api/auth/register/route.ts +9 -0
- package/templates/store-template/app/api/auth/resend-verification/route.ts +9 -0
- package/templates/store-template/app/api/auth/reset-password/route.ts +10 -0
- package/templates/store-template/app/api/auth/verify-email/route.ts +10 -0
- package/templates/store-template/app/api/categories/route.ts +10 -0
- package/templates/store-template/app/api/checkout/countries/route.ts +7 -0
- package/templates/store-template/app/api/checkout/discount/route.ts +9 -0
- package/templates/store-template/app/api/checkout/manual/route.ts +10 -0
- package/templates/store-template/app/api/checkout/payment-methods/route.ts +8 -0
- package/templates/store-template/app/api/checkout/pickup-locations/route.ts +8 -0
- package/templates/store-template/app/api/checkout/route.ts +10 -0
- package/templates/store-template/app/api/checkout/shipping/route.ts +21 -0
- package/templates/store-template/app/api/checkout/status/route.ts +10 -0
- package/templates/store-template/app/api/contact/route.ts +164 -0
- package/templates/store-template/app/api/media/presign/route.ts +7 -0
- package/templates/store-template/app/api/media/route.ts +7 -0
- package/templates/store-template/app/api/products/[slug]/route.ts +10 -0
- package/templates/store-template/app/api/products/route.ts +10 -0
- package/templates/store-template/app/api/store/settings/route.ts +12 -0
- package/templates/store-template/app/api/webhooks/stripe/route.ts +63 -0
- package/templates/store-template/app/auth/forgot-password/page.tsx +38 -0
- package/templates/store-template/app/auth/layout.tsx +17 -0
- package/templates/store-template/app/auth/reset-password/page.tsx +81 -0
- package/templates/store-template/app/auth/signin/page.tsx +62 -0
- package/templates/store-template/app/auth/signup/page.tsx +38 -0
- package/templates/store-template/app/auth/verify-email/page.tsx +185 -0
- package/templates/store-template/app/cart/page.tsx +137 -0
- package/templates/store-template/app/checkout/page.tsx +88 -0
- package/templates/store-template/app/checkout/success/page.tsx +50 -0
- package/templates/store-template/app/contact/layout.tsx +24 -0
- package/templates/store-template/app/contact/page.tsx +321 -0
- package/templates/store-template/app/faq/page.tsx +153 -0
- package/templates/store-template/app/globals.css +99 -0
- package/templates/store-template/app/layout.tsx +269 -0
- package/templates/store-template/app/not-found.tsx +35 -0
- package/templates/store-template/app/page.tsx +23 -0
- package/templates/store-template/app/privacy/page.tsx +281 -0
- package/templates/store-template/app/products/[slug]/ProductDetailClient.tsx +179 -0
- package/templates/store-template/app/products/[slug]/page.tsx +149 -0
- package/templates/store-template/app/products/layout.tsx +26 -0
- package/templates/store-template/app/products/page.tsx +102 -0
- package/templates/store-template/app/robots.ts +25 -0
- package/templates/store-template/app/sitemap.ts +76 -0
- package/templates/store-template/app/terms/page.tsx +313 -0
- package/templates/store-template/components/PathSyncBridge.tsx +61 -0
- package/templates/store-template/components/Providers.tsx +37 -0
- package/templates/store-template/components/layout/Footer.tsx +150 -0
- package/templates/store-template/components/layout/Header.tsx +162 -0
- package/templates/store-template/components/sections/.gitkeep +7 -0
- package/templates/store-template/components/sections/CTA.tsx +51 -0
- package/templates/store-template/components/sections/Features.tsx +91 -0
- package/templates/store-template/components/sections/Hero.tsx +85 -0
- package/templates/store-template/eslint.config.mjs +18 -0
- package/templates/store-template/lib/store-config.ts +77 -0
- package/templates/store-template/lib/store-policy.ts +68 -0
- package/templates/store-template/lib/utils.ts +21 -0
- package/templates/store-template/next-env.d.ts +6 -0
- package/templates/store-template/next.config.js +105 -0
- package/templates/store-template/package.json +32 -0
- package/templates/store-template/postcss.config.js +6 -0
- package/templates/store-template/proxy.ts +150 -0
- package/templates/store-template/public/rovela-logo.png +0 -0
- package/templates/store-template/tailwind.config.ts +74 -0
- package/templates/store-template/tsconfig.json +41 -0
- package/templates/store-template/types/next-auth.d.ts +32 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin Dashboard
|
|
5
|
+
*
|
|
6
|
+
* Overview of store metrics, charts, and recent activity.
|
|
7
|
+
*
|
|
8
|
+
* Metric layout:
|
|
9
|
+
* - Primary row: Revenue / Orders / AOV / Refund rate (with period deltas)
|
|
10
|
+
* - Secondary row: Best seller / Customers / Active products
|
|
11
|
+
* - Time-window selector in the header drives both rows
|
|
12
|
+
*
|
|
13
|
+
* Charts (RevenueChart + OrderStatusChart) and Activity (RecentOrders +
|
|
14
|
+
* LowStockAlert) preserve the existing behavior. RevenueChart now also
|
|
15
|
+
* scopes to the selected window.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { useState } from 'react'
|
|
19
|
+
import Link from 'next/link'
|
|
20
|
+
import { Plus } from 'lucide-react'
|
|
21
|
+
import {
|
|
22
|
+
AdminPageHeader,
|
|
23
|
+
PrimaryMetricsRow,
|
|
24
|
+
SecondaryMetricsRow,
|
|
25
|
+
PeriodSelector,
|
|
26
|
+
RecentOrders,
|
|
27
|
+
LowStockAlert,
|
|
28
|
+
RevenueChart,
|
|
29
|
+
OrderStatusChart,
|
|
30
|
+
useAdminStats,
|
|
31
|
+
useAdminAuth,
|
|
32
|
+
} from '@rovela-ai/sdk/admin'
|
|
33
|
+
import type { DashboardPeriod } from '@rovela-ai/sdk/admin'
|
|
34
|
+
import { useStoreSettings } from '@rovela-ai/sdk/core'
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Helpers
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
function getGreeting(): string {
|
|
41
|
+
const hour = new Date().getHours()
|
|
42
|
+
if (hour < 12) return 'Good morning'
|
|
43
|
+
if (hour < 18) return 'Good afternoon'
|
|
44
|
+
return 'Good evening'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getFormattedDate(): string {
|
|
48
|
+
return new Date().toLocaleDateString('en-US', {
|
|
49
|
+
weekday: 'long',
|
|
50
|
+
month: 'long',
|
|
51
|
+
day: 'numeric',
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Component
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
export default function AdminDashboardPage() {
|
|
60
|
+
const { admin } = useAdminAuth()
|
|
61
|
+
const { currency, locale } = useStoreSettings()
|
|
62
|
+
const [period, setPeriod] = useState<DashboardPeriod>('30d')
|
|
63
|
+
|
|
64
|
+
const {
|
|
65
|
+
stats,
|
|
66
|
+
summary,
|
|
67
|
+
revenueByDay,
|
|
68
|
+
ordersByStatus,
|
|
69
|
+
isLoading,
|
|
70
|
+
} = useAdminStats({
|
|
71
|
+
includeRevenue: true,
|
|
72
|
+
includeOrderStatus: true,
|
|
73
|
+
period,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const firstName = admin?.name?.split(' ')[0] || 'Admin'
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className="space-y-6">
|
|
81
|
+
{/* THE page header (AdminPageHeader = LEDGER page architecture):
|
|
82
|
+
date eyebrow + greeting title + actions, one primary button. */}
|
|
83
|
+
<AdminPageHeader
|
|
84
|
+
eyebrow={getFormattedDate()}
|
|
85
|
+
title={`${getGreeting()}, ${firstName}`}
|
|
86
|
+
actions={
|
|
87
|
+
/* View/Edit store moved to the AdminTopBar (2026-07 revamp) — the
|
|
88
|
+
page keeps only its own concerns: the period and the primary. */
|
|
89
|
+
<>
|
|
90
|
+
<PeriodSelector value={period} onChange={setPeriod} />
|
|
91
|
+
<Link href="/admin/products/new" className="admin-btn admin-btn-primary admin-btn-sm">
|
|
92
|
+
<Plus className="h-3.5 w-3.5" strokeWidth={2} />
|
|
93
|
+
Add product
|
|
94
|
+
</Link>
|
|
95
|
+
</>
|
|
96
|
+
}
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
{/* Primary metrics — Revenue / Orders / AOV / Refund rate */}
|
|
100
|
+
<section>
|
|
101
|
+
<PrimaryMetricsRow
|
|
102
|
+
summary={summary}
|
|
103
|
+
isLoading={isLoading}
|
|
104
|
+
currency={currency}
|
|
105
|
+
locale={locale}
|
|
106
|
+
/>
|
|
107
|
+
</section>
|
|
108
|
+
|
|
109
|
+
{/* Secondary — Best seller / Customers / Products */}
|
|
110
|
+
<section>
|
|
111
|
+
<SecondaryMetricsRow
|
|
112
|
+
summary={summary}
|
|
113
|
+
stats={stats}
|
|
114
|
+
isLoading={isLoading}
|
|
115
|
+
currency={currency}
|
|
116
|
+
locale={locale}
|
|
117
|
+
/>
|
|
118
|
+
</section>
|
|
119
|
+
|
|
120
|
+
{/* Charts Row */}
|
|
121
|
+
<section className="grid grid-cols-1 gap-6 xl:grid-cols-3">
|
|
122
|
+
<div className="xl:col-span-2">
|
|
123
|
+
<RevenueChart
|
|
124
|
+
data={revenueByDay}
|
|
125
|
+
isLoading={isLoading}
|
|
126
|
+
currency={currency}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
<div>
|
|
130
|
+
<OrderStatusChart
|
|
131
|
+
data={ordersByStatus}
|
|
132
|
+
isLoading={isLoading}
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
</section>
|
|
136
|
+
|
|
137
|
+
{/* Activity Section */}
|
|
138
|
+
<section>
|
|
139
|
+
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
140
|
+
<RecentOrders period={period} />
|
|
141
|
+
<LowStockAlert />
|
|
142
|
+
</div>
|
|
143
|
+
</section>
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin Payments Page
|
|
5
|
+
*
|
|
6
|
+
* Stripe connection, manual payment methods and currency. The
|
|
7
|
+
* PaymentSettings component renders the page header itself (LEDGER page
|
|
8
|
+
* architecture): this wrapper stays a bare mount.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { PaymentSettings } from '@rovela-ai/sdk/admin'
|
|
12
|
+
|
|
13
|
+
export default function AdminPaymentsPage() {
|
|
14
|
+
return <PaymentSettings />
|
|
15
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin Edit Product Page
|
|
5
|
+
*
|
|
6
|
+
* Edit an existing product using the SDK ProductForm component.
|
|
7
|
+
* Uses useParams() hook for client-side dynamic route access.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useParams, useRouter } from 'next/navigation'
|
|
11
|
+
import { AdminPageHeader, ProductForm } from '@rovela-ai/sdk/admin'
|
|
12
|
+
|
|
13
|
+
export default function EditProductPage() {
|
|
14
|
+
const params = useParams()
|
|
15
|
+
const id = params.id as string
|
|
16
|
+
const router = useRouter()
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="space-y-6">
|
|
20
|
+
<AdminPageHeader
|
|
21
|
+
backHref="/admin/products"
|
|
22
|
+
backLabel="Back to products"
|
|
23
|
+
eyebrow="Products"
|
|
24
|
+
title="Edit product"
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
<ProductForm
|
|
28
|
+
productId={id}
|
|
29
|
+
onSuccess={() => {
|
|
30
|
+
router.push('/admin/products')
|
|
31
|
+
}}
|
|
32
|
+
onCancel={() => {
|
|
33
|
+
router.push('/admin/products')
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin New Product Page
|
|
5
|
+
*
|
|
6
|
+
* Create a new product using the SDK ProductForm component.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useRouter } from 'next/navigation'
|
|
10
|
+
import { AdminPageHeader, ProductForm } from '@rovela-ai/sdk/admin'
|
|
11
|
+
|
|
12
|
+
export default function NewProductPage() {
|
|
13
|
+
const router = useRouter()
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="space-y-6">
|
|
17
|
+
<AdminPageHeader
|
|
18
|
+
backHref="/admin/products"
|
|
19
|
+
backLabel="Back to products"
|
|
20
|
+
eyebrow="Products"
|
|
21
|
+
title="Add product"
|
|
22
|
+
subtitle="Create a new product for your store."
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<ProductForm
|
|
26
|
+
onSuccess={() => {
|
|
27
|
+
router.push('/admin/products')
|
|
28
|
+
}}
|
|
29
|
+
onCancel={() => {
|
|
30
|
+
router.push('/admin/products')
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin Products Page
|
|
5
|
+
*
|
|
6
|
+
* Product management with table and actions.
|
|
7
|
+
* Uses SDK admin ProductTable component.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import Link from 'next/link'
|
|
11
|
+
import { Plus } from 'lucide-react'
|
|
12
|
+
import { AdminPageHeader, ProductTable } from '@rovela-ai/sdk/admin'
|
|
13
|
+
|
|
14
|
+
export default function AdminProductsPage() {
|
|
15
|
+
return (
|
|
16
|
+
<div className="space-y-6">
|
|
17
|
+
<AdminPageHeader
|
|
18
|
+
title="Products"
|
|
19
|
+
subtitle="Manage your product catalog."
|
|
20
|
+
actions={
|
|
21
|
+
<Link href="/admin/products/new" className="admin-btn admin-btn-primary">
|
|
22
|
+
<Plus className="h-4 w-4" />
|
|
23
|
+
Add product
|
|
24
|
+
</Link>
|
|
25
|
+
}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<ProductTable />
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin Settings Page
|
|
5
|
+
*
|
|
6
|
+
* Store settings management with database persistence.
|
|
7
|
+
* Uses self-contained SDK components:
|
|
8
|
+
* - StoreSettings: Fetches and saves store configuration
|
|
9
|
+
* - LogoUpload: Handles logo upload/delete
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { AdminPageHeader, StoreSettings, LogoUpload, FaviconUpload } from '@rovela-ai/sdk/admin'
|
|
13
|
+
import { useStoreSettings } from '@rovela-ai/sdk/core'
|
|
14
|
+
|
|
15
|
+
export default function AdminSettingsPage() {
|
|
16
|
+
// Get refetch from global store settings context to update sidebar/header after save
|
|
17
|
+
const { refetch: refetchGlobalSettings } = useStoreSettings()
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="space-y-6">
|
|
21
|
+
<AdminPageHeader
|
|
22
|
+
title="Settings"
|
|
23
|
+
subtitle="Your store's identity, checkout and languages."
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
{/* Two-column layout */}
|
|
27
|
+
<div className="grid gap-6 lg:grid-cols-3">
|
|
28
|
+
{/* Left Column: Settings Form (2/3) */}
|
|
29
|
+
<div className="lg:col-span-2">
|
|
30
|
+
<StoreSettings onSave={() => refetchGlobalSettings()} />
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{/* Right Column: Branding (1/3) */}
|
|
34
|
+
<div>
|
|
35
|
+
<div className="admin-card p-6">
|
|
36
|
+
<h3 className="admin-card-title mb-4">Store branding</h3>
|
|
37
|
+
<LogoUpload />
|
|
38
|
+
<div className="mt-6 border-t border-[hsl(var(--admin-border))] pt-6">
|
|
39
|
+
<FaviconUpload />
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin Shipping Page
|
|
5
|
+
*
|
|
6
|
+
* Carriers, zones and rates. The ShippingSettings component renders the
|
|
7
|
+
* page header itself (LEDGER page architecture): this wrapper stays a
|
|
8
|
+
* bare mount.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ShippingSettings } from '@rovela-ai/sdk/admin'
|
|
12
|
+
|
|
13
|
+
export default function AdminShippingPage() {
|
|
14
|
+
return <ShippingSettings />
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin Tax Page
|
|
5
|
+
*
|
|
6
|
+
* Tax configuration and zone-based rates. The TaxSettings component
|
|
7
|
+
* renders the page header itself (LEDGER page architecture): this wrapper
|
|
8
|
+
* stays a bare mount.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { TaxSettings } from '@rovela-ai/sdk/admin'
|
|
12
|
+
|
|
13
|
+
export default function AdminTaxPage() {
|
|
14
|
+
return <TaxSettings />
|
|
15
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin User Detail Page
|
|
5
|
+
*
|
|
6
|
+
* Minimal detail view for a single admin. Shows their core info (name,
|
|
7
|
+
* email, role, status, created/last-login dates) and a "Back to users"
|
|
8
|
+
* link. Phase 4 may extend this with activity history and audit info.
|
|
9
|
+
*
|
|
10
|
+
* For Phase 2 we keep it deliberately bare: the table at /admin/users is
|
|
11
|
+
* the main interaction surface and handles all lifecycle actions directly.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useEffect, useState } from 'react'
|
|
15
|
+
import { useParams } from 'next/navigation'
|
|
16
|
+
import Link from 'next/link'
|
|
17
|
+
import { AdminPageHeader, useAdminUsers, type AdminListItem } from '@rovela-ai/sdk/admin'
|
|
18
|
+
|
|
19
|
+
export default function AdminUserDetailPage() {
|
|
20
|
+
const params = useParams<{ id: string }>()
|
|
21
|
+
const { getUser } = useAdminUsers()
|
|
22
|
+
const [user, setUser] = useState<AdminListItem | null>(null)
|
|
23
|
+
const [loading, setLoading] = useState(true)
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!params?.id) return
|
|
27
|
+
let cancelled = false
|
|
28
|
+
;(async () => {
|
|
29
|
+
const result = await getUser(params.id)
|
|
30
|
+
if (!cancelled) {
|
|
31
|
+
setUser(result)
|
|
32
|
+
setLoading(false)
|
|
33
|
+
}
|
|
34
|
+
})()
|
|
35
|
+
return () => {
|
|
36
|
+
cancelled = true
|
|
37
|
+
}
|
|
38
|
+
}, [params?.id, getUser])
|
|
39
|
+
|
|
40
|
+
if (loading) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="space-y-4">
|
|
43
|
+
<div className="admin-skeleton admin-skeleton-lg w-64 rounded" />
|
|
44
|
+
<div className="admin-skeleton admin-skeleton-md w-96 rounded" />
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!user) {
|
|
50
|
+
return (
|
|
51
|
+
<div className="space-y-4">
|
|
52
|
+
<h1 className="admin-page-title">Team member not found</h1>
|
|
53
|
+
<Link href="/admin/users" className="text-sm admin-text-muted hover:underline">
|
|
54
|
+
← Back to users
|
|
55
|
+
</Link>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="space-y-6">
|
|
62
|
+
<AdminPageHeader
|
|
63
|
+
backHref="/admin/users"
|
|
64
|
+
backLabel="Back to team"
|
|
65
|
+
eyebrow="Team"
|
|
66
|
+
title={user.name || 'Unnamed admin'}
|
|
67
|
+
subtitle={user.email}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<div className="admin-card p-6">
|
|
71
|
+
<dl className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
|
|
72
|
+
<div>
|
|
73
|
+
<dt className="admin-text-muted">Role</dt>
|
|
74
|
+
<dd className="font-medium">{user.role}</dd>
|
|
75
|
+
</div>
|
|
76
|
+
<div>
|
|
77
|
+
<dt className="admin-text-muted">Status</dt>
|
|
78
|
+
<dd className="font-medium">{user.status}</dd>
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<dt className="admin-text-muted">Created</dt>
|
|
82
|
+
<dd className="font-medium">
|
|
83
|
+
{new Date(user.createdAt).toLocaleDateString()}
|
|
84
|
+
</dd>
|
|
85
|
+
</div>
|
|
86
|
+
<div>
|
|
87
|
+
<dt className="admin-text-muted">Last login</dt>
|
|
88
|
+
<dd className="font-medium">
|
|
89
|
+
{user.lastLoginAt
|
|
90
|
+
? new Date(user.lastLoginAt).toLocaleDateString()
|
|
91
|
+
: 'Never'}
|
|
92
|
+
</dd>
|
|
93
|
+
</div>
|
|
94
|
+
{user.deactivatedAt && (
|
|
95
|
+
<>
|
|
96
|
+
<div>
|
|
97
|
+
<dt className="admin-text-muted">Deactivated</dt>
|
|
98
|
+
<dd className="font-medium">
|
|
99
|
+
{new Date(user.deactivatedAt).toLocaleDateString()}
|
|
100
|
+
</dd>
|
|
101
|
+
</div>
|
|
102
|
+
<div>
|
|
103
|
+
<dt className="admin-text-muted">Deactivated by</dt>
|
|
104
|
+
<dd className="font-medium">
|
|
105
|
+
{user.deactivatedBy || 'Unknown'}
|
|
106
|
+
</dd>
|
|
107
|
+
</div>
|
|
108
|
+
</>
|
|
109
|
+
)}
|
|
110
|
+
</dl>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Admin Team Page (route: /admin/users)
|
|
5
|
+
*
|
|
6
|
+
* The UsersTable renders the page header itself (title, subtitle and the
|
|
7
|
+
* Invite CTA) — LEDGER page architecture: this wrapper stays a bare mount
|
|
8
|
+
* so the title and primary action exist exactly once. All permission
|
|
9
|
+
* checks, invariants, and error handling happen at the service + API layer.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { UsersTable } from '@rovela-ai/sdk/admin'
|
|
13
|
+
|
|
14
|
+
export default function AdminUsersPage() {
|
|
15
|
+
return <UsersTable pageSize={20} />
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Accept Invite Page
|
|
3
|
+
*
|
|
4
|
+
* Public page (outside the dashboard route group). The SDK's
|
|
5
|
+
* AdminAcceptInviteForm renders its own AdminAuthShell (brand tile +
|
|
6
|
+
* store name + card + theme CSS), so this page is a bare mount.
|
|
7
|
+
*
|
|
8
|
+
* The form reads the `?token=X` query param via `useSearchParams`, so
|
|
9
|
+
* Next.js App Router requires a <Suspense> boundary here. The fallback
|
|
10
|
+
* reuses the shell so the suspense moment and the form's own validating
|
|
11
|
+
* state look identical.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Suspense } from 'react'
|
|
15
|
+
import {
|
|
16
|
+
AdminAuthShell,
|
|
17
|
+
AdminAcceptInviteForm,
|
|
18
|
+
} from '@rovela-ai/sdk/admin/components'
|
|
19
|
+
|
|
20
|
+
function LoadingFallback() {
|
|
21
|
+
return (
|
|
22
|
+
<AdminAuthShell>
|
|
23
|
+
<div className="flex items-center justify-center py-8">
|
|
24
|
+
<span className="admin-spinner admin-spinner-lg" />
|
|
25
|
+
</div>
|
|
26
|
+
</AdminAuthShell>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function AdminAcceptInvitePage() {
|
|
31
|
+
return (
|
|
32
|
+
<Suspense fallback={<LoadingFallback />}>
|
|
33
|
+
<AdminAcceptInviteForm />
|
|
34
|
+
</Suspense>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Forgot Password Page
|
|
3
|
+
*
|
|
4
|
+
* Public page (outside the dashboard route group). The SDK's
|
|
5
|
+
* AdminForgotPasswordForm renders its own AdminAuthShell (brand tile +
|
|
6
|
+
* store name + card + theme CSS), so this page is a bare mount.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AdminForgotPasswordForm } from '@rovela-ai/sdk/admin/components'
|
|
10
|
+
|
|
11
|
+
export default function AdminForgotPasswordPage() {
|
|
12
|
+
return <AdminForgotPasswordForm />
|
|
13
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Plus_Jakarta_Sans } from 'next/font/google'
|
|
2
|
+
import type { Viewport } from 'next'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Admin segment layout — loads Plus Jakarta Sans via next/font and exposes it
|
|
6
|
+
* as `--admin-font-jakarta`, which admin-theme.css consumes through
|
|
7
|
+
* `--admin-font-sans`. This replaced a render-blocking Google Fonts @import
|
|
8
|
+
* inside the CSS (2026-07 admin redesign): next/font self-hosts the files,
|
|
9
|
+
* eliminates the extra origin round-trip, and covers EVERY /admin route
|
|
10
|
+
* (dashboard group + the auth pages) from one place.
|
|
11
|
+
*
|
|
12
|
+
* `display: contents` keeps the wrapper structurally invisible — it exists
|
|
13
|
+
* only to scope the font variable.
|
|
14
|
+
*/
|
|
15
|
+
const jakarta = Plus_Jakarta_Sans({
|
|
16
|
+
subsets: ['latin'],
|
|
17
|
+
weight: ['400', '500', '600', '700'],
|
|
18
|
+
variable: '--admin-font-jakarta',
|
|
19
|
+
display: 'swap',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Explicit viewport contract for the responsive admin (2026-07 mobile pass).
|
|
23
|
+
// Next.js would inject the same default, but the admin's mobile chrome now
|
|
24
|
+
// depends on it, so it is stated rather than assumed.
|
|
25
|
+
export const viewport: Viewport = {
|
|
26
|
+
width: 'device-width',
|
|
27
|
+
initialScale: 1,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// The admin is per-request and session-gated — never static. Declaring it
|
|
31
|
+
// (same pattern as the auth/ and account/ segments) stops the build from
|
|
32
|
+
// attempting to prerender ~21 admin pages, which threw Next's intentional
|
|
33
|
+
// DYNAMIC_SERVER_USAGE bailout through requireAdmin's catch and printed a
|
|
34
|
+
// scary-but-benign "Failed to read session" block per page in build logs.
|
|
35
|
+
export const dynamic = 'force-dynamic'
|
|
36
|
+
|
|
37
|
+
export default function AdminSegmentLayout({
|
|
38
|
+
children,
|
|
39
|
+
}: {
|
|
40
|
+
children: React.ReactNode
|
|
41
|
+
}) {
|
|
42
|
+
return (
|
|
43
|
+
<div className={jakarta.variable} style={{ display: 'contents' }}>
|
|
44
|
+
{children}
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|