@onexapis/cli 1.1.38 → 1.1.39
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/cli.js +384 -380
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +381 -376
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +258 -293
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +255 -289
- package/dist/index.mjs.map +1 -1
- package/dist/preview/preview-app.tsx +13 -5
- package/package.json +1 -3
- package/templates/default/AUTH_AND_PROFILE.md +167 -0
- package/templates/default/CLAUDE.md +334 -1
- package/templates/default/LAYOUT.md +195 -0
- package/templates/default/bundle-entry.ts +5 -0
- package/templates/default/esbuild.config.js +20 -0
- package/templates/default/hooks/index.ts +26 -0
- package/templates/default/hooks/use-forgot-password-form.ts +90 -0
- package/templates/default/hooks/use-login-form.ts +102 -0
- package/templates/default/hooks/use-profile-form.ts +255 -0
- package/templates/default/hooks/use-register-form.ts +154 -0
- package/templates/default/hooks/use-verify-code-form.ts +224 -0
- package/templates/default/index.ts +21 -1
- package/templates/default/pages/about.ts +2 -2
- package/templates/default/pages/forgot-password.ts +39 -0
- package/templates/default/pages/home.ts +4 -4
- package/templates/default/pages/login.ts +39 -0
- package/templates/default/pages/profile.ts +39 -0
- package/templates/default/pages/register.ts +39 -0
- package/templates/default/pages/showcase.ts +7 -7
- package/templates/default/pages/verify-code.ts +39 -0
- package/templates/default/sections/about/about.schema.ts +1 -1
- package/templates/default/sections/auth-forgot-password/auth-forgot-password-default.tsx +192 -0
- package/templates/default/sections/auth-forgot-password/auth-forgot-password.schema.ts +150 -0
- package/templates/default/sections/auth-forgot-password/index.ts +14 -0
- package/templates/default/sections/auth-login/auth-login-default.tsx +238 -0
- package/templates/default/sections/auth-login/auth-login.schema.ts +171 -0
- package/templates/default/sections/auth-login/index.ts +14 -0
- package/templates/default/sections/auth-register/auth-register-default.tsx +327 -0
- package/templates/default/sections/auth-register/auth-register.schema.ts +188 -0
- package/templates/default/sections/auth-register/index.ts +14 -0
- package/templates/default/sections/auth-verify-code/auth-verify-code-default.tsx +209 -0
- package/templates/default/sections/auth-verify-code/auth-verify-code.schema.ts +150 -0
- package/templates/default/sections/auth-verify-code/index.ts +14 -0
- package/templates/default/sections/cta/cta.schema.ts +1 -1
- package/templates/default/sections/features/features.schema.ts +1 -1
- package/templates/default/sections/footer/footer-default.tsx +214 -0
- package/templates/default/sections/footer/footer.schema.ts +170 -0
- package/templates/default/sections/footer/index.ts +14 -0
- package/templates/default/sections/gallery/gallery.schema.ts +1 -1
- package/templates/default/sections/header/header-default.tsx +322 -0
- package/templates/default/sections/header/header.schema.ts +168 -0
- package/templates/default/sections/header/index.ts +14 -0
- package/templates/default/sections/hero/hero.schema.ts +1 -1
- package/templates/default/sections/profile/index.ts +14 -0
- package/templates/default/sections/profile/profile-default.tsx +522 -0
- package/templates/default/sections/profile/profile.schema.ts +228 -0
- package/templates/default/sections/stats/stats.schema.ts +1 -1
- package/templates/default/sections/testimonials/testimonials.schema.ts +1 -1
- package/templates/default/sections-registry.ts +28 -0
- package/templates/default/theme.layout.ts +71 -2
|
@@ -455,6 +455,14 @@ function PreviewApp() {
|
|
|
455
455
|
>("disconnected");
|
|
456
456
|
const [error, setError] = useState<string | null>(null);
|
|
457
457
|
const wsRef = useRef<WebSocket | null>(null);
|
|
458
|
+
const [isEditing, setIsEditing] = useState(() => {
|
|
459
|
+
if (typeof window !== "undefined") {
|
|
460
|
+
return (
|
|
461
|
+
new URLSearchParams(window.location.search).get("editing") === "true"
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return false;
|
|
465
|
+
});
|
|
458
466
|
|
|
459
467
|
// Load theme bundle
|
|
460
468
|
const loadTheme = useCallback(async (timestamp?: number) => {
|
|
@@ -701,7 +709,7 @@ function PreviewApp() {
|
|
|
701
709
|
section={section}
|
|
702
710
|
schema={schema}
|
|
703
711
|
template={template}
|
|
704
|
-
isEditing={
|
|
712
|
+
isEditing={isEditing}
|
|
705
713
|
data={sectionData}
|
|
706
714
|
/>
|
|
707
715
|
);
|
|
@@ -712,16 +720,16 @@ function PreviewApp() {
|
|
|
712
720
|
{/* Inject theme CSS variables (Gap 2) */}
|
|
713
721
|
{themeCSS && <style dangerouslySetInnerHTML={{ __html: themeCSS }} />}
|
|
714
722
|
|
|
715
|
-
{/* Header
|
|
716
|
-
{headerSections.length > 0 && (
|
|
723
|
+
{/* Header — hidden if page config sets hideHeader: true */}
|
|
724
|
+
{headerSections.length > 0 && !currentPage.config?.hideHeader && (
|
|
717
725
|
<header>{headerSections.map(renderSection)}</header>
|
|
718
726
|
)}
|
|
719
727
|
|
|
720
728
|
{/* Page sections */}
|
|
721
729
|
<main>{pageSections.map(renderSection)}</main>
|
|
722
730
|
|
|
723
|
-
{/* Footer
|
|
724
|
-
{footerSections.length > 0 && (
|
|
731
|
+
{/* Footer — hidden if page config sets hideFooter: true */}
|
|
732
|
+
{footerSections.length > 0 && !currentPage.config?.hideFooter && (
|
|
725
733
|
<footer>{footerSections.map(renderSection)}</footer>
|
|
726
734
|
)}
|
|
727
735
|
</>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onexapis/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.39",
|
|
4
4
|
"description": "CLI tool for OneX theme development - scaffolds themes using @onexapis/core",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
"chalk": "^5.3.0",
|
|
58
58
|
"chokidar": "^4.0.0",
|
|
59
59
|
"commander": "^12.1.0",
|
|
60
|
-
"cross-spawn": "^7.0.6",
|
|
61
60
|
"dotenv": "^17.3.1",
|
|
62
61
|
"ejs": "^3.1.10",
|
|
63
62
|
"esbuild": "^0.25.0",
|
|
@@ -78,7 +77,6 @@
|
|
|
78
77
|
"devDependencies": {
|
|
79
78
|
"@types/adm-zip": "^0.5.7",
|
|
80
79
|
"@types/archiver": "^7.0.0",
|
|
81
|
-
"@types/cross-spawn": "^6.0.6",
|
|
82
80
|
"@types/ejs": "^3.1.5",
|
|
83
81
|
"@types/fs-extra": "^11.0.4",
|
|
84
82
|
"@types/inquirer": "^9.0.7",
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Authentication & Profile — Default Theme
|
|
2
|
+
|
|
3
|
+
This document covers the built-in authentication and profile sections included in the default theme template.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
When you initialize a theme with `onexthm init`, it comes with 5 auth/profile sections and their corresponding hooks ready to use:
|
|
8
|
+
|
|
9
|
+
| Section | Path | Description |
|
|
10
|
+
| -------------------- | ------------------ | -------------------------------------------- |
|
|
11
|
+
| Auth Login | `/login` | Username/email + password sign-in |
|
|
12
|
+
| Auth Register | `/register` | Email, username, password, confirm password |
|
|
13
|
+
| Auth Forgot Password | `/forgot-password` | Request verification code via email/username |
|
|
14
|
+
| Auth Verify Code | `/verify-code` | 6-digit OTP input with resend + countdown |
|
|
15
|
+
| Profile | `/profile` | View/edit profile + change password |
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Theme Section (UI only)
|
|
21
|
+
└── Theme Hook (form state, validation)
|
|
22
|
+
└── useAuth() from @onexapis/core/hooks (Zustand store)
|
|
23
|
+
└── AuthService (API calls)
|
|
24
|
+
└── ApiClient (HTTP client with token management)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Hooks
|
|
28
|
+
|
|
29
|
+
All hooks live in `hooks/` and use `useAuth` from `@onexapis/core/hooks`:
|
|
30
|
+
|
|
31
|
+
| Hook | Uses | Manages |
|
|
32
|
+
| ----------------------- | ------------------------------------------------------------- | ---------------------------------------------------- |
|
|
33
|
+
| `useLoginForm` | `useAuth().login()` | Username, password, show/hide toggle, validation |
|
|
34
|
+
| `useRegisterForm` | `useAuth().signup()` | Email, username, password, confirm, validation |
|
|
35
|
+
| `useForgotPasswordForm` | `useAuth().forgotPassword()` | Username/email input, validation |
|
|
36
|
+
| `useVerifyCodeForm` | `useAuth().verifyCode()`, `verifyCodeReset()`, `resendCode()` | 6-digit OTP, auto-focus, paste, countdown |
|
|
37
|
+
| `useProfileForm` | `useAuth().updateProfile()`, `changePassword()`, `logout()` | Profile fields, dirty tracking, change password form |
|
|
38
|
+
|
|
39
|
+
### API Endpoints
|
|
40
|
+
|
|
41
|
+
The `AuthService` in `@onexapis/core` handles all API calls:
|
|
42
|
+
|
|
43
|
+
| Action | Method | Endpoint |
|
|
44
|
+
| ------------------- | ------ | ----------------------------------- |
|
|
45
|
+
| Login | POST | `/auth/login` |
|
|
46
|
+
| Register | POST | `/auth/register` |
|
|
47
|
+
| Forgot Password | POST | `/auth/forgot` |
|
|
48
|
+
| Verify Code | POST | `/auth/confirm` |
|
|
49
|
+
| Verify Code (reset) | POST | `/auth/confirm_reset_password_code` |
|
|
50
|
+
| Resend Code | POST | `/auth/resend_code` |
|
|
51
|
+
| Reset Password | POST | `/auth/new_password` |
|
|
52
|
+
| Change Password | POST | `/auth/change_password` |
|
|
53
|
+
| Get User Info | GET | `/auth/user_info` |
|
|
54
|
+
| Update Profile | POST | `/auth/user_info` |
|
|
55
|
+
|
|
56
|
+
### Token Handling
|
|
57
|
+
|
|
58
|
+
- **IdToken** → `Authorization: Bearer {IdToken}` header
|
|
59
|
+
- **AccessToken** → `?access_token={AccessToken}` query parameter
|
|
60
|
+
- **Tokens stored** in `localStorage` under key `auth_tokens`
|
|
61
|
+
- **Signup** auto-injects `company_id` (from `initializeOnex()` config) and `is_customer: true`
|
|
62
|
+
|
|
63
|
+
## Editable Settings
|
|
64
|
+
|
|
65
|
+
All sections expose these layout settings via the editor sidebar:
|
|
66
|
+
|
|
67
|
+
| Setting | Type | Default | Description |
|
|
68
|
+
| ------------------ | ------ | --------- | ------------------------------------ |
|
|
69
|
+
| `backgroundColor` | color | `#F9FAFB` | Page background |
|
|
70
|
+
| `cardBackground` | color | `#FFFFFF` | Card/form container background |
|
|
71
|
+
| `primaryColor` | color | `#2563EB` | Buttons and links |
|
|
72
|
+
| `textColor` | color | `#111827` | Heading text color |
|
|
73
|
+
| `cardBorderRadius` | select | `2xl` | Card corner rounding (lg/xl/2xl/3xl) |
|
|
74
|
+
|
|
75
|
+
Plus all text labels, placeholders, button text, and URLs are customizable per section.
|
|
76
|
+
|
|
77
|
+
## Auth Flow
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Register → Verify Code → Login → Profile
|
|
81
|
+
↑ │
|
|
82
|
+
Forgot Password → Verify Code (reset) → Reset Password
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
1. **Register**: User fills email, username, password → `signup()` → redirects to `/verify-code?username=...`
|
|
86
|
+
2. **Verify Code**: User enters 6-digit OTP → `verifyCode()` → redirects to `/login`
|
|
87
|
+
3. **Login**: User signs in → `login()` → tokens stored → redirects to `/`
|
|
88
|
+
4. **Forgot Password**: User enters email → `forgotPassword()` → redirects to `/verify-code?mode=reset&username=...`
|
|
89
|
+
5. **Verify Code (reset)**: OTP → `verifyCodeReset()` → returns session → redirects to `/reset-password?session=...`
|
|
90
|
+
6. **Profile**: View/edit name, phone, address → `updateProfile()`. Change password → `changePassword()`
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
### Required Environment Variables
|
|
95
|
+
|
|
96
|
+
```env
|
|
97
|
+
NEXT_PUBLIC_API_URL=https://api-dev.onexeos.com
|
|
98
|
+
NEXT_PUBLIC_COMPANY_ID=your-company-id
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
These are read by `initializeOnex()` which creates the `AuthService` and registers it with the `useAuth` Zustand store.
|
|
102
|
+
|
|
103
|
+
## Testing Locally
|
|
104
|
+
|
|
105
|
+
### Preview mode (UI only)
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
onexthm dev
|
|
109
|
+
# Visit http://localhost:3456/login
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Preview with editing mode
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
onexthm dev
|
|
116
|
+
# Visit http://localhost:3456/login?editing=true
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Preview with real API (requires .env)
|
|
120
|
+
|
|
121
|
+
Create `.env` in your theme directory:
|
|
122
|
+
|
|
123
|
+
```env
|
|
124
|
+
NEXT_PUBLIC_API_URL=https://api-dev.onexeos.com
|
|
125
|
+
NEXT_PUBLIC_COMPANY_ID=your-company-id
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Then run `onexthm dev` — login/register/verify will make real API calls.
|
|
129
|
+
|
|
130
|
+
## File Structure
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
hooks/
|
|
134
|
+
index.ts
|
|
135
|
+
use-login-form.ts
|
|
136
|
+
use-register-form.ts
|
|
137
|
+
use-forgot-password-form.ts
|
|
138
|
+
use-verify-code-form.ts
|
|
139
|
+
use-profile-form.ts
|
|
140
|
+
sections/
|
|
141
|
+
auth-login/
|
|
142
|
+
auth-login-default.tsx # Login form UI
|
|
143
|
+
auth-login.schema.ts # Editable settings
|
|
144
|
+
index.ts
|
|
145
|
+
auth-register/
|
|
146
|
+
auth-register-default.tsx
|
|
147
|
+
auth-register.schema.ts
|
|
148
|
+
index.ts
|
|
149
|
+
auth-forgot-password/
|
|
150
|
+
auth-forgot-password-default.tsx
|
|
151
|
+
auth-forgot-password.schema.ts
|
|
152
|
+
index.ts
|
|
153
|
+
auth-verify-code/
|
|
154
|
+
auth-verify-code-default.tsx
|
|
155
|
+
auth-verify-code.schema.ts
|
|
156
|
+
index.ts
|
|
157
|
+
profile/
|
|
158
|
+
profile-default.tsx
|
|
159
|
+
profile.schema.ts
|
|
160
|
+
index.ts
|
|
161
|
+
pages/
|
|
162
|
+
login.ts
|
|
163
|
+
register.ts
|
|
164
|
+
forgot-password.ts
|
|
165
|
+
verify-code.ts
|
|
166
|
+
profile.ts
|
|
167
|
+
```
|
|
@@ -369,7 +369,7 @@ import {
|
|
|
369
369
|
| **State** (3) | `useCart`, `useAuth`, `useOrders` |
|
|
370
370
|
| **Checkout** (8) | `useCheckout`, `usePayment`, `useOrderLookup`, `useOrderStatus`, `useOrderSuccess`, `useOrderSummary`, `useFinance`, `saveBuyNowItem` |
|
|
371
371
|
| **Products** (3) | `useSearchProducts`, `useAddToCart`, `useProductDetail` |
|
|
372
|
-
| **Theme** (
|
|
372
|
+
| **Theme** (9) | `useDesignSystem`, `usePageBackground`, `useCommerceData`, `useThemeMode`, `useLocale`, `useViewport`, `usePageData`, `useWebsiteSettings`, `useMotion` |
|
|
373
373
|
| **Utilities** (7) | `useDebounce`, `useMediaQuery`, `useIsMobile`, `useIsTablet`, `useIsDesktop`, `useContactForm`, `useCopyToClipboard`, `useFormatPrice`, `formatVndPrice` |
|
|
374
374
|
| **Animation** (1) | `useFlyToCart` |
|
|
375
375
|
| **Server** (4) | `fetchProducts`, `fetchBlogs`, `fetchSettings`, `prefetchSectionData` |
|
|
@@ -408,6 +408,198 @@ const { formatPrice } = useFormatPrice();
|
|
|
408
408
|
formatPrice(1250000); // "1.250.000đ"
|
|
409
409
|
```
|
|
410
410
|
|
|
411
|
+
### Orders & Payment Hooks
|
|
412
|
+
|
|
413
|
+
These hooks cover the full order lifecycle: creating orders, looking up orders, processing payments, and displaying order history.
|
|
414
|
+
|
|
415
|
+
#### `useOrders` — Order state management (Zustand)
|
|
416
|
+
|
|
417
|
+
```tsx
|
|
418
|
+
import { useOrders } from "@onexapis/core/hooks";
|
|
419
|
+
|
|
420
|
+
const {
|
|
421
|
+
orders, // Order[] — list of orders
|
|
422
|
+
currentOrder, // Order | null — single order detail
|
|
423
|
+
isLoading,
|
|
424
|
+
error,
|
|
425
|
+
total,
|
|
426
|
+
totalPages,
|
|
427
|
+
currentPage,
|
|
428
|
+
|
|
429
|
+
// Actions
|
|
430
|
+
createOrder, // (data: CreateOrderData) => Promise<Order>
|
|
431
|
+
createPrivateOrder, // (data: CreateOrderData) => Promise<Order> — requires auth
|
|
432
|
+
fetchOrders, // (params?) => Promise<void> — public order list
|
|
433
|
+
fetchOrderHistory, // (params?) => Promise<void> — authenticated user's orders
|
|
434
|
+
fetchOrderById, // (orderId: string) => Promise<void>
|
|
435
|
+
fetchOrderByNumber, // (orderNumber: string) => Promise<void>
|
|
436
|
+
updateOrderStatus, // (orderId, status) => Promise<void>
|
|
437
|
+
cancelOrder, // (orderId) => Promise<void>
|
|
438
|
+
payOrder, // (orderId, paymentData?) => Promise<Order> — returns updated order
|
|
439
|
+
clearError,
|
|
440
|
+
clearCurrentOrder,
|
|
441
|
+
} = useOrders();
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**`payOrder` returns the updated `Order`** — always check `order.status` and `order.payment_status` before navigating to success. Do NOT blindly redirect after calling `payOrder`.
|
|
445
|
+
|
|
446
|
+
#### `useOrderLookup` — Search/track order by number or ID
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
import { useOrderLookup } from "@onexapis/core/hooks";
|
|
450
|
+
|
|
451
|
+
// Basic — manual search via input
|
|
452
|
+
const lookup = useOrderLookup();
|
|
453
|
+
|
|
454
|
+
// With auto-fetch from URL route param (for dynamic pages like /order-lookup/[orderId])
|
|
455
|
+
const routeParams = (data?.routeParams || {}) as Record<string, string>;
|
|
456
|
+
const lookup = useOrderLookup({
|
|
457
|
+
initialOrderId: routeParams.orderId, // auto-fetches on mount
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// lookup.orderNumber — input value (string)
|
|
461
|
+
// lookup.setOrderNumber — update input
|
|
462
|
+
// lookup.handleTrackOrder — search (auto-detects UUID vs order number)
|
|
463
|
+
// lookup.handleClear — reset search
|
|
464
|
+
// lookup.order — Order | null (result)
|
|
465
|
+
// lookup.isSearching — boolean
|
|
466
|
+
// lookup.errorMessage — string | null
|
|
467
|
+
// lookup.getStatusLabel — (status: string) => Vietnamese label
|
|
468
|
+
// lookup.formatCurrency — (amount: number) => "1.250.000đ"
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Dynamic page setup** for `/order-lookup/[orderId]`:
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
// pages/order-lookup.ts
|
|
475
|
+
export const orderLookupPageConfig = {
|
|
476
|
+
handle: "order-lookup",
|
|
477
|
+
path: "/order-lookup/[orderId]",
|
|
478
|
+
isDynamic: true,
|
|
479
|
+
dynamicSegments: ["orderId"],
|
|
480
|
+
type: "custom",
|
|
481
|
+
// ...sections
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
#### `usePayment` — QR code payment with bank transfer
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
import { usePayment } from "@onexapis/core/hooks";
|
|
489
|
+
|
|
490
|
+
const payment = usePayment({
|
|
491
|
+
successRedirectUrl: "/order-success", // default
|
|
492
|
+
checkoutRedirectUrl: "/checkout", // default — redirect if no order info
|
|
493
|
+
isEditing: false,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// payment.orderId, payment.orderNumber, payment.total
|
|
497
|
+
// payment.qrCodeImage — data URI for QR code
|
|
498
|
+
// payment.qrNote — transfer note (e.g. "DH4X7K2M")
|
|
499
|
+
// payment.bankTransferData — { accountNumber, accountName, bankName }
|
|
500
|
+
// payment.isLoading, payment.isProcessing, payment.error
|
|
501
|
+
// payment.handleConfirmPayment — calls payOrder API, checks response, then redirects
|
|
502
|
+
// payment.handleCancel — clears order info, redirects to checkout
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**`handleConfirmPayment` validates the API response:**
|
|
506
|
+
|
|
507
|
+
- Checks `order.status` — blocks redirect if `failed`, `cancelled`, or `refunded`
|
|
508
|
+
- Checks `order.payment_status` — blocks redirect if `failed`
|
|
509
|
+
- Only navigates to success page when payment is valid
|
|
510
|
+
- Shows error message from API on failure
|
|
511
|
+
|
|
512
|
+
#### `useOrderSuccess` — Post-purchase success page
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
import { useOrderSuccess } from "@onexapis/core/hooks";
|
|
516
|
+
|
|
517
|
+
const success = useOrderSuccess();
|
|
518
|
+
// success.orderId, success.orderNumber, success.displayOrderNumber
|
|
519
|
+
// success.trackOrderUrl — link to order lookup page
|
|
520
|
+
// success.hasOrder — boolean (false if no order in URL params)
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
#### `useOrderSummary` — Calculate order totals
|
|
524
|
+
|
|
525
|
+
```tsx
|
|
526
|
+
import { useOrderSummary, calculateOrderTotal } from "@onexapis/core/hooks";
|
|
527
|
+
|
|
528
|
+
const summary = useOrderSummary({
|
|
529
|
+
vatRate: 0.1,
|
|
530
|
+
shippingFee: 30000,
|
|
531
|
+
discount: 0,
|
|
532
|
+
freeShippingThreshold: 500000,
|
|
533
|
+
});
|
|
534
|
+
// summary.subtotal, summary.discount, summary.shipping, summary.vat, summary.total
|
|
535
|
+
// summary.formatPrice(amount)
|
|
536
|
+
|
|
537
|
+
// Standalone helper (no hook needed)
|
|
538
|
+
const total = calculateOrderTotal(items, { vatRate: 0.1, shippingFee: 30000 });
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
#### `useOrderStatus` — Status label translation
|
|
542
|
+
|
|
543
|
+
```tsx
|
|
544
|
+
import { useOrderStatus, getOrderStatusLabel } from "@onexapis/core/hooks";
|
|
545
|
+
|
|
546
|
+
const { getLabel, getPaymentLabel } = useOrderStatus();
|
|
547
|
+
getLabel("shipping"); // "Đang giao hàng"
|
|
548
|
+
getLabel("completed"); // "Hoàn thành"
|
|
549
|
+
getPaymentLabel("paid"); // "Đã thanh toán"
|
|
550
|
+
|
|
551
|
+
// Or use the standalone function
|
|
552
|
+
getOrderStatusLabel("pending"); // "Chờ xử lý"
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
#### `useOrderListPage` — Order history page
|
|
556
|
+
|
|
557
|
+
```tsx
|
|
558
|
+
import { useOrderListPage } from "@onexapis/core/hooks";
|
|
559
|
+
|
|
560
|
+
const orderList = useOrderListPage({ autoFetch: true, collapsedItemCount: 2 });
|
|
561
|
+
// orderList.orders — Order[]
|
|
562
|
+
// orderList.isLoading, orderList.error
|
|
563
|
+
// orderList.toggleOrder — expand/collapse order items
|
|
564
|
+
// orderList.refresh — re-fetch orders
|
|
565
|
+
// orderList.getDisplayItems — get visible items (respects collapsed state)
|
|
566
|
+
// orderList.hasMoreItems — check if order has hidden items
|
|
567
|
+
// orderList.formatCurrency, orderList.formatDate
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
#### Order Type Reference
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
interface Order {
|
|
574
|
+
id: string;
|
|
575
|
+
order_number: string;
|
|
576
|
+
status: string; // pending | confirmed | processing | packing | shipping | completed | cancelled | failed | refunded
|
|
577
|
+
payment_status?: string; // paid | unpaid | failed
|
|
578
|
+
payment_method?: string;
|
|
579
|
+
total: number;
|
|
580
|
+
sub_total?: number;
|
|
581
|
+
tax?: number;
|
|
582
|
+
shipping_fee?: number;
|
|
583
|
+
discount?: number;
|
|
584
|
+
order_line_items?: OrderLineItem[];
|
|
585
|
+
shipping_address?: OrderAddress;
|
|
586
|
+
note?: string;
|
|
587
|
+
created_at: string;
|
|
588
|
+
updated_at?: string;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
interface OrderLineItem {
|
|
592
|
+
name: string;
|
|
593
|
+
unit_price: number;
|
|
594
|
+
quantity: number;
|
|
595
|
+
product_id: string;
|
|
596
|
+
variant_name?: string;
|
|
597
|
+
image_url?: string;
|
|
598
|
+
sku?: string;
|
|
599
|
+
location?: { id: string; name: string };
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
411
603
|
## Color System
|
|
412
604
|
|
|
413
605
|
Colors in OneX themes work with **two approaches** (both are valid):
|
|
@@ -836,6 +1028,147 @@ export function MySection({ section, isEditing }: SectionComponentProps) {
|
|
|
836
1028
|
}
|
|
837
1029
|
```
|
|
838
1030
|
|
|
1031
|
+
## Page Background System
|
|
1032
|
+
|
|
1033
|
+
Every page has a background that comes from the theme by default, overridable per-page. This is a **page-level** feature (not section-level) — it wraps all sections on the page.
|
|
1034
|
+
|
|
1035
|
+
### How it works
|
|
1036
|
+
|
|
1037
|
+
Background is resolved with priority:
|
|
1038
|
+
|
|
1039
|
+
1. **Per-page override** (`PageConfig.background`) — set via editor or page config
|
|
1040
|
+
2. **Theme default** (`ThemeDesignSystem.pageBackground`) — set in `theme.layout.ts`
|
|
1041
|
+
3. **Fallback** — solid white `#FFFFFF`
|
|
1042
|
+
|
|
1043
|
+
### Setting the theme default
|
|
1044
|
+
|
|
1045
|
+
In `theme.layout.ts`, add `pageBackground` inside `designSystem`:
|
|
1046
|
+
|
|
1047
|
+
```typescript
|
|
1048
|
+
export const layoutConfig: ThemeLayoutConfig = {
|
|
1049
|
+
// ...headerSections, footerSections...
|
|
1050
|
+
designSystem: {
|
|
1051
|
+
colors: {
|
|
1052
|
+
primaryColor: "#3B82F6",
|
|
1053
|
+
secondaryColor: "#8B5CF6",
|
|
1054
|
+
colorMode: "light",
|
|
1055
|
+
},
|
|
1056
|
+
typography: { headingFont: "system-ui", bodyFont: "system-ui" },
|
|
1057
|
+
layout: { spacing: "comfortable" },
|
|
1058
|
+
pageBackground: {
|
|
1059
|
+
type: "solid", // "solid" | "gradient" | "image" | "pattern" | "none"
|
|
1060
|
+
color: "#FFFFFF", // Base background color
|
|
1061
|
+
},
|
|
1062
|
+
},
|
|
1063
|
+
};
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
### Background types
|
|
1067
|
+
|
|
1068
|
+
| Type | Fields | Example |
|
|
1069
|
+
| ---------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
1070
|
+
| `solid` | `color` | `{ type: "solid", color: "#FFF8F0" }` |
|
|
1071
|
+
| `gradient` | `gradient`, `color` (fallback) | `{ type: "gradient", gradient: "linear-gradient(135deg, #667eea, #764ba2)" }` |
|
|
1072
|
+
| `image` | `image`, `imageSize`, `imagePosition`, `imageFixed`, `color` (fallback) | `{ type: "image", image: "/bg.jpg", imageSize: "cover", imageFixed: true }` |
|
|
1073
|
+
| `pattern` | `pattern`, `color`, `opacity`, `overlayColor` | `{ type: "pattern", pattern: "dots", color: "#FAFAFA", opacity: 0.05 }` |
|
|
1074
|
+
| `none` | — | `{ type: "none" }` (transparent, no wrapper) |
|
|
1075
|
+
|
|
1076
|
+
### Built-in CSS patterns
|
|
1077
|
+
|
|
1078
|
+
These work out of the box with `type: "pattern"`:
|
|
1079
|
+
|
|
1080
|
+
- `dots` — dot grid
|
|
1081
|
+
- `grid` — line grid
|
|
1082
|
+
- `diagonal-lines` — 45-degree stripes
|
|
1083
|
+
- `cross-dots` — offset dot grid
|
|
1084
|
+
- `noise` — fractal noise texture
|
|
1085
|
+
|
|
1086
|
+
### Custom animated patterns
|
|
1087
|
+
|
|
1088
|
+
Themes can provide custom pattern renderers (e.g., SVG-based animated backgrounds). Export the component from `bundle-entry.ts`:
|
|
1089
|
+
|
|
1090
|
+
```typescript
|
|
1091
|
+
// In bundle-entry.ts
|
|
1092
|
+
export { SenBackground } from "./assets/sen-background";
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
The storefront will pick up exported `*Background` components and match them to pattern names. For example, `SenBackground` maps to `pattern: "sen"`.
|
|
1096
|
+
|
|
1097
|
+
Custom pattern components receive these props:
|
|
1098
|
+
|
|
1099
|
+
```typescript
|
|
1100
|
+
interface PatternProps {
|
|
1101
|
+
opacity?: number; // Pattern opacity (default: 0.05)
|
|
1102
|
+
color?: string; // Overlay color
|
|
1103
|
+
className?: string; // Additional CSS classes
|
|
1104
|
+
}
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
### Per-page override
|
|
1108
|
+
|
|
1109
|
+
In a page config (e.g., `pages/about.ts`), set `background`:
|
|
1110
|
+
|
|
1111
|
+
```typescript
|
|
1112
|
+
export const aboutPageConfig = {
|
|
1113
|
+
title: "About",
|
|
1114
|
+
handle: "about",
|
|
1115
|
+
// ...other config...
|
|
1116
|
+
background: {
|
|
1117
|
+
type: "gradient",
|
|
1118
|
+
gradient: "linear-gradient(180deg, #EEF2FF 0%, #FFFFFF 50%)",
|
|
1119
|
+
},
|
|
1120
|
+
};
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
### Accessing in sections via hook
|
|
1124
|
+
|
|
1125
|
+
Use `usePageBackground()` to read the resolved background in section components:
|
|
1126
|
+
|
|
1127
|
+
```tsx
|
|
1128
|
+
import { usePageBackground } from "@onexapis/core/hooks";
|
|
1129
|
+
|
|
1130
|
+
export function MySection({ section }: SectionComponentProps) {
|
|
1131
|
+
const { config, styles, hasPattern, pattern, patternOpacity } =
|
|
1132
|
+
usePageBackground();
|
|
1133
|
+
// config.type — "solid" | "gradient" | "image" | "pattern" | "none"
|
|
1134
|
+
// config.color — base color
|
|
1135
|
+
// styles — React.CSSProperties derived from config
|
|
1136
|
+
// hasPattern — boolean, true if type === "pattern" && pattern is set
|
|
1137
|
+
// pattern — pattern name (e.g., "sen", "dots")
|
|
1138
|
+
// patternOpacity — number (0-1)
|
|
1139
|
+
}
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
### PageBackgroundConfig type reference
|
|
1143
|
+
|
|
1144
|
+
```typescript
|
|
1145
|
+
interface PageBackgroundConfig {
|
|
1146
|
+
type: "solid" | "gradient" | "image" | "pattern" | "none";
|
|
1147
|
+
color?: string; // Base background color (hex/CSS)
|
|
1148
|
+
gradient?: string; // CSS gradient value
|
|
1149
|
+
image?: string; // Background image URL
|
|
1150
|
+
imageSize?: "cover" | "contain" | "auto";
|
|
1151
|
+
imagePosition?: string; // CSS background-position (default: "center")
|
|
1152
|
+
imageFixed?: boolean; // Fixed background attachment
|
|
1153
|
+
pattern?: string; // Pattern identifier
|
|
1154
|
+
opacity?: number; // Pattern/overlay opacity (0-1, default: 0.05)
|
|
1155
|
+
overlayColor?: string; // Pattern overlay color
|
|
1156
|
+
className?: string; // Additional CSS classes
|
|
1157
|
+
}
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
### CSS variables
|
|
1161
|
+
|
|
1162
|
+
The storefront injects these CSS variables when a page background is configured:
|
|
1163
|
+
|
|
1164
|
+
- `--page-bg-color` — base color
|
|
1165
|
+
- `--page-bg-gradient` — gradient value
|
|
1166
|
+
- `--page-bg-pattern` — pattern name
|
|
1167
|
+
- `--page-bg-pattern-opacity` — opacity value
|
|
1168
|
+
- `--page-bg-overlay-color` — overlay color
|
|
1169
|
+
|
|
1170
|
+
Use these in custom CSS if needed: `background-color: var(--page-bg-color);`
|
|
1171
|
+
|
|
839
1172
|
## Dark/Light Mode
|
|
840
1173
|
|
|
841
1174
|
Themes support light, dark, and system color modes via `useThemeMode()`:
|