@webround/commerce-react 0.3.15 → 0.4.1

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/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export { useResource } from './use-resource.js';
2
- export type { UseResourceOptions, UseResourceResult } from './use-resource.js';
2
+ export type { UseResourceOptions, UseResourceResult, UseResourceState } from './use-resource.js';
3
+ export { useStoreData } from './use-store-data.js';
4
+ export type { UseStoreDataOptions } from './use-store-data.js';
5
+ export { useProduct, useProductReviews, useRelatedProducts } from './use-product.js';
6
+ export type { ProductQuery } from './use-product.js';
3
7
  export { useShop } from './use-shop.js';
4
8
  export type { UseShopOptions, UseShopResult } from './use-shop.js';
5
9
  export { useShopAddress } from './use-shop-address.js';
@@ -18,6 +22,6 @@ export { useCustomerResource } from './use-customer-resource.js';
18
22
  export { useOrder, useOrders, useSubscription, useSubscriptions } from './use-orders.js';
19
23
  export { useGuestReturnSession, useReturnRequest, useReturnRequests } from './use-returns.js';
20
24
  export { useWishlist } from './use-wishlist.js';
21
- export type { UseWishlistResult } from './use-wishlist.js';
25
+ export type { UseWishlistResult, WishlistActions } from './use-wishlist.js';
22
26
  export { useMyReviews, useReviewAbility } from './use-reviews.js';
23
27
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // @webround/commerce-react: what a React component needs to use a store (React 19). The logic is `@webround/commerce`'s; this only binds it to components.
2
2
  export { useResource } from './use-resource.js';
3
+ export { useStoreData } from './use-store-data.js';
4
+ export { useProduct, useProductReviews, useRelatedProducts } from './use-product.js';
3
5
  export { useShop } from './use-shop.js';
4
6
  export { useShopAddress } from './use-shop-address.js';
5
7
  export { authActions, useAuth } from './use-auth.js';
@@ -7,9 +7,8 @@ export interface AddressActions {
7
7
  setDefault: AddressBook['setDefault'];
8
8
  refresh: AddressBook['refresh'];
9
9
  }
