@usequeek/theme-kit 0.1.23 → 0.1.25
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/CHANGELOG.md +45 -0
- package/LICENSE +1 -1
- package/README.md +164 -21
- package/api/collections.ts +14 -1
- package/api/orders.ts +15 -1
- package/apps/follow-card.tsx +5 -0
- package/apps/subscribe-form.tsx +2 -2
- package/components/auth/auth-flow.tsx +12 -0
- package/components/cart/cart-panel-controller.tsx +13 -0
- package/components/checkout/default-checkout-shell.tsx +5 -0
- package/components/checkout/policy-links.tsx +4 -0
- package/components/image.tsx +10 -0
- package/components/language-switcher.tsx +1 -0
- package/components/powered-by-queek.tsx +4 -3
- package/components/product-media-frame.tsx +5 -0
- package/components/product-metafields.tsx +9 -0
- package/components/variant-picker.css +4 -7
- package/edit-mode.tsx +4 -3
- package/hooks/use-address-autocomplete.ts +9 -0
- package/hooks/use-auth-flow.tsx +8 -13
- package/hooks/use-auth.ts +10 -0
- package/hooks/use-cart.ts +12 -0
- package/hooks/use-categories.ts +16 -2
- package/hooks/use-href.ts +9 -4
- package/hooks/use-posts.ts +16 -8
- package/hooks/use-product-detail.ts +25 -11
- package/hooks/use-product-questions.ts +16 -3
- package/hooks/use-products.ts +44 -22
- package/hooks/use-related-products.ts +17 -5
- package/hooks/use-resolve-coordinates.ts +9 -0
- package/hooks/use-reviews.ts +16 -2
- package/hooks/use-search.ts +10 -0
- package/hooks/use-shop-cart.ts +8 -0
- package/hooks/use-shop.ts +19 -6
- package/hooks/use-variant-selection.ts +23 -19
- package/index.ts +10 -11
- package/locales/en.default.json +4 -0
- package/navigation.tsx +2 -0
- package/package.json +29 -8
- package/page-renderer.tsx +11 -0
- package/provider.tsx +29 -3
- package/sdk/client.ts +183 -36
- package/sdk/error-handler.ts +18 -10
- package/sdk/queek-client.ts +12 -2
- package/sdk/server-store-client.ts +2 -2
- package/shared-blocks/embed.tsx +7 -3
- package/shared-blocks/manifest.ts +1 -1
- package/shared-blocks/product-qa.tsx +1 -1
- package/stores/address-modal-store.ts +1 -0
- package/stores/auth-modal-store.ts +1 -0
- package/stores/cart-panel-store.ts +1 -0
- package/stores/cart-store.ts +12 -7
- package/stores/checkout-store.ts +4 -4
- package/stores/location-store.ts +5 -0
- package/stores/orders-sheet-store.ts +1 -0
- package/stores/product-modal-store.ts +1 -0
- package/stores/search-modal-store.ts +1 -0
- package/stores/user-store.ts +1 -1
- package/strings/pseudo-locale.ts +1 -1
- package/theme-context.tsx +11 -1
- package/theme-mount.tsx +14 -0
- package/utils/format.ts +10 -1
- package/utils/menu-link.ts +8 -0
- package/utils/to-error.ts +4 -0
- package/utils/vendor-host.ts +1 -2
- package/scripts/verify-package.ts +0 -102
- package/tests/apps.test.tsx +0 -33
- package/tests/auth-email-errors.test.tsx +0 -55
- package/tests/currency-contract.test.ts +0 -85
- package/tests/default-metaobject-page.test.tsx +0 -56
- package/tests/display-format.test.ts +0 -403
- package/tests/fixtures/external-theme.tsx +0 -90
- package/tests/image-loading.test.tsx +0 -31
- package/tests/language-menu.test.tsx +0 -598
- package/tests/language-switcher.test.tsx +0 -350
- package/tests/list-pagination.test.tsx +0 -71
- package/tests/locale-switch.test.ts +0 -197
- package/tests/locale-transport.test.tsx +0 -321
- package/tests/manifest-parity.test.ts +0 -54
- package/tests/markdown-render.test.ts +0 -46
- package/tests/menu-placement.test.ts +0 -63
- package/tests/metaobjects-block.test.tsx +0 -70
- package/tests/navigation.test.tsx +0 -30
- package/tests/orders-walk.test.ts +0 -48
- package/tests/page-renderer-signature.test.tsx +0 -42
- package/tests/powered-by-queek.test.tsx +0 -65
- package/tests/powered-by-url.test.ts +0 -49
- package/tests/product-images.test.ts +0 -242
- package/tests/product-metafields.test.tsx +0 -154
- package/tests/public-surface.test.ts +0 -35
- package/tests/shop-query.test.ts +0 -64
- package/tests/shopper-chrome-strings.test.tsx +0 -271
- package/tests/storefront-client-scope.test.ts +0 -43
- package/tests/theme-hook-placement.test.tsx +0 -179
- package/tests/theme-strings-client.test.tsx +0 -310
- package/tests/theme-strings-pseudo.test.tsx +0 -171
- package/tests/theme-strings-typed.test.tsx +0 -97
- package/tests/theme-strings.test.tsx +0 -401
- package/tests/theme-typed-keys.fixture.ts +0 -76
- package/tests/use-shop-prefetch.test.tsx +0 -87
package/hooks/use-reviews.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
|
|
|
4
4
|
import { useStorefront, useStorefrontLocale } from '../provider';
|
|
5
5
|
import { createBrowserClient } from '../sdk/client';
|
|
6
6
|
import { withLocaleQuery } from '../utils/locale';
|
|
7
|
+
import { toError } from '../utils/to-error';
|
|
7
8
|
import type { ReviewItem, ReviewsBlockData, ReviewsSummary } from '../types/block';
|
|
8
9
|
|
|
9
10
|
interface ReviewsResponse {
|
|
@@ -62,10 +63,20 @@ function summarize(reviews: ReviewItem[]): ReviewsSummary {
|
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Customer reviews for a `reviews` block: a list plus the rating summary
|
|
68
|
+
* (average and 1 to 5 breakdown). Scope, filters and sorting come from the
|
|
69
|
+
* block's own data. On failure the list is empty and `error` is set; the hook
|
|
70
|
+
* never throws.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const { reviews, summary, isLoading } = useReviews({ scope: 'product', product_slug: 'oak-chair', limit: 6 });
|
|
74
|
+
*/
|
|
65
75
|
export function useReviews(options: ReviewsBlockData): {
|
|
66
76
|
reviews: ReviewItem[];
|
|
67
77
|
summary: ReviewsSummary;
|
|
68
78
|
isLoading: boolean;
|
|
79
|
+
error: Error | null;
|
|
69
80
|
} {
|
|
70
81
|
const { vendor, previewData } = useStorefront();
|
|
71
82
|
const locale = useStorefrontLocale();
|
|
@@ -73,6 +84,7 @@ export function useReviews(options: ReviewsBlockData): {
|
|
|
73
84
|
const [reviews, setReviews] = useState<ReviewItem[]>(seeded);
|
|
74
85
|
const [summary, setSummary] = useState<ReviewsSummary>(options._preview?.summary ?? summarize(seeded));
|
|
75
86
|
const [isLoading, setIsLoading] = useState(!options._preview && !previewData);
|
|
87
|
+
const [error, setError] = useState<Error | null>(null);
|
|
76
88
|
|
|
77
89
|
const {
|
|
78
90
|
scope,
|
|
@@ -114,6 +126,7 @@ export function useReviews(options: ReviewsBlockData): {
|
|
|
114
126
|
|
|
115
127
|
async function run(): Promise<void> {
|
|
116
128
|
setIsLoading(true);
|
|
129
|
+
setError(null);
|
|
117
130
|
try {
|
|
118
131
|
let path = '/reviews';
|
|
119
132
|
if (ids && ids.length > 0) {
|
|
@@ -136,10 +149,11 @@ export function useReviews(options: ReviewsBlockData): {
|
|
|
136
149
|
setReviews(response.data.reviews ?? []);
|
|
137
150
|
setSummary(response.data.summary ?? EMPTY_SUMMARY);
|
|
138
151
|
}
|
|
139
|
-
} catch {
|
|
152
|
+
} catch (caught) {
|
|
140
153
|
if (!cancelled) {
|
|
141
154
|
setReviews([]);
|
|
142
155
|
setSummary(EMPTY_SUMMARY);
|
|
156
|
+
setError(toError(caught));
|
|
143
157
|
}
|
|
144
158
|
} finally {
|
|
145
159
|
if (!cancelled) setIsLoading(false);
|
|
@@ -166,5 +180,5 @@ export function useReviews(options: ReviewsBlockData): {
|
|
|
166
180
|
locale,
|
|
167
181
|
]);
|
|
168
182
|
|
|
169
|
-
return { reviews, summary, isLoading };
|
|
183
|
+
return { reviews, summary, isLoading, error };
|
|
170
184
|
}
|
package/hooks/use-search.ts
CHANGED
|
@@ -4,12 +4,22 @@ import { useMemo } from 'react';
|
|
|
4
4
|
import type { Product } from '../types/product';
|
|
5
5
|
import type { Collection } from '../types/page';
|
|
6
6
|
|
|
7
|
+
/** What {@link useSearch} returns: the matches to show in a search panel. */
|
|
7
8
|
export interface SearchResults {
|
|
8
9
|
products: Product[];
|
|
9
10
|
categories: Array<[slug: string, name: string]>;
|
|
10
11
|
collections: Collection[];
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Instant search over products and collections you already hold (no request).
|
|
16
|
+
* An empty query returns the first products and categories as suggestions.
|
|
17
|
+
* Matches are case-insensitive on product title, category name and collection
|
|
18
|
+
* name, capped at 5 products, 4 categories and 2 collections.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const { products, categories, collections } = useSearch(query, allProducts, allCollections);
|
|
22
|
+
*/
|
|
13
23
|
export function useSearch(
|
|
14
24
|
query: string,
|
|
15
25
|
products: Product[],
|
package/hooks/use-shop-cart.ts
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
4
|
import { useCartStore } from '../stores/cart-store';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The cart lines for one shop, with the unit count and the total (in the
|
|
8
|
+
* currency's major unit, before delivery and discounts). Pass the shop id from
|
|
9
|
+
* `product.shop_id`; a missing id gives an empty cart.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const { items, count, total } = useShopCart(product.shop_id);
|
|
13
|
+
*/
|
|
6
14
|
export function useShopCart(shopId: string | null | undefined): {
|
|
7
15
|
items: ReturnType<typeof useCartStore.getState>['items'];
|
|
8
16
|
count: number;
|
package/hooks/use-shop.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { useStorefront, useStorefrontLocale } from '../provider';
|
|
|
5
5
|
import type { Product, ApiResponse } from '../types/product';
|
|
6
6
|
import { createBrowserClient } from '../sdk/client';
|
|
7
7
|
import { localeCacheKeySegment, withLocaleQuery } from '../utils/locale';
|
|
8
|
+
import { toError } from '../utils/to-error';
|
|
8
9
|
import {
|
|
9
10
|
SHOP_DEFAULT_PER_PAGE,
|
|
10
11
|
normalizeShopQuery,
|
|
@@ -78,11 +79,18 @@ function filterProducts(
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
82
|
+
* The shop page's catalogue: one page of products for a category, keyword,
|
|
83
|
+
* sort and page number. Reads the live products endpoint in production. In
|
|
84
|
+
* preview (when the provider carries `previewData.products`) it filters, sorts
|
|
85
|
+
* and paginates the demo catalogue in memory, so themes render real cards.
|
|
84
86
|
*
|
|
85
|
-
*
|
|
87
|
+
* The page is controlled by the caller; pair this with `useShopParams` to keep
|
|
88
|
+
* it in the URL. On failure the previous page of products stays on screen and
|
|
89
|
+
* `error` is set; the hook never throws.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* const { categorySlug, keyword, sort, page } = useShopParams();
|
|
93
|
+
* const { products, isLoading, pagination, error } = useShop({ categorySlug, keyword, sort, page });
|
|
86
94
|
*/
|
|
87
95
|
export function useShop(options?: {
|
|
88
96
|
categorySlug?: string | null;
|
|
@@ -94,6 +102,7 @@ export function useShop(options?: {
|
|
|
94
102
|
products: Product[];
|
|
95
103
|
isLoading: boolean;
|
|
96
104
|
pagination: ShopPagination;
|
|
105
|
+
error: Error | null;
|
|
97
106
|
} {
|
|
98
107
|
const { vendor, previewData } = useStorefront();
|
|
99
108
|
const locale = useStorefrontLocale();
|
|
@@ -123,6 +132,7 @@ export function useShop(options?: {
|
|
|
123
132
|
|
|
124
133
|
const [products, setProducts] = useState<Product[]>(seed?.products ?? []);
|
|
125
134
|
const [isLoading, setIsLoading] = useState(!isPreview && !seed);
|
|
135
|
+
const [error, setError] = useState<Error | null>(null);
|
|
126
136
|
const [pagination, setPagination] = useState<ShopPagination>(seed?.pagination ?? { ...EMPTY_PAGINATION, perPage });
|
|
127
137
|
// The key whose results are already in state. Seeded with the prefetch so
|
|
128
138
|
// the first effect run does not refetch the page the server just rendered
|
|
@@ -166,6 +176,7 @@ export function useShop(options?: {
|
|
|
166
176
|
// a newer one (filters B) had reset it, overwriting B's products.
|
|
167
177
|
let cancelled = false;
|
|
168
178
|
setIsLoading(true);
|
|
179
|
+
setError(null);
|
|
169
180
|
|
|
170
181
|
const client = createBrowserClient(vendor.slug ?? undefined);
|
|
171
182
|
const current = JSON.parse(queryKey) as typeof query;
|
|
@@ -182,6 +193,8 @@ export function useShop(options?: {
|
|
|
182
193
|
setPagination(parsed.pagination);
|
|
183
194
|
setProducts(parsed.products);
|
|
184
195
|
loadedQueryRef.current = fetchKey;
|
|
196
|
+
} catch (caught) {
|
|
197
|
+
if (!cancelled) setError(toError(caught));
|
|
185
198
|
} finally {
|
|
186
199
|
if (!cancelled) setIsLoading(false);
|
|
187
200
|
}
|
|
@@ -195,8 +208,8 @@ export function useShop(options?: {
|
|
|
195
208
|
}, [isPreview, vendor.slug, queryKey, fetchKey, locale]);
|
|
196
209
|
|
|
197
210
|
if (isPreview && previewResult) {
|
|
198
|
-
return { products: previewResult.products, isLoading: false, pagination: previewResult.pagination };
|
|
211
|
+
return { products: previewResult.products, isLoading: false, pagination: previewResult.pagination, error: null };
|
|
199
212
|
}
|
|
200
213
|
|
|
201
|
-
return { products, isLoading, pagination };
|
|
214
|
+
return { products, isLoading, pagination, error };
|
|
202
215
|
}
|
|
@@ -25,25 +25,6 @@ export interface VariantSelection {
|
|
|
25
25
|
activeHasDiscount: boolean;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* The option-selection state machine every theme's product page used to
|
|
30
|
-
* hand-roll — six near-identical copies, all of which gated their add button
|
|
31
|
-
* on stock and addons but never on a resolved variant.
|
|
32
|
-
*
|
|
33
|
-
* Two things it fixes that the copies got wrong:
|
|
34
|
-
*
|
|
35
|
-
* 1. It seeds from a real variant (first in-stock, else first) rather than
|
|
36
|
-
* from the first value of each option independently. On a sparse matrix —
|
|
37
|
-
* Red/S is not stocked but Red/M and Blue/S are — first-value-of-each
|
|
38
|
-
* produces a combination that matches nothing, so the page opened with an
|
|
39
|
-
* unresolvable selection.
|
|
40
|
-
* 2. It exposes `variantSatisfied`, so a selection that resolves to nothing
|
|
41
|
-
* (mid-change, or that sparse seed) disables the add instead of sending a
|
|
42
|
-
* line the backend rejects.
|
|
43
|
-
*
|
|
44
|
-
* Themes keep their own option markup and call `selectOption` — core owns the
|
|
45
|
-
* state, the theme owns the design.
|
|
46
|
-
*/
|
|
47
28
|
/**
|
|
48
29
|
* The opening selection. Seeded from a variant that actually exists, so the
|
|
49
30
|
* initial state is always resolvable — preferring one in stock, since opening
|
|
@@ -85,6 +66,29 @@ export function resolveVariantState(
|
|
|
85
66
|
};
|
|
86
67
|
}
|
|
87
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The option-selection state machine every theme's product page used to
|
|
71
|
+
* hand-roll, and which gated the add button on stock and add-ons but never on a
|
|
72
|
+
* resolved variant.
|
|
73
|
+
*
|
|
74
|
+
* It does two things a hand-rolled version tends to get wrong:
|
|
75
|
+
*
|
|
76
|
+
* 1. It seeds from a real variant (first in-stock, else first) rather than
|
|
77
|
+
* from the first value of each option independently. On a sparse matrix —
|
|
78
|
+
* Red/S is not stocked but Red/M and Blue/S are — first-value-of-each
|
|
79
|
+
* produces a combination that matches nothing, so the page opened with an
|
|
80
|
+
* unresolvable selection.
|
|
81
|
+
* 2. It exposes `variantSatisfied`, so a selection that resolves to nothing
|
|
82
|
+
* (mid-change, or that sparse seed) disables the add instead of sending a
|
|
83
|
+
* line the backend rejects.
|
|
84
|
+
*
|
|
85
|
+
* Themes keep their own option markup and call `selectOption`: the kit owns the
|
|
86
|
+
* state, the theme owns the design.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* const { selectedOptions, selectOption, variantSatisfied, activePrice } = useVariantSelection(product);
|
|
90
|
+
* <button disabled={!variantSatisfied || !activeInStock}>Add to cart</button>
|
|
91
|
+
*/
|
|
88
92
|
export function useVariantSelection(product: Product): VariantSelection {
|
|
89
93
|
const [selectedOptions, setSelectedOptions] = useState<Record<string, string>>(() => seedVariantOptions(product));
|
|
90
94
|
|
package/index.ts
CHANGED
|
@@ -11,19 +11,18 @@
|
|
|
11
11
|
* Two entries because the split is real, not cosmetic: this one is types and
|
|
12
12
|
* pure functions, importable from anywhere including node tooling; `client.ts`
|
|
13
13
|
* is React state and components and needs a client boundary at the consumer.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Modules that call `next/headers` (`sdk/server-store-client.ts` and the
|
|
15
|
+
* `api/collections.ts` and `api/shop.ts` fetchers built on it) are server-only:
|
|
16
|
+
* import them from server components, never from a client module.
|
|
16
17
|
*
|
|
17
18
|
* THESE ARE A DECLARATION, NOT AN IMPORT PATH. Do not migrate themes onto them.
|
|
18
|
-
* Pointing a theme's files at `client.ts`
|
|
19
|
-
* imports theme components under tsx (a theme registry generator, for
|
|
20
|
-
* would reach a gallery block through the barrel, which transitively
|
|
21
|
-
* SDK, and the SDK does not resolve outside a bundler.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* these barrels stay the machine-checkable statement of what is in the public
|
|
26
|
-
* surface.
|
|
19
|
+
* Pointing a theme's files at `client.ts` can fail the build outright: tooling
|
|
20
|
+
* that imports theme components under `tsx` (a theme registry generator, for
|
|
21
|
+
* example) would reach a gallery block through the barrel, which transitively
|
|
22
|
+
* pulls the SDK, and the SDK does not resolve outside a bundler. A barrel also
|
|
23
|
+
* makes every client module reachable from one entry, which bloats each theme
|
|
24
|
+
* chunk. Themes import the exact module they use; these barrels stay the
|
|
25
|
+
* machine-checkable statement of what is in the public surface.
|
|
27
26
|
*/
|
|
28
27
|
|
|
29
28
|
export * from './types/theme';
|
package/locales/en.default.json
CHANGED
package/navigation.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import NextLink from 'next/link';
|
|
|
15
15
|
import { usePathname as useFrameworkPathname, useRouter as useFrameworkRouter } from 'next/navigation';
|
|
16
16
|
import { useMemo, type AnchorHTMLAttributes, type ReactNode, type Ref } from 'react';
|
|
17
17
|
|
|
18
|
+
/** Props for {@link Link}: any anchor attribute except `href`, plus `prefetch` and `replace`. */
|
|
18
19
|
export interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
|
|
19
20
|
/** A path inside the store (`menuItemToHref()`, `useHref()`) or a full URL. */
|
|
20
21
|
href: string;
|
|
@@ -31,6 +32,7 @@ export function Link({ href, prefetch, replace, ...anchor }: LinkProps) {
|
|
|
31
32
|
return <NextLink href={href} prefetch={prefetch} replace={replace} {...anchor} />;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/** What {@link useRouter} returns. */
|
|
34
36
|
export interface Router {
|
|
35
37
|
/** Go to `href`, adding a history entry. */
|
|
36
38
|
push(href: string): void;
|
package/package.json
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usequeek/theme-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"description": "The
|
|
6
|
+
"description": "The runtime a Queek storefront theme is built on: hooks, stores, page renderer, navigation, shared blocks and locale strings.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"queek",
|
|
9
|
+
"storefront",
|
|
10
|
+
"theme",
|
|
11
|
+
"themes",
|
|
12
|
+
"react",
|
|
13
|
+
"nextjs",
|
|
14
|
+
"commerce",
|
|
15
|
+
"ecommerce"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://docs.usequeek.com/docs/themes",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/usequeek/theme-kit/issues"
|
|
20
|
+
},
|
|
21
|
+
"author": "Queekx Technology LTD",
|
|
7
22
|
"repository": {
|
|
8
23
|
"type": "git",
|
|
9
24
|
"url": "git+https://github.com/usequeek/theme-kit.git"
|
|
@@ -25,25 +40,31 @@
|
|
|
25
40
|
"**/*.css",
|
|
26
41
|
"locales/*.json",
|
|
27
42
|
"README.md",
|
|
28
|
-
"LICENSE"
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"CHANGELOG.md",
|
|
45
|
+
"!tests",
|
|
46
|
+
"!scripts"
|
|
29
47
|
],
|
|
30
48
|
"peerDependencies": {
|
|
49
|
+
"@usequeek/client-sdk": "^0.4.0",
|
|
31
50
|
"next": ">=15",
|
|
32
51
|
"react": ">=19",
|
|
33
|
-
"react-dom": ">=19"
|
|
34
|
-
|
|
52
|
+
"react-dom": ">=19"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22"
|
|
35
56
|
},
|
|
36
57
|
"dependencies": {
|
|
37
|
-
"
|
|
58
|
+
"html-to-image": "^1.11.0",
|
|
38
59
|
"react-markdown": "^10.0.0",
|
|
39
60
|
"remark-gfm": "^4.0.0",
|
|
40
|
-
"
|
|
61
|
+
"zustand": "^5.0.11"
|
|
41
62
|
},
|
|
42
63
|
"devDependencies": {
|
|
43
|
-
"@queekai/client-sdk": "^0.3.0",
|
|
44
64
|
"@types/node": "^20",
|
|
45
65
|
"@types/react": "^19",
|
|
46
66
|
"@types/react-dom": "^19",
|
|
67
|
+
"@usequeek/client-sdk": "^0.4.0",
|
|
47
68
|
"jsdom": "^28.1.0",
|
|
48
69
|
"next": "^16.0.0",
|
|
49
70
|
"react": "^19.2.3",
|
package/page-renderer.tsx
CHANGED
|
@@ -65,6 +65,17 @@ function signatureReplacer(_key: string, value: unknown): unknown {
|
|
|
65
65
|
return value;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Renders a page's blocks in order, choosing each block's theme variant (or
|
|
70
|
+
* the kit's shared block). Block data arrives already resolved: a theme never
|
|
71
|
+
* fetches it. While a merchant edits, a draft section that cannot render shows
|
|
72
|
+
* a short "can't be previewed yet" notice instead of blanking the page.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* export default function HomePage({ blocks }: HomePageProps) {
|
|
76
|
+
* return <PageRenderer blocks={blocks} />;
|
|
77
|
+
* }
|
|
78
|
+
*/
|
|
68
79
|
export function PageRenderer({ blocks }: { blocks: Block[] }): JSX.Element {
|
|
69
80
|
const draftBlocks = useEditPreviewStore((state) => state.blocks);
|
|
70
81
|
const reconcileDraftBlocks = useEditPreviewStore((state) => state.reconcile);
|
package/provider.tsx
CHANGED
|
@@ -61,8 +61,23 @@ interface StorefrontContextValue {
|
|
|
61
61
|
locales?: StoreLocale[];
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/** The context behind {@link StorefrontProvider}. Read it through the hooks; use it directly only to test for a provider without throwing. */
|
|
64
65
|
export const StorefrontContext = createContext<StorefrontContextValue | null>(null);
|
|
65
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Gives every kit hook and component the store they render for: its profile,
|
|
69
|
+
* configuration, menus, locale, languages and strings. The Queek storefront
|
|
70
|
+
* mounts it for every theme, so a theme never renders it. Render it yourself
|
|
71
|
+
* only in a test, a preview or a script.
|
|
72
|
+
*
|
|
73
|
+
* `basePath` defaults to `/<vendor.slug>`; pass `''` for a store served on its
|
|
74
|
+
* own domain. Brand design tokens become CSS variables on a wrapping element.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* <StorefrontProvider vendor={vendor} config={config} menus={[]}>
|
|
78
|
+
* <MySection />
|
|
79
|
+
* </StorefrontProvider>
|
|
80
|
+
*/
|
|
66
81
|
export function StorefrontProvider({
|
|
67
82
|
vendor,
|
|
68
83
|
config,
|
|
@@ -147,11 +162,22 @@ export function StorefrontProvider({
|
|
|
147
162
|
);
|
|
148
163
|
}
|
|
149
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The current store: `vendor`, `config`, `menus`, `basePath`, `locale` and the
|
|
167
|
+
* rest of what the host passed to {@link StorefrontProvider}.
|
|
168
|
+
*
|
|
169
|
+
* @throws {Error} when called outside a `StorefrontProvider`.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* const { vendor, config } = useStorefront();
|
|
173
|
+
*/
|
|
150
174
|
export function useStorefront(): StorefrontContextValue {
|
|
151
175
|
const context = useContext(StorefrontContext);
|
|
152
176
|
|
|
153
177
|
if (!context) {
|
|
154
|
-
throw new Error(
|
|
178
|
+
throw new Error(
|
|
179
|
+
'useStorefront() was called outside <StorefrontProvider>. Render the component inside StorefrontProvider (the Queek storefront does this for every theme). In a test, wrap it yourself.',
|
|
180
|
+
);
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
return context;
|
|
@@ -167,7 +193,7 @@ export function useStorefrontLocale(): string | null {
|
|
|
167
193
|
const context = useContext(StorefrontContext);
|
|
168
194
|
|
|
169
195
|
if (!context) {
|
|
170
|
-
throw new Error('useStorefrontLocale
|
|
196
|
+
throw new Error('useStorefrontLocale() was called outside <StorefrontProvider>. Render the component inside StorefrontProvider.');
|
|
171
197
|
}
|
|
172
198
|
|
|
173
199
|
return context.locale ?? null;
|
|
@@ -188,7 +214,7 @@ export function useThemeStrings(): ThemeStringsFn {
|
|
|
188
214
|
const context = useContext(StorefrontContext);
|
|
189
215
|
|
|
190
216
|
if (!context) {
|
|
191
|
-
throw new Error('useThemeStrings
|
|
217
|
+
throw new Error('useThemeStrings() was called outside <StorefrontProvider>. Render the component inside StorefrontProvider.');
|
|
192
218
|
}
|
|
193
219
|
|
|
194
220
|
const theme = useOptionalTheme();
|