@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.
@@ -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 { CartProvider, FlyToCartProvider } from "@onexapis/core/contexts";
27
+ import {
28
+ CartProvider,
29
+ FlyToCartProvider,
30
+ PageDataProvider,
31
+ } from "@onexapis/core/contexts";
26
32
  import { LocaleProvider } from "@onexapis/core/contexts";
27
33
 
28
- // Compose @onexapis/core/hooks from published subpaths
29
- // Only include use* hook functions spreading entire modules includes
30
- // Zustand stores and React providers that cause "selector is not a function" errors.
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
- if (a.config.type === "home") return -1;
347
- if (b.config.type === "home") return 1;
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): { locale: string; rest: 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.replace(/^\/+|\/+$/g, "").toLowerCase();
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
- <PreviewApp />
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.31",
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.3",
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
- Import from `@onexapis/core/hooks`:
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
- useAuth,
359
+ useCheckout,
360
360
  useDesignSystem,
361
361
  } from "@onexapis/core/hooks";
362
362
  ```
363
363
 
364
- ### Commerce Data (React Query)
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
- // Products
368
- const { data, isLoading } = useProducts({ limit: 8, page: 0 });
380
+ // Fetch products
381
+ const { data, isLoading } = useProducts({ limit: 8 });
369
382
  const products = data?.data ?? [];
370
383
 
371
- // Single product
372
- const { data: product } = useProductBySlug("blue-t-shirt");
384
+ // Cart
385
+ const { items, addItem, itemCount, subtotal } = useCart();
373
386
 
374
- // Blogs
375
- const { data, isLoading } = useBlogs({ limit: 6 });
376
- const blogs = data?.data ?? [];
387
+ // Auth
388
+ const { user, isAuthenticated, login, logout } = useAuth();
377
389
 
378
- // Website settings
379
- const { data: settings } = useSettings();
380
- ```
390
+ // Checkout (full form)
391
+ const checkout = useCheckout({ paymentRedirectUrl: "/payment" });
392
+ // checkout.fullName, checkout.handleCheckout, checkout.errors
381
393
 
382
- ### State Management
394
+ // Product detail with buy now
395
+ const detail = useProductDetail({ slug: routeParams.slug });
396
+ // detail.product, detail.handleAddToCart, detail.handleBuyNow
383
397
 
384
- ```tsx
385
- // Shopping cart
386
- const { items, addItem, removeItem, totalPrice, clearCart } = useCart();
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
- ### Theme & Context
402
+ // Responsive
403
+ const isMobile = useIsMobile();
404
+ const isDesktop = useIsDesktop();
400
405
 
401
- ```tsx
402
- // Design system tokens
403
- const { colors, typography, colorMode } = useDesignSystem();
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<PageConfig, "id" | "createdAt" | "updatedAt"> = {
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]", // Dynamic segment in brackets
612
- isDynamic: true, // Flag as dynamic
613
- dynamicSegments: ["slug"], // Parameter names
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({ section, schema, isEditing, data }: SectionComponentProps) {
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 || ""; // "blue-shirt" from /products/blue-shirt
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]", // Default/fallback
665
+ path: "/products/[slug]", // Default/fallback
662
666
  isDynamic: true,
663
667
  dynamicSegments: ["slug"],
664
- localizedPaths: { // Locale-specific paths (typed by Locale enum)
665
- vi: "/san-pham/[slug]", // /vi/san-pham/blue-shirt
666
- en: "/products/[slug]", // /en/products/blue-shirt
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; // "vi" or "en"
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 { items, addItem, removeItem, updateQuantity, clearCart, itemCount, subtotal } = useCart();
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) => addItem({ productId: p.id, name: p.title, image: p.image, price: p.salePrice })}
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, { limit: 12 });
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);