@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,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer Refund Request API Route
|
|
3
|
+
*
|
|
4
|
+
* POST /api/account/orders/[id]/refund - Request a refund for an order
|
|
5
|
+
*
|
|
6
|
+
* Only available for orders that:
|
|
7
|
+
* - Belong to the authenticated customer
|
|
8
|
+
* - Have status 'paid' (not yet shipped)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { POST } from '@rovela-ai/sdk/auth/api/request-refund'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer Return Request API Route
|
|
3
|
+
*
|
|
4
|
+
* POST /api/account/orders/[id]/return - Request a return for a delivered order
|
|
5
|
+
*
|
|
6
|
+
* Only available for orders that:
|
|
7
|
+
* - Belong to the authenticated customer
|
|
8
|
+
* - Have status 'delivered'
|
|
9
|
+
*
|
|
10
|
+
* This creates a return request that must be approved by an admin
|
|
11
|
+
* before a refund is processed.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { POST } from '@rovela-ai/sdk/auth/api/request-return'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer Single Order API Route
|
|
3
|
+
*
|
|
4
|
+
* GET /api/account/orders/[id] - Get single order for authenticated customer
|
|
5
|
+
*
|
|
6
|
+
* Protected by `requireCustomer()`. In addition, we verify the order's
|
|
7
|
+
* `customerId` matches the authenticated customer — cross-customer access
|
|
8
|
+
* returns 403 rather than leaking existence information.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { requireCustomer } from '@rovela-ai/sdk/auth/server'
|
|
12
|
+
import { findOrderById } from '@rovela-ai/sdk/core/server'
|
|
13
|
+
|
|
14
|
+
export async function GET(
|
|
15
|
+
_request: Request,
|
|
16
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
17
|
+
) {
|
|
18
|
+
const guard = await requireCustomer()
|
|
19
|
+
if (!guard.ok) return guard.response
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const { id } = await params
|
|
23
|
+
const order = await findOrderById(id)
|
|
24
|
+
|
|
25
|
+
if (!order) {
|
|
26
|
+
return Response.json({ error: 'Order not found' }, { status: 404 })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Verify the order belongs to this customer
|
|
30
|
+
if (order.customerId !== guard.customer.id) {
|
|
31
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Response.json({ order })
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Failed to fetch order:', error)
|
|
37
|
+
return Response.json(
|
|
38
|
+
{ error: 'Failed to fetch order' },
|
|
39
|
+
{ status: 500 }
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer Orders API Route
|
|
3
|
+
*
|
|
4
|
+
* GET /api/account/orders - Get orders for authenticated customer
|
|
5
|
+
*
|
|
6
|
+
* Protected by `requireCustomer()` which:
|
|
7
|
+
* - Rejects unauthenticated requests (401)
|
|
8
|
+
* - Rejects admin sessions (403) — defense in depth
|
|
9
|
+
* - Verifies the customer row still exists in the store DB (401 if not)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { requireCustomer } from '@rovela-ai/sdk/auth/server'
|
|
13
|
+
import { findOrdersWithItems } from '@rovela-ai/sdk/core/server'
|
|
14
|
+
|
|
15
|
+
export async function GET() {
|
|
16
|
+
const guard = await requireCustomer()
|
|
17
|
+
if (!guard.ok) return guard.response
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const orders = await findOrdersWithItems({ customerId: guard.customer.id })
|
|
21
|
+
return Response.json({ orders })
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Failed to fetch orders:', error)
|
|
24
|
+
return Response.json(
|
|
25
|
+
{ error: 'Failed to fetch orders' },
|
|
26
|
+
{ status: 500 }
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer Profile API Route
|
|
3
|
+
*
|
|
4
|
+
* GET /api/account/profile - Get current customer profile
|
|
5
|
+
* PUT /api/account/profile - Update customer name and/or email
|
|
6
|
+
*
|
|
7
|
+
* Protected by `requireCustomer()` — admins get a 403, not a 401, because
|
|
8
|
+
* the endpoint is valid but the identity class isn't.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { requireCustomer } from '@rovela-ai/sdk/auth/server'
|
|
12
|
+
import {
|
|
13
|
+
findCustomerById,
|
|
14
|
+
updateCustomer,
|
|
15
|
+
emailExists,
|
|
16
|
+
} from '@rovela-ai/sdk/auth/server'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* GET /api/account/profile
|
|
20
|
+
*
|
|
21
|
+
* Returns the authenticated customer's profile information.
|
|
22
|
+
*/
|
|
23
|
+
export async function GET() {
|
|
24
|
+
const guard = await requireCustomer()
|
|
25
|
+
if (!guard.ok) return guard.response
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const customer = await findCustomerById(guard.customer.id)
|
|
29
|
+
|
|
30
|
+
if (!customer) {
|
|
31
|
+
return Response.json({ error: 'Customer not found' }, { status: 404 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Return profile data (exclude sensitive fields)
|
|
35
|
+
return Response.json({
|
|
36
|
+
profile: {
|
|
37
|
+
id: customer.id,
|
|
38
|
+
email: customer.email,
|
|
39
|
+
name: customer.name,
|
|
40
|
+
phone: customer.phone ?? null,
|
|
41
|
+
emailVerified: !!customer.emailVerified,
|
|
42
|
+
createdAt: customer.createdAt,
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('[Profile API] Failed to fetch profile:', error)
|
|
47
|
+
return Response.json(
|
|
48
|
+
{ error: 'Failed to fetch profile' },
|
|
49
|
+
{ status: 500 }
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* PUT /api/account/profile
|
|
56
|
+
*
|
|
57
|
+
* Updates the authenticated customer's profile.
|
|
58
|
+
* Accepts: { name?: string, email?: string, phone?: string | null }
|
|
59
|
+
*/
|
|
60
|
+
export async function PUT(request: Request) {
|
|
61
|
+
const guard = await requireCustomer()
|
|
62
|
+
if (!guard.ok) return guard.response
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const body = await request.json()
|
|
66
|
+
const { name, email, phone } = body as {
|
|
67
|
+
name?: string
|
|
68
|
+
email?: string
|
|
69
|
+
phone?: string | null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Validate input
|
|
73
|
+
if (name !== undefined && typeof name !== 'string') {
|
|
74
|
+
return Response.json(
|
|
75
|
+
{ error: 'Name must be a string' },
|
|
76
|
+
{ status: 400 }
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (name !== undefined && name.trim().length > 100) {
|
|
81
|
+
return Response.json(
|
|
82
|
+
{ error: 'Name must be 100 characters or less' },
|
|
83
|
+
{ status: 400 }
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (phone !== undefined && phone !== null && typeof phone !== 'string') {
|
|
88
|
+
return Response.json(
|
|
89
|
+
{ error: 'Phone must be a string' },
|
|
90
|
+
{ status: 400 }
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof phone === 'string' && phone.trim().length > 40) {
|
|
95
|
+
return Response.json(
|
|
96
|
+
{ error: 'Phone must be 40 characters or less' },
|
|
97
|
+
{ status: 400 }
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (email !== undefined) {
|
|
102
|
+
if (typeof email !== 'string') {
|
|
103
|
+
return Response.json(
|
|
104
|
+
{ error: 'Email must be a string' },
|
|
105
|
+
{ status: 400 }
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Simple email validation
|
|
110
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
111
|
+
if (!emailRegex.test(email)) {
|
|
112
|
+
return Response.json(
|
|
113
|
+
{ error: 'Invalid email format' },
|
|
114
|
+
{ status: 400 }
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check if email is already in use by another customer
|
|
119
|
+
const normalizedEmail = email.toLowerCase().trim()
|
|
120
|
+
const currentCustomer = await findCustomerById(guard.customer.id)
|
|
121
|
+
|
|
122
|
+
if (currentCustomer && currentCustomer.email !== normalizedEmail) {
|
|
123
|
+
const emailTaken = await emailExists(normalizedEmail)
|
|
124
|
+
if (emailTaken) {
|
|
125
|
+
return Response.json(
|
|
126
|
+
{ error: 'Email is already registered' },
|
|
127
|
+
{ status: 400 }
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Update customer
|
|
134
|
+
const updatedCustomer = await updateCustomer(guard.customer.id, {
|
|
135
|
+
name: name?.trim(),
|
|
136
|
+
email: email?.toLowerCase().trim(),
|
|
137
|
+
...(phone !== undefined ? { phone: phone === null ? null : phone.trim() } : {}),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if (!updatedCustomer) {
|
|
141
|
+
return Response.json(
|
|
142
|
+
{ error: 'Customer not found' },
|
|
143
|
+
{ status: 404 }
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return Response.json({
|
|
148
|
+
success: true,
|
|
149
|
+
profile: {
|
|
150
|
+
id: updatedCustomer.id,
|
|
151
|
+
email: updatedCustomer.email,
|
|
152
|
+
name: updatedCustomer.name,
|
|
153
|
+
phone: updatedCustomer.phone ?? null,
|
|
154
|
+
emailVerified: !!updatedCustomer.emailVerified,
|
|
155
|
+
createdAt: updatedCustomer.createdAt,
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('[Profile API] Failed to update profile:', error)
|
|
160
|
+
return Response.json(
|
|
161
|
+
{ error: 'Failed to update profile' },
|
|
162
|
+
{ status: 500 }
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Accept Invite API Route
|
|
3
|
+
*
|
|
4
|
+
* Thin re-export of the SDK handlers:
|
|
5
|
+
* GET ?token=X — non-destructive token validation (returns invitee snippet)
|
|
6
|
+
* POST { token, password } — consume the token, activate the admin
|
|
7
|
+
*
|
|
8
|
+
* Unauthenticated — the token IS the auth.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { GET, POST } from '@rovela-ai/sdk/admin/api/accept-invite'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/admin/analytics?period=today|7d|30d|90d
|
|
3
|
+
*
|
|
4
|
+
* Admin-gated dashboard payload (visitors, sessions, pageviews, conversion,
|
|
5
|
+
* top pages, top products, traffic sources, devices). Gated on settings.read.
|
|
6
|
+
*
|
|
7
|
+
* See CLAUDE.md §46.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { GET } from '@rovela-ai/sdk/analytics/api/dashboard'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Favicon API — SDK re-export (see @rovela-ai/sdk/admin/api/branding).
|
|
3
|
+
*
|
|
4
|
+
* Same architecture as the logo route: R2 object at the fixed key
|
|
5
|
+
* stores/{STORE_ID}/branding/favicon + store_settings.favicon_url mirror.
|
|
6
|
+
* The favicon surfaces in the browser tab (app/layout.tsx metadata) and the
|
|
7
|
+
* admin top bar tile; both fall back to the logo when no favicon is set.
|
|
8
|
+
*/
|
|
9
|
+
export { faviconGET as GET, faviconPOST as POST, faviconDELETE as DELETE } from '@rovela-ai/sdk/admin/api/branding'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Logo API — SDK re-export (see @rovela-ai/sdk/admin/api/branding).
|
|
3
|
+
*
|
|
4
|
+
* Writes the R2 object at the fixed key stores/{STORE_ID}/branding/logo AND
|
|
5
|
+
* mirrors the URL into store_settings.logo_url (the store-side runtime
|
|
6
|
+
* mirror every settings-first surface reads: header, emails, admin top bar).
|
|
7
|
+
* GET reads the truth directly via an R2 HEAD on the fixed key.
|
|
8
|
+
*/
|
|
9
|
+
export { logoGET as GET, logoPOST as POST, logoDELETE as DELETE } from '@rovela-ai/sdk/admin/api/branding'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Single Category API Route
|
|
3
|
+
*
|
|
4
|
+
* GET/PUT/DELETE /api/admin/categories/[id]
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getCategory, updateCategory, deleteCategory } from '@rovela-ai/sdk/admin/api'
|
|
8
|
+
|
|
9
|
+
export { getCategory as GET, updateCategory as PUT, deleteCategory as DELETE }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Single Customer Group API Route
|
|
3
|
+
*
|
|
4
|
+
* PUT /api/admin/customer-groups/[id] - Update a group
|
|
5
|
+
* DELETE /api/admin/customer-groups/[id] - Delete a group (members are ungrouped)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { updateCustomerGroupHandler, deleteCustomerGroupHandler } from '@rovela-ai/sdk/admin/api'
|
|
9
|
+
|
|
10
|
+
export { updateCustomerGroupHandler as PUT, deleteCustomerGroupHandler as DELETE }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Customer Groups API Route
|
|
3
|
+
*
|
|
4
|
+
* GET /api/admin/customer-groups - List groups (with member counts)
|
|
5
|
+
* POST /api/admin/customer-groups - Create a group
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getCustomerGroups, createCustomerGroupHandler } from '@rovela-ai/sdk/admin/api'
|
|
9
|
+
|
|
10
|
+
export { getCustomerGroups as GET, createCustomerGroupHandler as POST }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Customer Address — update / set-default / archive one address.
|
|
3
|
+
*
|
|
4
|
+
* PATCH /api/admin/customers/[id]/addresses/[addressId] → update / set default
|
|
5
|
+
* DELETE /api/admin/customers/[id]/addresses/[addressId] → soft-delete (archive)
|
|
6
|
+
*
|
|
7
|
+
* Gated by `customers.write` inside the SDK handlers.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
updateCustomerAddressAdmin,
|
|
12
|
+
deleteCustomerAddressAdmin,
|
|
13
|
+
} from '@rovela-ai/sdk/admin/api'
|
|
14
|
+
|
|
15
|
+
export { updateCustomerAddressAdmin as PATCH, deleteCustomerAddressAdmin as DELETE }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Customer Address Book — list + create.
|
|
3
|
+
*
|
|
4
|
+
* GET /api/admin/customers/[id]/addresses → list a customer's saved addresses
|
|
5
|
+
* POST /api/admin/customers/[id]/addresses → create an address for the customer
|
|
6
|
+
*
|
|
7
|
+
* Gated by `customers.read` / `customers.write` inside the SDK handlers.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
listCustomerAddressesAdmin,
|
|
12
|
+
createCustomerAddressAdmin,
|
|
13
|
+
} from '@rovela-ai/sdk/admin/api'
|
|
14
|
+
|
|
15
|
+
export { listCustomerAddressesAdmin as GET, createCustomerAddressAdmin as POST }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Single Customer API Route
|
|
3
|
+
*
|
|
4
|
+
* GET /api/admin/customers/[id] - Get customer details
|
|
5
|
+
* PUT /api/admin/customers/[id] - Update customer
|
|
6
|
+
* DELETE /api/admin/customers/[id] - Delete (anonymize) customer
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getCustomer, updateCustomer, deleteCustomer } from '@rovela-ai/sdk/admin/api'
|
|
10
|
+
|
|
11
|
+
export { getCustomer as GET, updateCustomer as PUT, deleteCustomer as DELETE }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Customers Bulk API Route
|
|
3
|
+
*
|
|
4
|
+
* POST /api/admin/customers/bulk - Bulk group assign / soft delete
|
|
5
|
+
* Body: { ids: string[], action: 'assign_group' | 'delete', groupId?: string | null }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { bulkCustomers as POST } from '@rovela-ai/sdk/admin/api'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Single Discount API Route
|
|
3
|
+
*
|
|
4
|
+
* PUT /api/admin/discounts/[id] - Update a discount
|
|
5
|
+
* DELETE /api/admin/discounts/[id] - Delete a discount (order history keeps
|
|
6
|
+
* its code + amount snapshots; redemption records survive)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { updateDiscountHandler, deleteDiscountHandler } from '@rovela-ai/sdk/admin/api'
|
|
10
|
+
|
|
11
|
+
export { updateDiscountHandler as PUT, deleteDiscountHandler as DELETE }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Discounts API Route
|
|
3
|
+
*
|
|
4
|
+
* GET /api/admin/discounts - List all discounts
|
|
5
|
+
* POST /api/admin/discounts - Create a discount (code or automatic)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getDiscounts, createDiscountHandler } from '@rovela-ai/sdk/admin/api'
|
|
9
|
+
|
|
10
|
+
export { getDiscounts as GET, createDiscountHandler as POST }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Files API Route
|
|
3
|
+
*
|
|
4
|
+
* GET /api/admin/files - List the store's media library (R2 listing)
|
|
5
|
+
* DELETE /api/admin/files - Delete one file by key (system assets protected)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { getStoreFilesHandler as GET, deleteStoreFileHandler as DELETE } from '@rovela-ai/sdk/media/api'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Linked-Customer API Route
|
|
3
|
+
*
|
|
4
|
+
* Thin re-export of the SDK handler:
|
|
5
|
+
* GET — returns { hasLinkedCustomer } for the current admin.
|
|
6
|
+
*
|
|
7
|
+
* Powers the conditional "My customer account" entry in the admin user
|
|
8
|
+
* menu (R112). Gated on the SDK's `requireAdmin()`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { GET } from '@rovela-ai/sdk/admin/api/linked-customer'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Location API Route (single)
|
|
3
|
+
*
|
|
4
|
+
* PUT /api/admin/locations/[id] - Update a location
|
|
5
|
+
* DELETE /api/admin/locations/[id] - Delete a location
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { updateLocationHandler, deleteLocationHandler } from '@rovela-ai/sdk/admin/api'
|
|
9
|
+
|
|
10
|
+
export { updateLocationHandler as PUT, deleteLocationHandler as DELETE }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Locations API Route
|
|
3
|
+
*
|
|
4
|
+
* GET /api/admin/locations - List store locations
|
|
5
|
+
* POST /api/admin/locations - Create a location
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getLocations, createLocationHandler } from '@rovela-ai/sdk/admin/api'
|
|
9
|
+
|
|
10
|
+
export { getLocations as GET, createLocationHandler as POST }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Me Password API Route
|
|
3
|
+
*
|
|
4
|
+
* Thin re-export of the SDK handler. This route is mounted separately
|
|
5
|
+
* from `/api/admin/me` because Next.js uses the HTTP method name as the
|
|
6
|
+
* export binding — we have two PATCH handlers (one for profile, one for
|
|
7
|
+
* password), and they need distinct route files.
|
|
8
|
+
*
|
|
9
|
+
* PATCH /api/admin/me/password — change own password
|
|
10
|
+
* Body: { currentPassword, newPassword }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { PATCH_PASSWORD as PATCH } from '@rovela-ai/sdk/admin/api/me'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Me API Route
|
|
3
|
+
*
|
|
4
|
+
* Thin re-export of the SDK handlers:
|
|
5
|
+
* GET — return the caller's own profile
|
|
6
|
+
* PATCH — update own name / email
|
|
7
|
+
*
|
|
8
|
+
* The password change endpoint lives at /api/admin/me/password.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { GET, PATCH } from '@rovela-ai/sdk/admin/api/me'
|