@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,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
|
+
}
|