10
- export interface UseAddressesResult {
11
- state: AddressBookState;
12
- addresses: AddressBookState['addresses'];
10
+ /** The addresses, by name, as `commerce.addresses.getState()` has them: `addresses`, `owner`, `loaded`, `busy`, `error`; and `actions`. */
11
+ export interface UseAddressesResult extends AddressBookState {
13
12
  actions: AddressActions;
14
13
  }
15
14
  export declare function addressActions(book: AddressBook): AddressActions;
@@ -15,6 +15,6 @@ export function useAddresses(commerce) {
15
15
  const state = useSyncExternalStore(book.subscribe, book.getState, book.getState);
16
16
  const actions = useMemo(() => addressActions(book), [book]);
17
17
  useEffect(() => { void book.start(); }, [book]);
18
- return { state, addresses: state.addresses, actions };
18
+ return { ...state, actions };
19
19
  }
20
20
  //# sourceMappingURL=use-addresses.js.map
@@ -1,6 +1,5 @@
1
- import type { Auth } from '@webround/commerce';
2
- import type { AuthState, AuthStatus, AuthStore, Customer } from '@webround/commerce';
3
- import type { TwoFactorChallenge } from '@webround/commerce';
1
+ import type { Commerce } from '@webround/commerce';
2
+ import type { AuthState, AuthStore } from '@webround/commerce';
4
3
  /** What changes the session. A call that fails THROWS what the store said (a wrong password, a validation list in `issues`): it belongs to the form that made it. */
5
4
  export interface AuthActions {
6
5
  signup: AuthStore['signup'];
@@ -9,16 +8,14 @@ export interface AuthActions {
9
8
  cancelChallenge: AuthStore['cancelChallenge'];
10
9
  logout: AuthStore['logout'];
11
10
  }
12
- export interface UseAuthResult {
13
- state: AuthState;
14
- /** `unknown` until it is known whether a customer is kept (and on a server render: no window, nobody to take back). */
15
- status: AuthStatus;
16
- customer: Customer | null;
17
- /** The code of the authenticator app is wanted (status `challenge`). */
18
- challenge: TwoFactorChallenge | null;
11
+ /**
12
+ * The session, by name, as `commerce.auth.store.getState()` has it: `status` (`unknown` until it is known whether a customer is kept, and on a server render: no window, nobody to take back), `customer`, `challenge` (the code
13
+ * of the authenticator app is wanted: status `challenge`), `error`; and `actions`.
14
+ */
15
+ export interface UseAuthResult extends AuthState {
19
16
  actions: AuthActions;
20
17
  }
21
18
  export declare function authActions(store: AuthStore): AuthActions;
22
- /** `auth` is what `createAuth` made. Any number of components can use it: the customer is taken back once. */
23
- export declare function useAuth(auth: Pick<Auth, 'store'>): UseAuthResult;
19
+ /** `commerce` is what `createCommerce` made (or `@webround/commerce-react/site`'s). Any number of components can use it: the customer is taken back once. */
20
+ export declare function useAuth(commerce: Pick<Commerce, 'auth'>): UseAuthResult;
24
21
  //# sourceMappingURL=use-auth.d.ts.map
package/dist/use-auth.js CHANGED
@@ -8,13 +8,13 @@ export function authActions(store) {
8
8
  logout: () => store.logout(),
9
9
  };
10
10
  }
11
- /** `auth` is what `createAuth` made. Any number of components can use it: the customer is taken back once. */
12
- export function useAuth(auth) {
13
- const { store } = auth;
11
+ /** `commerce` is what `createCommerce` made (or `@webround/commerce-react/site`'s). Any number of components can use it: the customer is taken back once. */
12
+ export function useAuth(commerce) {
13
+ const { store } = commerce.auth;
14
14
  const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
15
15
  const actions = useMemo(() => authActions(store), [store]);
16
16
  // Not in render: a server render asks nothing. `restore` is one call for whoever asks while it is under way, and nothing once it is known
17
17
  useEffect(() => { void store.restore(); }, [store]);
18
- return { state, status: state.status, customer: state.customer, challenge: state.challenge, actions };
18
+ return { ...state, actions };
19
19
  }
20
20
  //# sourceMappingURL=use-auth.js.map
@@ -7,18 +7,17 @@ export interface CartActions {
7
7
  clear: CartStore['clear'];
8
8
  refresh: CartStore['refresh'];
9
9
  }
10
- export interface UseCartResult {
11
- state: CartState;
12
- /** How many items are in the cart: known before the store has priced anything. */
13
- count: number;
14
- /** What the store says the cart costs (null until it said); `state.isStale` when it is one change behind. */
15
- priced: CartState['priced'];
10
+ /**
11
+ * The cart, by name, as `commerce.cart.getState()` has it: `lines` (what the cart IS: known at once, no network), `count` (how many items: what a badge says), `cart` (the cart as the store prices it, with names, pictures and totals:
12
+ * `null` until the store said; `isStale` when it is one change behind), `owner`, `busy`, `error`; and `actions`.
13
+ */
14
+ export interface UseCartResult extends CartState {
16
15
  actions: CartActions;
17
16
  }
18
17
  export declare function cartActions(store: CartStore): CartActions;
19
18
  /**
20
- * To step a quantity (a "+" button), read it from `state.lines`, not from `priced`: `lines` is what the cart is NOW (a change shows in it at once), `priced` is the store's answer, which can be one change behind
21
- * (`state.isStale`), so three quick clicks on a "+" that reads `priced` ask for the same quantity three times.
19
+ * To step a quantity (a "+" button), read it from `lines`, not from `cart`: `lines` is what the cart is NOW (a change shows in it at once), `cart` is the store's answer, which can be one change behind
20
+ * (`isStale`), so three quick clicks on a "+" that reads `cart` ask for the same quantity three times.
22
21
  */
23
22
  /** `commerce` is what `createCommerce` made (or `@webround/commerce-react/site`'s). Any number of components can use it: the cart starts once. A server render says `unknown` and zero items, as the first render in the browser does. */
24
23
  export declare function useCart(commerce: {
package/dist/use-cart.js CHANGED
@@ -9,8 +9,8 @@ export function cartActions(store) {
9
9
  };
10
10
  }
11
11
  /**
12
- * To step a quantity (a "+" button), read it from `state.lines`, not from `priced`: `lines` is what the cart is NOW (a change shows in it at once), `priced` is the store's answer, which can be one change behind
13
- * (`state.isStale`), so three quick clicks on a "+" that reads `priced` ask for the same quantity three times.
12
+ * To step a quantity (a "+" button), read it from `lines`, not from `cart`: `lines` is what the cart is NOW (a change shows in it at once), `cart` is the store's answer, which can be one change behind
13
+ * (`isStale`), so three quick clicks on a "+" that reads `cart` ask for the same quantity three times.
14
14
  */
15
15
  /** `commerce` is what `createCommerce` made (or `@webround/commerce-react/site`'s). Any number of components can use it: the cart starts once. A server render says `unknown` and zero items, as the first render in the browser does. */
16
16
  export function useCart(commerce) {
@@ -18,6 +18,6 @@ export function useCart(commerce) {
18
18
  const state = useSyncExternalStore(cart.subscribe, cart.getState, cart.getState);
19
19
  const actions = useMemo(() => cartActions(cart), [cart]);
20
20
  useEffect(() => { void cart.start(); }, [cart]);
21
- return { state, count: state.count, priced: state.priced, actions };
21
+ return { ...state, actions };
22
22
  }
23
23
  //# sourceMappingURL=use-cart.js.map
@@ -1,5 +1,5 @@
1
1
  import type { Auth } from '@webround/commerce';
2
2
  import type { Resource } from '@webround/commerce';
3
3
  import { type UseResourceOptions, type UseResourceResult } from './use-resource.js';
4
- export declare function useCustomerResource<T>(auth: Pick<Auth, 'store'>, make: () => Resource<T> | null, options?: UseResourceOptions): UseResourceResult<T>;
4
+ export declare function useCustomerResource<T extends object>(auth: Pick<Auth, 'store'>, make: () => Resource<T> | null, options?: UseResourceOptions): UseResourceResult<T>;
5
5
  //# sourceMappingURL=use-customer-resource.d.ts.map
@@ -1,14 +1,14 @@
1
1
  import type { Auth } from '@webround/commerce';
2
2
  import type { Orders } from '@webround/commerce';
3
- import type { Order, OrderPage, PageQuery, Subscription, SubscriptionPage } from '@webround/commerce';
3
+ import type { OrderAnswer, OrderPage, PageQuery, SubscriptionAnswer, SubscriptionPage } from '@webround/commerce';
4
4
  import type { UseResourceOptions, UseResourceResult } from './use-resource.js';
5
5
  type Source = {
6
6
  auth: Pick<Auth, 'store'>;
7
7
  orders: Orders;
8
8
  };
9
9
  export declare const useOrders: (commerce: Source, query?: PageQuery, options?: UseResourceOptions) => UseResourceResult<OrderPage>;
10
- export declare const useOrder: (commerce: Source, id: string | null | undefined) => UseResourceResult<Order>;
10
+ export declare const useOrder: (commerce: Source, id: string | null | undefined) => UseResourceResult<OrderAnswer>;
11
11
  export declare const useSubscriptions: (commerce: Source, query?: PageQuery, options?: UseResourceOptions) => UseResourceResult<SubscriptionPage>;
12
- export declare const useSubscription: (commerce: Source, id: string | null | undefined) => UseResourceResult<Subscription>;
12
+ export declare const useSubscription: (commerce: Source, id: string | null | undefined) => UseResourceResult<SubscriptionAnswer>;
13
13
  export {};
14
14
  //# sourceMappingURL=use-orders.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { useCustomerResource } from './use-customer-resource.js';
2
- export const useOrders = (commerce, query, options = { keepPrevious: true }) => useCustomerResource(commerce.auth, () => commerce.orders.list(query), options);
3
- export const useOrder = (commerce, id) => useCustomerResource(commerce.auth, () => (id ? commerce.orders.get(id) : null));
4
- export const useSubscriptions = (commerce, query, options = { keepPrevious: true }) => useCustomerResource(commerce.auth, () => commerce.orders.subscriptions.list(query), options);
5
- export const useSubscription = (commerce, id) => useCustomerResource(commerce.auth, () => (id ? commerce.orders.subscriptions.get(id) : null));
2
+ export const useOrders = (commerce, query, options = { keepPrevious: true }) => useCustomerResource(commerce.auth, () => commerce.orders.resource.list(query), options);
3
+ export const useOrder = (commerce, id) => useCustomerResource(commerce.auth, () => (id ? commerce.orders.resource.get(id) : null));
4
+ export const useSubscriptions = (commerce, query, options = { keepPrevious: true }) => useCustomerResource(commerce.auth, () => commerce.orders.resource.subscriptions.list(query), options);
5
+ export const useSubscription = (commerce, id) => useCustomerResource(commerce.auth, () => (id ? commerce.orders.resource.subscriptions.get(id) : null));
6
6
  //# sourceMappingURL=use-orders.js.map
@@ -0,0 +1,16 @@
1
+ import type { Commerce, ProductAnswer, ProductByIdInput, ProductBySlugInput, ProductPage, ProductRelatedInput, ProductReviewsInput, ReviewPage } from '@webround/commerce';
2
+ import { type UseResourceOptions, type UseResourceResult } from './use-resource.js';
3
+ type Source = Pick<Commerce, 'catalog'>;
4
+ /** The product that has the variant `slug` (a product has no slug: it is the VARIANT's), or the one with this `id`; what is loaded with it is for `include*` to say. */
5
+ export type ProductQuery = ({
6
+ slug: string;
7
+ } & ProductBySlugInput) | ({
8
+ id: string;
9
+ } & ProductByIdInput);
10
+ export declare function useProduct(commerce: Source, query: ProductQuery | null | undefined): UseResourceResult<ProductAnswer>;
11
+ /** The reviews of a product, a page of them, with how many there are for each grade. `productId` is the product's (`product.id`). A page keeps showing the one before while the next comes. */
12
+ export declare function useProductReviews(commerce: Source, productId: string | null | undefined, input?: ProductReviewsInput, options?: UseResourceOptions): UseResourceResult<ReviewPage>;
13
+ /** The products related to a product (up to ten, the most relevant first). */
14
+ export declare function useRelatedProducts(commerce: Source, productId: string | null | undefined, input: ProductRelatedInput): UseResourceResult<ProductPage>;
15
+ export {};
16
+ //# sourceMappingURL=use-product.d.ts.map
@@ -0,0 +1,16 @@
1
+ import { useResource } from './use-resource.js';
2
+ export function useProduct(commerce, query) {
3
+ const { products } = commerce.catalog.resource;
4
+ return useResource(query ? ('slug' in query ? products.getBySlug(query.slug, withoutSlug(query)) : products.getById(query.id, withoutId(query))) : null);
5
+ }
6
+ const withoutSlug = ({ slug: _slug, ...input }) => input;
7
+ const withoutId = ({ id: _id, ...input }) => input;
8
+ /** The reviews of a product, a page of them, with how many there are for each grade. `productId` is the product's (`product.id`). A page keeps showing the one before while the next comes. */
9
+ export function useProductReviews(commerce, productId, input, options = { keepPrevious: true }) {
10
+ return useResource(productId ? commerce.catalog.resource.products.getReviews(productId, input) : null, options);
11
+ }
12
+ /** The products related to a product (up to ten, the most relevant first). */
13
+ export function useRelatedProducts(commerce, productId, input) {
14
+ return useResource(productId ? commerce.catalog.resource.products.getRelated(productId, input) : null);
15
+ }
16
+ //# sourceMappingURL=use-product.js.map
@@ -1,25 +1,29 @@
1
1
  import type { Resource } from '@webround/commerce';
2
2
  export interface UseResourceOptions {
3
3
  /**
4
- * When the question changes (another page of the results, another search), keep showing the answer of the one before until the new one comes, instead of nothing: `data` is that
5
- * answer and `isStale` says it is not the one asked for. Off by default: what is shown is always the answer to what is asked.
4
+ * When the question changes (another page of the results, another search), keep showing the answer of the one before until the new one comes, instead of nothing: the keys of the answer are that
5
+ * answer's and `isStale` says it is not the one asked for. Off by default: what is shown is always the answer to what is asked.
6
6
  */
7
7
  keepPrevious?: boolean;
8
8
  }
9
- export interface UseResourceResult<T> {
9
+ /** How the asking stands: the same fields in every hook that reads from the backend. */
10
+ export interface UseResourceState {
10
11
  /** `idle`: nothing is asked (no resource was given). `pending`: the answer is being waited for. `ready`: it is there. `error`: it could not be had. */
11
12
  status: 'idle' | 'pending' | 'ready' | 'error';
12
- /** The answer to what is asked, or (with `keepPrevious`, while the new one is awaited) the answer to the question before; undefined when there is none. */
13
- data: T | undefined;
14
13
  /** Why it failed (a `CommerceError` for what the store did, or a network error): set with `status: 'error'`, undefined otherwise. */
15
14
  error: unknown;
16
15
  /** The answer to what is asked is being waited for. */
17
16
  isFetching: boolean;
18
- /** `data` is the answer to an earlier question (`keepPrevious`), not to the one asked now. */
17
+ /** The keys of the answer are the answer to an earlier question (`keepPrevious`), not to the one asked now. */
19
18
  isStale: boolean;
20
19
  /** Asks again after an error. It does nothing in any other state: what is there is not asked for again (to forget it, invalidate it in the catalog). */
21
20
  retry: () => void;
22
21
  }
22
+ /**
23
+ * What a hook gives: the keys of the answer, each by its own name (`products` and `pagination`; `order`; `reviews`, `pagination` and `breakdown`…: the same a call gives without React), `undefined` until the answer
24
+ * is there, together with how the asking stands (`UseResourceState`). The answer's keys never meet the state's own (`status`, `error`…): the state's are written last, and a test keeps an answer from taking one.
25
+ */
26
+ export type UseResourceResult<T extends object> = Partial<T> & UseResourceState;
23
27
  /** A resource asked for by a component: `null`/`undefined` is "not asked yet" (a slug that is not known, a search that is not made). */
24
- export declare function useResource<T>(resource: Resource<T> | null | undefined, options?: UseResourceOptions): UseResourceResult<T>;
28
+ export declare function useResource<T extends object>(resource: Resource<T> | null | undefined, options?: UseResourceOptions): UseResourceResult<T>;
25
29
  //# sourceMappingURL=use-resource.d.ts.map
@@ -47,14 +47,16 @@ export function useResource(resource, options = {}) {
47
47
  hasShown.current = true;
48
48
  }
49
49
  }, [snapshot]);
50
+ // The answer's keys first, the state's after: the state's own names win
51
+ const result = (state, answer) => ({ ...answer, ...state, retry });
50
52
  if (!asked)
51
- return { status: 'idle', data: undefined, error: undefined, isFetching: false, isStale: false, retry };
53
+ return result({ status: 'idle', error: undefined, isFetching: false, isStale: false }, undefined);
52
54
  if (snapshot.status === 'ready')
53
- return { status: 'ready', data: snapshot.data, error: undefined, isFetching: false, isStale: false, retry };
55
+ return result({ status: 'ready', error: undefined, isFetching: false, isStale: false }, snapshot.data);
54
56
  if (snapshot.status === 'error')
55
- return { status: 'error', data: undefined, error: snapshot.error, isFetching: false, isStale: false, retry };
57
+ return result({ status: 'error', error: snapshot.error, isFetching: false, isStale: false }, undefined);
56
58
  // `idle` here is a question that is about to be asked (the effect has not run yet): it is awaited, there is no frame in which it is "nothing"
57
59
  const stale = keepPrevious && hasShown.current;
58
- return { status: 'pending', data: stale ? shown.current : undefined, error: undefined, isFetching: true, isStale: stale, retry };
60
+ return result({ status: 'pending', error: undefined, isFetching: true, isStale: stale }, stale ? shown.current : undefined);
59
61
  }
60
62
  //# sourceMappingURL=use-resource.js.map
@@ -1,14 +1,12 @@
1
- import type { Auth } from '@webround/commerce';
2
- import type { Returns } from '@webround/commerce';
3
- import type { GuestReturnSession, PageQuery, ReturnRequest, ReturnRequestPage } from '@webround/commerce';
1
+ import type { Auth, GuestReturnSessionAnswer, PageQuery, ReturnRequestAnswer, ReturnRequestPage, Returns } from '@webround/commerce';
4
2
  import { type UseResourceOptions, type UseResourceResult } from './use-resource.js';
5
3
  type Source = {
6
4
  auth: Pick<Auth, 'store'>;
7
5
  returns: Returns;
8
6
  };
9
7
  export declare const useReturnRequests: (commerce: Source, query?: PageQuery, options?: UseResourceOptions) => UseResourceResult<ReturnRequestPage>;
10
- export declare const useReturnRequest: (commerce: Source, id: string | null | undefined) => UseResourceResult<ReturnRequest>;
8
+ export declare const useReturnRequest: (commerce: Source, id: string | null | undefined) => UseResourceResult<ReturnRequestAnswer>;
11
9
  /** The order a guest's link opens (the `token` of the page's address; none yet is no question). */
12
- export declare const useGuestReturnSession: (commerce: Pick<Source, "returns">, token: string | null | undefined) => UseResourceResult<GuestReturnSession>;
10
+ export declare const useGuestReturnSession: (commerce: Pick<Source, "returns">, token: string | null | undefined) => UseResourceResult<GuestReturnSessionAnswer>;
13
11
  export {};
14
12
  //# sourceMappingURL=use-returns.d.ts.map
@@ -1,7 +1,7 @@
1
- import { useCustomerResource } from './use-customer-resource.js';
2
1
  import { useResource } from './use-resource.js';
3
- export const useReturnRequests = (commerce, query, options = { keepPrevious: true }) => useCustomerResource(commerce.auth, () => commerce.returns.list(query), options);
4
- export const useReturnRequest = (commerce, id) => useCustomerResource(commerce.auth, () => (id ? commerce.returns.get(id) : null));
2
+ import { useCustomerResource } from './use-customer-resource.js';
3
+ export const useReturnRequests = (commerce, query, options = { keepPrevious: true }) => useCustomerResource(commerce.auth, () => commerce.returns.resource.list(query), options);
4
+ export const useReturnRequest = (commerce, id) => useCustomerResource(commerce.auth, () => (id ? commerce.returns.resource.get(id) : null));
5
5
  /** The order a guest's link opens (the `token` of the page's address; none yet is no question). */
6
- export const useGuestReturnSession = (commerce, token) => useResource(token ? commerce.returns.guest.session(token) : null);
6
+ export const useGuestReturnSession = (commerce, token) => useResource(token ? commerce.returns.resource.guest.session(token) : null);
7
7
  //# sourceMappingURL=use-returns.js.map
@@ -1,7 +1,7 @@
1
1
  import { useSyncExternalStore } from 'react';
2
2
  import { reviewAbility } from '@webround/commerce';
3
3
  import { useCustomerResource } from './use-customer-resource.js';
4
- export const useMyReviews = (commerce, query, options = { keepPrevious: true }) => useCustomerResource(commerce.auth, () => commerce.reviews.mine(query), options);
4
+ export const useMyReviews = (commerce, query, options = { keepPrevious: true }) => useCustomerResource(commerce.auth, () => commerce.reviews.resource.list(query), options);
5
5
  /** Who can write a review now: a guest has to log in, a customer whose address is not verified has to verify it, one whose is can. It follows the session. */
6
6
  export function useReviewAbility(commerce) {
7
7
  const state = useSyncExternalStore(commerce.auth.store.subscribe, commerce.auth.store.getState, commerce.auth.store.getState);
@@ -1,12 +1,10 @@
1
- import type { CachedCatalog } from '@webround/commerce';
2
- import type { ProductPage, ProductSearch, StoreDataResponse } from '@webround/commerce';
1
+ import type { Commerce, ProductPage, ProductSearch, StoreData } from '@webround/commerce';
3
2
  import { type SortOption } from '@webround/commerce';
4
3
  import type { ShopState } from '@webround/commerce';
5
4
  import { type ShopActions, type ShopStore } from '@webround/commerce';
6
5
  import type { SearchContext } from '@webround/commerce';
7
- import { type UseResourceResult } from './use-resource.js';
6
+ import { type UseResourceState } from './use-resource.js';
8
7
  export interface UseShopOptions {
9
- catalog: CachedCatalog;
10
8
  /** What the question needs besides what the customer chose: the currency, the language, the cadence, what the products are loaded with. */
11
9
  context: SearchContext;
12
10
  /** The state the shop starts in (only when the hook keeps the state itself: it is not read again). */
@@ -16,19 +14,19 @@ export interface UseShopOptions {
16
14
  /** While a new question is awaited, keep showing the answer to the one before (marked `isStale`), so a listing does not empty at every click. On by default. */
17
15
  keepPrevious?: boolean;
18
16
  }
19
- export interface UseShopResult {
20
- /** What the customer chose. */
17
+ /**
18
+ * The store's data (`store`, `collections`, `tags`, `attributes`, `filters`: `undefined` until it is there), the products and their `pagination` (the same: `undefined` until they are), and how the asking of the PRODUCTS
19
+ * stands (`status` and the rest say `pending` or `error` while the store's data, which the question needs, is awaited or failed).
20
+ */
21
+ export interface UseShopResult extends Partial<StoreData>, Partial<ProductPage>, UseResourceState {
22
+ /** What the customer chose: the collection, the text, the tag values and ranges, the order, the page. */
21
23
  state: ShopState;
22
- /** What changes it. */
24
+ /** What changes it. Every change that is not `setPage` goes back to page 1. */
23
25
  actions: ShopActions;
24
- /** The store's own data (its tags, collections, filters, currencies): what the controls are drawn from. */
25
- storeData: UseResourceResult<StoreDataResponse>;
26
26
  /** The orders that can be offered now (with the store's data in hand, and a text or not); none until it is. */
27
27
  sortOptions: SortOption[];
28
- /** The question the catalog is being asked now, or null while the store's data is awaited. */
28
+ /** The exact question the catalog is being asked now (`state` made a request, with `context`), or `null` while the store's data is awaited. `products` are its answer. */
29
29
  question: ProductSearch | null;
30
- /** The products. While the store's data is awaited this is `pending` (or its error, with a `retry` that asks for the store again). */
31
- results: UseResourceResult<ProductPage>;
32
30
  }
33
- export declare function useShop(options: UseShopOptions): UseShopResult;
31
+ export declare function useShop(commerce: Pick<Commerce, 'catalog'>, options: UseShopOptions): UseShopResult;
34
32
  //# sourceMappingURL=use-shop.d.ts.map
package/dist/use-shop.js CHANGED
@@ -4,8 +4,10 @@ import { shopQuestion } from '@webround/commerce';
4
4
  import { sortOptionsOf } from '@webround/commerce';
5
5
  import { createShopStore, shopActions } from '@webround/commerce';
6
6
  import { useResource } from './use-resource.js';
7
- export function useShop(options) {
8
- const { catalog, context, keepPrevious = true } = options;
7
+ import { useStoreData } from './use-store-data.js';
8
+ export function useShop(commerce, options) {
9
+ const { context, keepPrevious = true } = options;
10
+ const { catalog } = commerce;
9
11
  // The state: in memory, in the hook, unless a store was given
10
12
  const own = useRef(null);
11
13
  if (!options.store && !own.current)
@@ -13,19 +15,20 @@ export function useShop(options) {
13
15
  const store = options.store ?? own.current;
14
16
  const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
15
17
  const actions = useMemo(() => shopActions(store), [store]);
16
- const storeData = useResource(catalog.resource.storeData.get(context.langCode === undefined ? undefined : { langCode: context.langCode }));
17
- const data = storeData.data?.data;
18
+ const storeData = useStoreData(commerce, { langCode: context.langCode });
19
+ const { status: storeStatus, error: storeError, retry: retryStore, isFetching: _fetching, isStale: _stale, ...data } = storeData;
20
+ const { tags, attributes, collections, filters } = data;
18
21
  // A new `context` object at every render is the same context: only what is in it makes a new question
19
22
  const contextKey = canonicalJson(context);
20
23
  // eslint-disable-next-line react-hooks/exhaustive-deps
21
- const asked = useMemo(() => shopQuestion(catalog, data, state, context), [catalog, data, state, contextKey]);
24
+ const asked = useMemo(() => (tags && attributes && collections && filters ? shopQuestion(catalog, { tags, attributes, collections, filters }, state, context) : null), [catalog, tags, attributes, collections, filters, state, contextKey]);
22
25
  const products = useResource(asked?.resource ?? null, { keepPrevious });
23
- const sortOptions = useMemo(() => (data ? sortOptionsOf(data) : []), [data]);
26
+ const sortOptions = useMemo(() => (tags && attributes ? sortOptionsOf({ tags, attributes }) : []), [tags, attributes]);
24
27
  // With no question yet the products are waiting for the store's data (or failed with it): say that, so the screen has one thing to look at
25
- const retry = useCallback(() => storeData.retry(), [storeData]);
26
- const results = asked
28
+ const retry = useCallback(() => retryStore(), [retryStore]);
29
+ const listing = asked
27
30
  ? products
28
- : { status: storeData.status === 'error' ? 'error' : 'pending', data: undefined, error: storeData.error, isFetching: storeData.status !== 'error', isStale: false, retry };
29
- return { state, actions, storeData, sortOptions, question: asked?.question ?? null, results };
31
+ : { status: storeStatus === 'error' ? 'error' : 'pending', error: storeError, isFetching: storeStatus !== 'error', isStale: false, retry };
32
+ return { ...data, state, actions, sortOptions, question: asked?.question ?? null, ...listing };
30
33
  }
31
34
  //# sourceMappingURL=use-shop.js.map
@@ -0,0 +1,8 @@
1
+ import type { Commerce, StoreData } from '@webround/commerce';
2
+ import { type UseResourceResult } from './use-resource.js';
3
+ export interface UseStoreDataOptions {
4
+ /** The language the names are asked in (left out: the store's own). */
5
+ langCode?: string;
6
+ }
7
+ export declare function useStoreData(commerce: Pick<Commerce, 'catalog'>, options?: UseStoreDataOptions): UseResourceResult<StoreData>;
8
+ //# sourceMappingURL=use-store-data.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { useResource } from './use-resource.js';
2
+ export function useStoreData(commerce, options = {}) {
3
+ const { langCode } = options;
4
+ return useResource(commerce.catalog.resource.storeData.get(langCode === undefined ? undefined : { langCode }));
5
+ }
6
+ //# sourceMappingURL=use-store-data.js.map
@@ -1,13 +1,18 @@
1
1
  import { type Wishlist, type WishlistState } from '@webround/commerce';
2
- export interface UseWishlistResult {
3
- state: WishlistState;
4
- count: number;
5
- /** Whether a variant is in the wishlist. */
6
- has(variantId: string): boolean;
7
- /** The switch: adds a variant that is not in the wishlist, removes one that is. Throws what the store refuses; `LOGIN_REQUIRED` for a guest. */
2
+ export interface WishlistActions {
3
+ /** The switch: adds a variant that is not in the wishlist, removes one that is. Throws what the store refuses; `LOGIN_REQUIRED` for a guest. What is shown goes back to what the store holds. */
8
4
  toggle: Wishlist['toggle'];
9
5
  refresh: Wishlist['refresh'];
10
6
  }
7
+ /**
8
+ * The wishlist, by name, as `commerce.wishlist.getState()` has it: `wishlist` (the items), `count`, `variantIds`, `loaded`, `enabled` (`false` once the store said it has the wishlist off), `owner`, `busy`, `error`; `has`
9
+ * (whether a variant is in it, for a heart); and `actions`.
10
+ */
11
+ export interface UseWishlistResult extends WishlistState {
12
+ /** Whether a variant is in the wishlist. */
13
+ has(variantId: string): boolean;
14
+ actions: WishlistActions;
15
+ }
11
16
  /** `commerce` is what `createCommerce` made (or `@webround/commerce-react/site`'s). Any number of components can use it (a heart on each product): the wishlist starts once. A server render says `unknown` and nothing in it. */
12
17
  export declare function useWishlist(commerce: {
13
18
  wishlist: Wishlist;
@@ -7,6 +7,6 @@ export function useWishlist(commerce) {
7
7
  useEffect(() => { void wishlist.start(); }, [wishlist]);
8
8
  const has = useCallback((variantId) => isInWishlist(state, variantId), [state.variantIds]);
9
9
  const actions = useMemo(() => ({ toggle: (variantId) => wishlist.toggle(variantId), refresh: () => wishlist.refresh() }), [wishlist]);
10
- return { state, count: state.count, has, toggle: actions.toggle, refresh: actions.refresh };
10
+ return { ...state, has, actions };
11
11
  }
12
12
  //# sourceMappingURL=use-wishlist.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webround/commerce-react",
3
- "version": "0.3.15",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -28,8 +28,8 @@
28
28
  "typecheck": "tsc --noEmit -p tsconfig.test.json"
29
29
  },
30
30
  "peerDependencies": {
31
- "@webround/commerce": "^0.3.0",
32
- "@webround/core": "^0.3.0",
31
+ "@webround/commerce": "^0.4.0",
32
+ "@webround/core": "^0.4.0",
33
33
  "react": ">=19"
34
34
  },
35
35
  "devDependencies": {