@onexapis/cli 1.1.31 → 1.1.34
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 +19 -3
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +19 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -2
- package/dist/index.mjs.map +1 -1
- package/dist/preview/preview-app.tsx +31 -22
- package/package.json +2 -2
- package/templates/default/CLAUDE.md +82 -59
|
@@ -20,27 +20,20 @@ import * as coreProducts from "@onexapis/core/products";
|
|
|
20
20
|
import * as coreOrders from "@onexapis/core/orders";
|
|
21
21
|
import * as coreBlog from "@onexapis/core/blog";
|
|
22
22
|
import * as coreFinance from "@onexapis/core/finance";
|
|
23
|
+
import { initializeOnex } from "@onexapis/core";
|
|
24
|
+
import * as coreHooksAll from "@onexapis/core/hooks";
|
|
23
25
|
import * as coreInternal from "@onexapis/core/internal";
|
|
24
26
|
import * as coreTypes from "@onexapis/core/types";
|
|
25
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
CartProvider,
|
|
29
|
+
FlyToCartProvider,
|
|
30
|
+
PageDataProvider,
|
|
31
|
+
} from "@onexapis/core/contexts";
|
|
26
32
|
import { LocaleProvider } from "@onexapis/core/contexts";
|
|
27
33
|
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
const coreHooks = {
|
|
32
|
-
...coreCommerceHooks,
|
|
33
|
-
};
|
|
34
|
-
// Add context/state hooks individually (avoid spreading full modules)
|
|
35
|
-
for (const mod of [coreContexts, coreAuth, coreOrders, coreCart] as any[]) {
|
|
36
|
-
if (mod && typeof mod === "object") {
|
|
37
|
-
for (const [k, v] of Object.entries(mod)) {
|
|
38
|
-
if (typeof v === "function" && k.startsWith("use") && !(k in coreHooks)) {
|
|
39
|
-
(coreHooks as any)[k] = v;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
34
|
+
// @onexapis/core/hooks is the single source of truth — no manual composition.
|
|
35
|
+
// coreHooksAll re-exports all hooks (commerce, cart, auth, checkout, payment, etc.)
|
|
36
|
+
const coreHooks = coreHooksAll;
|
|
44
37
|
|
|
45
38
|
// Set React globals
|
|
46
39
|
(globalThis as any).__ONEX_REACT__ = React;
|
|
@@ -100,6 +93,12 @@ try {
|
|
|
100
93
|
if (apiUrl && companyId && CommerceClient && setCommerceClient) {
|
|
101
94
|
// Real API connection — preview will show actual products/blogs
|
|
102
95
|
setCommerceClient(new CommerceClient({ apiUrl, companyId }));
|
|
96
|
+
// Initialize all services (orders, finance, auth, etc.) so checkout/payment hooks work
|
|
97
|
+
try {
|
|
98
|
+
initializeOnex({ apiUrl, companyId });
|
|
99
|
+
} catch {
|
|
100
|
+
// May fail if already initialized — safe to ignore
|
|
101
|
+
}
|
|
103
102
|
} else if (setCommerceClient) {
|
|
104
103
|
// No API config — use stub that returns empty data
|
|
105
104
|
const emptyPagination = {
|
|
@@ -342,9 +341,12 @@ function discoverPageConfigs(
|
|
|
342
341
|
}
|
|
343
342
|
|
|
344
343
|
// Sort: "home" page first
|
|
344
|
+
// Sort: home page first (by handle, not type — multiple pages can have type "home")
|
|
345
345
|
pages.sort((a, b) => {
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
const aIsHome = a.config.handle === "home" || a.key === "homePageConfig";
|
|
347
|
+
const bIsHome = b.config.handle === "home" || b.key === "homePageConfig";
|
|
348
|
+
if (aIsHome && !bIsHome) return -1;
|
|
349
|
+
if (bIsHome && !aIsHome) return 1;
|
|
348
350
|
return a.label.localeCompare(b.label);
|
|
349
351
|
});
|
|
350
352
|
|
|
@@ -384,7 +386,10 @@ const PREVIEW_SUPPORTED_LOCALES = ["vi", "en"];
|
|
|
384
386
|
* "/vi/san-pham/slug" → { locale: "vi", rest: "/san-pham/slug" }
|
|
385
387
|
* "/products/slug" → { locale: "en", rest: "/products/slug" }
|
|
386
388
|
*/
|
|
387
|
-
function extractLocaleFromUrl(pathname: string): {
|
|
389
|
+
function extractLocaleFromUrl(pathname: string): {
|
|
390
|
+
locale: string;
|
|
391
|
+
rest: string;
|
|
392
|
+
} {
|
|
388
393
|
const parts = pathname.replace(/^\/+/, "").split("/");
|
|
389
394
|
if (parts[0] && PREVIEW_SUPPORTED_LOCALES.includes(parts[0])) {
|
|
390
395
|
return { locale: parts[0], rest: "/" + parts.slice(1).join("/") };
|
|
@@ -427,7 +432,9 @@ function getInitialPageFromURL(
|
|
|
427
432
|
if (!effectivePath) continue;
|
|
428
433
|
|
|
429
434
|
// Static locale path match (e.g., /gioi-thieu matches localizedPaths.vi: "/gioi-thieu")
|
|
430
|
-
const normalizedEffective = effectivePath
|
|
435
|
+
const normalizedEffective = effectivePath
|
|
436
|
+
.replace(/^\/+|\/+$/g, "")
|
|
437
|
+
.toLowerCase();
|
|
431
438
|
if (normalizedEffective === pathname) return i;
|
|
432
439
|
|
|
433
440
|
// Dynamic locale path match (e.g., /san-pham/my-slug matches localizedPaths.vi: "/san-pham/[slug]")
|
|
@@ -741,7 +748,9 @@ root.render(
|
|
|
741
748
|
<LocaleProvider locale={_rootLocale as any}>
|
|
742
749
|
<CartProvider>
|
|
743
750
|
<FlyToCartProvider>
|
|
744
|
-
<
|
|
751
|
+
<PageDataProvider data={{}}>
|
|
752
|
+
<PreviewApp />
|
|
753
|
+
</PageDataProvider>
|
|
745
754
|
</FlyToCartProvider>
|
|
746
755
|
</CartProvider>
|
|
747
756
|
</LocaleProvider>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onexapis/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.34",
|
|
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",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@aws-sdk/client-s3": "^3.470.0",
|
|
53
|
-
"@onexapis/core": "^1.0.
|
|
53
|
+
"@onexapis/core": "^1.0.6",
|
|
54
54
|
"@tanstack/react-query": "^5.90.16",
|
|
55
55
|
"adm-zip": "^0.5.16",
|
|
56
56
|
"archiver": "^7.0.1",
|
|
@@ -348,69 +348,64 @@ export const mySchema: SectionSchema = {
|
|
|
348
348
|
| `url` | URL input | Link href |
|
|
349
349
|
| `array` / `repeater` | List of repeating items | Feature list, social links |
|
|
350
350
|
|
|
351
|
-
## Available Hooks
|
|
351
|
+
## Available Hooks (44 hooks)
|
|
352
352
|
|
|
353
|
-
|
|
353
|
+
All hooks import from `@onexapis/core/hooks`. Use `onexthm_list_hooks` MCP tool for full API details.
|
|
354
354
|
|
|
355
355
|
```tsx
|
|
356
356
|
import {
|
|
357
357
|
useProducts,
|
|
358
358
|
useCart,
|
|
359
|
-
|
|
359
|
+
useCheckout,
|
|
360
360
|
useDesignSystem,
|
|
361
361
|
} from "@onexapis/core/hooks";
|
|
362
362
|
```
|
|
363
363
|
|
|
364
|
-
###
|
|
364
|
+
### Quick Reference
|
|
365
|
+
|
|
366
|
+
| Category | Hooks |
|
|
367
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
368
|
+
| **Commerce** (11) | `useProducts`, `useProductBySlug`, `useProductById`, `useProductCategories`, `useBlogs`, `useBlogBySlug`, `useBlogById`, `useBlogCategories`, `useSettings`, `useProductListing`, `useBlogListing` |
|
|
369
|
+
| **State** (3) | `useCart`, `useAuth`, `useOrders` |
|
|
370
|
+
| **Checkout** (8) | `useCheckout`, `usePayment`, `useOrderLookup`, `useOrderStatus`, `useOrderSuccess`, `useOrderSummary`, `useFinance`, `saveBuyNowItem` |
|
|
371
|
+
| **Products** (3) | `useSearchProducts`, `useAddToCart`, `useProductDetail` |
|
|
372
|
+
| **Theme** (8) | `useDesignSystem`, `useCommerceData`, `useThemeMode`, `useLocale`, `useViewport`, `usePageData`, `useWebsiteSettings`, `useMotion` |
|
|
373
|
+
| **Utilities** (7) | `useDebounce`, `useMediaQuery`, `useIsMobile`, `useIsTablet`, `useIsDesktop`, `useContactForm`, `useCopyToClipboard`, `useFormatPrice`, `formatVndPrice` |
|
|
374
|
+
| **Animation** (1) | `useFlyToCart` |
|
|
375
|
+
| **Server** (4) | `fetchProducts`, `fetchBlogs`, `fetchSettings`, `prefetchSectionData` |
|
|
376
|
+
|
|
377
|
+
### Most Used Patterns
|
|
365
378
|
|
|
366
379
|
```tsx
|
|
367
|
-
//
|
|
368
|
-
const { data, isLoading } = useProducts({ limit: 8
|
|
380
|
+
// Fetch products
|
|
381
|
+
const { data, isLoading } = useProducts({ limit: 8 });
|
|
369
382
|
const products = data?.data ?? [];
|
|
370
383
|
|
|
371
|
-
//
|
|
372
|
-
const {
|
|
384
|
+
// Cart
|
|
385
|
+
const { items, addItem, itemCount, subtotal } = useCart();
|
|
373
386
|
|
|
374
|
-
//
|
|
375
|
-
const {
|
|
376
|
-
const blogs = data?.data ?? [];
|
|
387
|
+
// Auth
|
|
388
|
+
const { user, isAuthenticated, login, logout } = useAuth();
|
|
377
389
|
|
|
378
|
-
//
|
|
379
|
-
const {
|
|
380
|
-
|
|
390
|
+
// Checkout (full form)
|
|
391
|
+
const checkout = useCheckout({ paymentRedirectUrl: "/payment" });
|
|
392
|
+
// checkout.fullName, checkout.handleCheckout, checkout.errors
|
|
381
393
|
|
|
382
|
-
|
|
394
|
+
// Product detail with buy now
|
|
395
|
+
const detail = useProductDetail({ slug: routeParams.slug });
|
|
396
|
+
// detail.product, detail.handleAddToCart, detail.handleBuyNow
|
|
383
397
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// Authentication
|
|
389
|
-
const auth = useAuth();
|
|
390
|
-
await auth.login(email, password);
|
|
391
|
-
await auth.signup({ username, email, password, name });
|
|
392
|
-
auth.logout();
|
|
393
|
-
await auth.forgotPassword(email);
|
|
394
|
-
|
|
395
|
-
// Orders
|
|
396
|
-
const { createOrder, getOrders } = useOrders();
|
|
397
|
-
```
|
|
398
|
+
// Search with debounce
|
|
399
|
+
const search = useSearchProducts({ debounceMs: 300 });
|
|
400
|
+
// search.query, search.setQuery, search.results
|
|
398
401
|
|
|
399
|
-
|
|
402
|
+
// Responsive
|
|
403
|
+
const isMobile = useIsMobile();
|
|
404
|
+
const isDesktop = useIsDesktop();
|
|
400
405
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// colors.primary, colors.secondary, colors.background, etc.
|
|
405
|
-
|
|
406
|
-
// Server-side data from SectionComponentProps.data
|
|
407
|
-
const { products, blogs, settings, company } = useCommerceData(data);
|
|
408
|
-
|
|
409
|
-
// Other contexts
|
|
410
|
-
const { mode, isDark } = useThemeMode();
|
|
411
|
-
const { locale, setLocale } = useLocale();
|
|
412
|
-
const { isMobile, isDesktop } = useViewport();
|
|
413
|
-
const motionTokens = useMotion();
|
|
406
|
+
// Price formatting
|
|
407
|
+
const { formatPrice } = useFormatPrice();
|
|
408
|
+
formatPrice(1250000); // "1.250.000đ"
|
|
414
409
|
```
|
|
415
410
|
|
|
416
411
|
## Color System
|
|
@@ -605,12 +600,15 @@ Dynamic pages use URL parameters like `/products/[slug]` to render detail views.
|
|
|
605
600
|
// pages/product-detail.ts
|
|
606
601
|
import type { PageConfig } from "@onexapis/core/types";
|
|
607
602
|
|
|
608
|
-
export const productDetailPageConfig: Omit<
|
|
603
|
+
export const productDetailPageConfig: Omit<
|
|
604
|
+
PageConfig,
|
|
605
|
+
"id" | "createdAt" | "updatedAt"
|
|
606
|
+
> = {
|
|
609
607
|
title: "Product Detail",
|
|
610
608
|
handle: "product-detail",
|
|
611
|
-
path: "/products/[slug]",
|
|
612
|
-
isDynamic: true,
|
|
613
|
-
dynamicSegments: ["slug"],
|
|
609
|
+
path: "/products/[slug]", // Dynamic segment in brackets
|
|
610
|
+
isDynamic: true, // Flag as dynamic
|
|
611
|
+
dynamicSegments: ["slug"], // Parameter names
|
|
614
612
|
type: "product",
|
|
615
613
|
renderMode: "sections",
|
|
616
614
|
themeId: "my-theme",
|
|
@@ -633,6 +631,7 @@ export default productDetailPageConfig;
|
|
|
633
631
|
```
|
|
634
632
|
|
|
635
633
|
Export in `bundle-entry.ts`:
|
|
634
|
+
|
|
636
635
|
```typescript
|
|
637
636
|
export { default as productDetailPageConfig } from "./pages/product-detail";
|
|
638
637
|
```
|
|
@@ -642,9 +641,14 @@ export { default as productDetailPageConfig } from "./pages/product-detail";
|
|
|
642
641
|
Sections receive `data.routeParams` with the extracted URL parameters:
|
|
643
642
|
|
|
644
643
|
```tsx
|
|
645
|
-
export function ProductDetail({
|
|
644
|
+
export function ProductDetail({
|
|
645
|
+
section,
|
|
646
|
+
schema,
|
|
647
|
+
isEditing,
|
|
648
|
+
data,
|
|
649
|
+
}: SectionComponentProps) {
|
|
646
650
|
const routeParams = (data?.routeParams || {}) as Record<string, string>;
|
|
647
|
-
const slug = routeParams.slug || "";
|
|
651
|
+
const slug = routeParams.slug || ""; // "blue-shirt" from /products/blue-shirt
|
|
648
652
|
|
|
649
653
|
const { data: product, isLoading } = useProductBySlug(slug, !!slug);
|
|
650
654
|
// ...
|
|
@@ -658,12 +662,13 @@ Use `localizedPaths` to define different URL slugs per locale:
|
|
|
658
662
|
```typescript
|
|
659
663
|
export const productDetailPageConfig = {
|
|
660
664
|
handle: "product-detail",
|
|
661
|
-
path: "/products/[slug]",
|
|
665
|
+
path: "/products/[slug]", // Default/fallback
|
|
662
666
|
isDynamic: true,
|
|
663
667
|
dynamicSegments: ["slug"],
|
|
664
|
-
localizedPaths: {
|
|
665
|
-
|
|
666
|
-
|
|
668
|
+
localizedPaths: {
|
|
669
|
+
// Locale-specific paths (typed by Locale enum)
|
|
670
|
+
vi: "/san-pham/[slug]", // /vi/san-pham/blue-shirt
|
|
671
|
+
en: "/products/[slug]", // /en/products/blue-shirt
|
|
667
672
|
},
|
|
668
673
|
type: "product",
|
|
669
674
|
// ...
|
|
@@ -673,8 +678,9 @@ export const productDetailPageConfig = {
|
|
|
673
678
|
The `Locale` type (`"vi" | "en"`) is from `@onexapis/core/types` — gives autocomplete.
|
|
674
679
|
|
|
675
680
|
Sections also receive `data.locale` to know the current locale:
|
|
681
|
+
|
|
676
682
|
```tsx
|
|
677
|
-
const locale = data?.locale as string;
|
|
683
|
+
const locale = data?.locale as string; // "vi" or "en"
|
|
678
684
|
```
|
|
679
685
|
|
|
680
686
|
### Key conventions
|
|
@@ -1010,7 +1016,15 @@ The cart system uses `useCart()` for state and a flexible fly-to-cart animation
|
|
|
1010
1016
|
```tsx
|
|
1011
1017
|
import { useCart } from "@onexapis/core/hooks";
|
|
1012
1018
|
|
|
1013
|
-
const {
|
|
1019
|
+
const {
|
|
1020
|
+
items,
|
|
1021
|
+
addItem,
|
|
1022
|
+
removeItem,
|
|
1023
|
+
updateQuantity,
|
|
1024
|
+
clearCart,
|
|
1025
|
+
itemCount,
|
|
1026
|
+
subtotal,
|
|
1027
|
+
} = useCart();
|
|
1014
1028
|
|
|
1015
1029
|
// Add to cart
|
|
1016
1030
|
addItem({
|
|
@@ -1033,11 +1047,11 @@ When a user clicks "Add to Cart", the product thumbnail flies from the button to
|
|
|
1033
1047
|
<div data-fly-to-cart-target className="my-cart-icon">
|
|
1034
1048
|
<ShoppingCartIcon />
|
|
1035
1049
|
<span>{itemCount}</span>
|
|
1036
|
-
</div
|
|
1050
|
+
</div>;
|
|
1037
1051
|
|
|
1038
1052
|
// Option B: Use CartIcon convenience component (auto-adds data-fly-to-cart-target)
|
|
1039
1053
|
import { CartIcon } from "@onexapis/core/components";
|
|
1040
|
-
<CartIcon count={itemCount} onClick={() => openCartDrawer()}
|
|
1054
|
+
<CartIcon count={itemCount} onClick={() => openCartDrawer()} />;
|
|
1041
1055
|
```
|
|
1042
1056
|
|
|
1043
1057
|
**Step 2: ProductCard triggers animation automatically** — just pass `onAddToCart`:
|
|
@@ -1045,7 +1059,14 @@ import { CartIcon } from "@onexapis/core/components";
|
|
|
1045
1059
|
```tsx
|
|
1046
1060
|
<ProductCard
|
|
1047
1061
|
product={product}
|
|
1048
|
-
onAddToCart={(p) =>
|
|
1062
|
+
onAddToCart={(p) =>
|
|
1063
|
+
addItem({
|
|
1064
|
+
productId: p.id,
|
|
1065
|
+
name: p.title,
|
|
1066
|
+
image: p.image,
|
|
1067
|
+
price: p.salePrice,
|
|
1068
|
+
})
|
|
1069
|
+
}
|
|
1049
1070
|
/>
|
|
1050
1071
|
```
|
|
1051
1072
|
|
|
@@ -1087,7 +1108,9 @@ import {
|
|
|
1087
1108
|
} from "@onexapis/core/server";
|
|
1088
1109
|
|
|
1089
1110
|
// Fetch products (server-side with ISR caching)
|
|
1090
|
-
const { data: products, pagination } = await fetchProducts(companyId, {
|
|
1111
|
+
const { data: products, pagination } = await fetchProducts(companyId, {
|
|
1112
|
+
limit: 12,
|
|
1113
|
+
});
|
|
1091
1114
|
|
|
1092
1115
|
// Fetch website settings
|
|
1093
1116
|
const settings = await fetchSettings(companyId);
|