@usequeek/theme-kit 0.1.0
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/README.md +37 -0
- package/api/collections.ts +93 -0
- package/api/orders.ts +23 -0
- package/apps/analytics-events.ts +32 -0
- package/apps/apps.css +613 -0
- package/apps/follow-card.tsx +192 -0
- package/apps/gtag.ts +19 -0
- package/apps/subscribe-form.tsx +124 -0
- package/client.ts +52 -0
- package/components/auth/auth-email-login-step.tsx +46 -0
- package/components/auth/auth-email-otp-step.tsx +54 -0
- package/components/auth/auth-email-otp-verify-step.tsx +82 -0
- package/components/auth/auth-email-register-step.tsx +59 -0
- package/components/auth/auth-flow.tsx +26 -0
- package/components/auth/auth-signup-step.tsx +53 -0
- package/components/blog/category-filter.tsx +92 -0
- package/components/blog/index.ts +12 -0
- package/components/blog/pagination.tsx +73 -0
- package/components/blog/post-categories.tsx +42 -0
- package/components/blog/post-meta.tsx +49 -0
- package/components/blog/related-posts.tsx +54 -0
- package/components/blog/share-buttons.tsx +89 -0
- package/components/cart/cart-panel-controller.tsx +67 -0
- package/components/cart/default-cart-shell.tsx +207 -0
- package/components/checkout/checkout-contact.tsx +84 -0
- package/components/checkout/default-checkout-shell.tsx +177 -0
- package/components/checkout/policy-links.tsx +98 -0
- package/components/image.tsx +149 -0
- package/components/markdown.tsx +54 -0
- package/components/product-media-frame.tsx +56 -0
- package/components/variant-picker.css +84 -0
- package/components/variant-picker.tsx +107 -0
- package/design-token-preview.tsx +81 -0
- package/edit-mode-active.tsx +324 -0
- package/edit-mode.tsx +80 -0
- package/hooks/use-address-autocomplete.ts +62 -0
- package/hooks/use-auth-flow.tsx +346 -0
- package/hooks/use-auth.ts +17 -0
- package/hooks/use-cart.ts +14 -0
- package/hooks/use-categories.ts +112 -0
- package/hooks/use-collection-params.ts +67 -0
- package/hooks/use-href.ts +15 -0
- package/hooks/use-posts.ts +75 -0
- package/hooks/use-product-detail.ts +69 -0
- package/hooks/use-product-questions.ts +77 -0
- package/hooks/use-product-sections.ts +165 -0
- package/hooks/use-products.ts +216 -0
- package/hooks/use-related-products.ts +56 -0
- package/hooks/use-resolve-coordinates.ts +48 -0
- package/hooks/use-reviews.ts +167 -0
- package/hooks/use-search.ts +55 -0
- package/hooks/use-shop-cart.ts +31 -0
- package/hooks/use-shop-params.ts +68 -0
- package/hooks/use-shop.ts +186 -0
- package/hooks/use-variant-selection.ts +98 -0
- package/index.ts +49 -0
- package/package.json +41 -0
- package/page-renderer.tsx +365 -0
- package/provider.tsx +91 -0
- package/sdk/client.ts +221 -0
- package/sdk/error-handler.ts +37 -0
- package/sdk/platform.ts +78 -0
- package/sdk/queek-client.ts +104 -0
- package/sdk/reauth-guard.ts +27 -0
- package/sdk/server-store-client.ts +165 -0
- package/shared-blocks/button.tsx +41 -0
- package/shared-blocks/callout.tsx +35 -0
- package/shared-blocks/content-default.tsx +23 -0
- package/shared-blocks/core-blocks.css +1792 -0
- package/shared-blocks/divider.tsx +17 -0
- package/shared-blocks/embed.tsx +40 -0
- package/shared-blocks/faq.tsx +44 -0
- package/shared-blocks/image.tsx +29 -0
- package/shared-blocks/index.ts +24 -0
- package/shared-blocks/manifest.ts +76 -0
- package/shared-blocks/product-qa.tsx +203 -0
- package/shared-blocks/quote.tsx +20 -0
- package/shared-blocks/reviews.tsx +436 -0
- package/shared-blocks/table.tsx +45 -0
- package/shared-blocks/video.tsx +60 -0
- package/stores/address-modal-store.ts +15 -0
- package/stores/auth-modal-store.ts +17 -0
- package/stores/cart-panel-store.ts +17 -0
- package/stores/cart-store.ts +172 -0
- package/stores/checkout-store.ts +167 -0
- package/stores/edit-preview-store.ts +61 -0
- package/stores/location-store.ts +41 -0
- package/stores/orders-sheet-store.ts +21 -0
- package/stores/policy-modal-store.ts +15 -0
- package/stores/product-modal-store.ts +18 -0
- package/stores/search-modal-store.ts +17 -0
- package/stores/toast-store.ts +46 -0
- package/stores/user-store.ts +88 -0
- package/theme-context.tsx +28 -0
- package/theme-variant-resolver.ts +44 -0
- package/types/block.ts +292 -0
- package/types/cart.ts +21 -0
- package/types/media.ts +55 -0
- package/types/menu.ts +27 -0
- package/types/order.ts +119 -0
- package/types/page.ts +86 -0
- package/types/product.ts +279 -0
- package/types/theme.ts +380 -0
- package/types/vendor.ts +243 -0
- package/utils/addon-selection.ts +31 -0
- package/utils/benefit-icon.tsx +145 -0
- package/utils/brand-palette.ts +64 -0
- package/utils/brand.ts +518 -0
- package/utils/contact-location.ts +44 -0
- package/utils/format.ts +64 -0
- package/utils/hero-content.ts +35 -0
- package/utils/image-srcset.ts +157 -0
- package/utils/markdown.tsx +237 -0
- package/utils/match-variant.ts +31 -0
- package/utils/menu-link.ts +41 -0
- package/utils/placeholder-resolver.ts +184 -0
- package/utils/product-media.ts +77 -0
- package/utils/product-normalizer.ts +323 -0
- package/utils/vendor-host.ts +173 -0
- package/utils/video-embed.ts +38 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
type VariantComponent = FC<any>;
|
|
4
|
+
|
|
5
|
+
export type ThemeVariantImplementations = Record<string, Record<string, VariantComponent>>;
|
|
6
|
+
export type LegacyBlockImplementations = Record<string, Record<string, VariantComponent>>;
|
|
7
|
+
|
|
8
|
+
function resolveVariant<T extends VariantComponent>(
|
|
9
|
+
implementations: ThemeVariantImplementations,
|
|
10
|
+
scope: string,
|
|
11
|
+
variant: string | undefined,
|
|
12
|
+
fallback: T,
|
|
13
|
+
): T {
|
|
14
|
+
if (!variant) return fallback;
|
|
15
|
+
return (implementations[scope]?.[variant] as T | undefined) ?? fallback;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveThemeHeader<T extends VariantComponent>(
|
|
19
|
+
implementations: ThemeVariantImplementations,
|
|
20
|
+
variant: string | undefined,
|
|
21
|
+
fallback: T,
|
|
22
|
+
): T {
|
|
23
|
+
return resolveVariant(implementations, 'header', variant, fallback);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveThemeFooter<T extends VariantComponent>(
|
|
27
|
+
implementations: ThemeVariantImplementations,
|
|
28
|
+
variant: string | undefined,
|
|
29
|
+
fallback: T,
|
|
30
|
+
): T {
|
|
31
|
+
return resolveVariant(implementations, 'footer', variant, fallback);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveThemeBlock<T extends VariantComponent>(
|
|
35
|
+
implementations: ThemeVariantImplementations,
|
|
36
|
+
type: string,
|
|
37
|
+
variant: string | undefined,
|
|
38
|
+
fallback: T,
|
|
39
|
+
legacyImplementations?: LegacyBlockImplementations,
|
|
40
|
+
): T {
|
|
41
|
+
if (!variant) return fallback;
|
|
42
|
+
const implementation = implementations[type]?.[variant] ?? legacyImplementations?.[type]?.[variant];
|
|
43
|
+
return (implementation as T | undefined) ?? fallback;
|
|
44
|
+
}
|
package/types/block.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import type { DesignTokens } from './vendor';
|
|
2
|
+
import type { Product } from './product';
|
|
3
|
+
|
|
4
|
+
export type BlockType =
|
|
5
|
+
| 'content'
|
|
6
|
+
| 'image'
|
|
7
|
+
| 'gallery'
|
|
8
|
+
| 'video'
|
|
9
|
+
| 'table'
|
|
10
|
+
| 'button'
|
|
11
|
+
| 'embed'
|
|
12
|
+
| 'divider'
|
|
13
|
+
| 'callout'
|
|
14
|
+
| 'quote'
|
|
15
|
+
| 'products'
|
|
16
|
+
| 'categories'
|
|
17
|
+
| 'contact'
|
|
18
|
+
| 'reviews'
|
|
19
|
+
| 'faq'
|
|
20
|
+
| 'product_qa'
|
|
21
|
+
| 'blog';
|
|
22
|
+
|
|
23
|
+
export interface ContentBlockData {
|
|
24
|
+
markdown: string;
|
|
25
|
+
/** Marquee variant: explicit items list set in admin (preferred over markdown). */
|
|
26
|
+
items?: string[];
|
|
27
|
+
/** Optional per-block background colour (e.g. marquee, promo). */
|
|
28
|
+
bg_color?: string;
|
|
29
|
+
/** Optional per-block text colour. */
|
|
30
|
+
text_color?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ImageBlockData {
|
|
34
|
+
url: string;
|
|
35
|
+
alt?: string | null;
|
|
36
|
+
caption?: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface GalleryBlockData {
|
|
40
|
+
/** Optional section copy (e.g. for the social-feed / insta-gallery variant). */
|
|
41
|
+
heading?: string | null;
|
|
42
|
+
description?: string | null;
|
|
43
|
+
/** Block-level (not per-slide) hero display controls — currently only
|
|
44
|
+
* glow's 'slider'/'thumb-rail' variants read these; harmless no-op
|
|
45
|
+
* elsewhere. */
|
|
46
|
+
zoom_effect?: boolean | null;
|
|
47
|
+
overlay?: 'none' | 'light' | 'medium' | 'strong' | null;
|
|
48
|
+
content_position?: 'bottom-right' | 'bottom-left' | 'bottom-center' | 'center' | null;
|
|
49
|
+
images: Array<{
|
|
50
|
+
url: string;
|
|
51
|
+
alt?: string | null;
|
|
52
|
+
caption?: string | null;
|
|
53
|
+
title?: string | null;
|
|
54
|
+
subtitle?: string | null;
|
|
55
|
+
link?: string | null;
|
|
56
|
+
cta_label?: string | null;
|
|
57
|
+
cta_url?: string | null;
|
|
58
|
+
/** Product ids featured in this image — lets a theme's social/UGC gallery
|
|
59
|
+
* variant open a shoppable modal on click instead of just linking out. */
|
|
60
|
+
product_ids?: string[] | null;
|
|
61
|
+
/** YouTube/Vimeo/direct file URL — takes over from `url` as the slide's
|
|
62
|
+
* media when set, with `url` still used as the poster/fallback. Only
|
|
63
|
+
* takes effect on themes/variants that render a single full-bleed slide
|
|
64
|
+
* (e.g. glow's `slider`/`thumb-rail` with exactly 1 image) — a
|
|
65
|
+
* multi-slide carousel would autoplay every slide's video concurrently
|
|
66
|
+
* since inactive slides stay mounted, not unmounted. */
|
|
67
|
+
video?: string | null;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface VideoBlockData {
|
|
72
|
+
url: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface TableBlockData {
|
|
76
|
+
headers: string[];
|
|
77
|
+
rows: string[][];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ButtonBlockData {
|
|
81
|
+
text: string;
|
|
82
|
+
url: string;
|
|
83
|
+
bg_color?: string;
|
|
84
|
+
text_color?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface EmbedBlockData {
|
|
88
|
+
url: string;
|
|
89
|
+
provider?: string | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface DividerBlockData {
|
|
93
|
+
style?: 'line' | 'space';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type CalloutTone = 'info' | 'warn' | 'success' | 'note';
|
|
97
|
+
|
|
98
|
+
export interface CalloutBlockData {
|
|
99
|
+
markdown: string;
|
|
100
|
+
tone?: CalloutTone;
|
|
101
|
+
title?: string | null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface QuoteBlockData {
|
|
105
|
+
/** The quote body — plain text or short markdown. */
|
|
106
|
+
markdown?: string;
|
|
107
|
+
/** Legacy/alternate field name for the quote text. */
|
|
108
|
+
text?: string;
|
|
109
|
+
/** Attribution line (author, source). */
|
|
110
|
+
cite?: string | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface ProductsBlockData {
|
|
114
|
+
collection?: string | null;
|
|
115
|
+
ids?: string[];
|
|
116
|
+
sort?: 'latest' | 'popular' | 'price_low' | 'price_high';
|
|
117
|
+
limit?: number;
|
|
118
|
+
title?: string | null;
|
|
119
|
+
/** Single-product spotlight only — merchant-authored supporting copy, never product facts. */
|
|
120
|
+
description?: string | null;
|
|
121
|
+
/** Single-product spotlight only — label for the derived product route. */
|
|
122
|
+
cta_label?: string | null;
|
|
123
|
+
/**
|
|
124
|
+
* Single-product spotlight only — where the product image sits relative to
|
|
125
|
+
* the copy. 'left'/'right' set them side-by-side (mirrored), 'top' stacks a
|
|
126
|
+
* full-width image above centred copy. Same vocabulary as content/brand-story
|
|
127
|
+
* so a repeated spotlight can alternate instead of rendering three identical
|
|
128
|
+
* rows. Layout only — it never re-authors a derived product fact.
|
|
129
|
+
*/
|
|
130
|
+
align?: 'left' | 'right' | 'top';
|
|
131
|
+
layout?: 'grid' | 'carousel' | 'list' | 'featured';
|
|
132
|
+
/** 'featured' variant only. Default true — hides the product excerpt/description. */
|
|
133
|
+
show_description?: boolean;
|
|
134
|
+
/** 'featured' variant only. Default 'top' — vertical alignment of the gallery/info columns. */
|
|
135
|
+
content_align?: 'top' | 'center' | 'bottom';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface CategoriesBlockData {
|
|
139
|
+
parent?: string | null;
|
|
140
|
+
ids?: string[];
|
|
141
|
+
limit?: number;
|
|
142
|
+
title?: string | null;
|
|
143
|
+
layout?: 'grid' | 'carousel';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface ContactSocialLink {
|
|
147
|
+
label: string;
|
|
148
|
+
url: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ContactBlockData {
|
|
152
|
+
heading?: string;
|
|
153
|
+
info_heading?: string;
|
|
154
|
+
description?: string;
|
|
155
|
+
email?: string;
|
|
156
|
+
phone?: string;
|
|
157
|
+
address?: string;
|
|
158
|
+
hours?: string;
|
|
159
|
+
socials?: ContactSocialLink[];
|
|
160
|
+
cta_label?: string;
|
|
161
|
+
cta_url?: string;
|
|
162
|
+
map_embed_url?: string;
|
|
163
|
+
map_lat?: number | string | null;
|
|
164
|
+
map_lng?: number | string | null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface ReviewMediaItem {
|
|
168
|
+
type: 'image' | 'video';
|
|
169
|
+
url: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface ReviewItem {
|
|
173
|
+
id: string;
|
|
174
|
+
rating: number;
|
|
175
|
+
title?: string | null;
|
|
176
|
+
description?: string | null;
|
|
177
|
+
media?: ReviewMediaItem[];
|
|
178
|
+
is_verified_purchase: boolean;
|
|
179
|
+
created_at: string;
|
|
180
|
+
edited_at?: string | null;
|
|
181
|
+
vendor_response?: string | null;
|
|
182
|
+
vendor_response_at?: string | null;
|
|
183
|
+
user: { id: string; name: string; avatar?: string | null };
|
|
184
|
+
product?: {
|
|
185
|
+
id: string;
|
|
186
|
+
slug: string;
|
|
187
|
+
title: string;
|
|
188
|
+
thumbnail?: string | null;
|
|
189
|
+
} | null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface ReviewsSummary {
|
|
193
|
+
average_rating: number;
|
|
194
|
+
total_reviews: number;
|
|
195
|
+
rating_breakdown: Record<string, number>;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface ReviewsBlockData {
|
|
199
|
+
scope?: 'vendor' | 'product' | 'ids';
|
|
200
|
+
product_slug?: string | null;
|
|
201
|
+
ids?: string[];
|
|
202
|
+
min_rating?: 1 | 2 | 3 | 4 | 5;
|
|
203
|
+
verified_only?: boolean;
|
|
204
|
+
with_media?: boolean;
|
|
205
|
+
limit?: number;
|
|
206
|
+
sort?: 'newest' | 'highest' | 'featured';
|
|
207
|
+
layout?: 'grid' | 'carousel' | 'wall' | 'compact' | 'spotlight' | 'minimal';
|
|
208
|
+
title?: string | null;
|
|
209
|
+
/** Optional inline preview data — used by theme preview / demo mode when backend is unavailable. */
|
|
210
|
+
_preview?: { summary?: ReviewsSummary; reviews?: ReviewItem[] };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface FaqItem {
|
|
214
|
+
question: string;
|
|
215
|
+
answer: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface FaqBlockData {
|
|
219
|
+
heading?: string | null;
|
|
220
|
+
items: FaqItem[];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface ProductQuestionItem {
|
|
224
|
+
id: string;
|
|
225
|
+
question: string;
|
|
226
|
+
answer: string | null;
|
|
227
|
+
status: 'answered';
|
|
228
|
+
created_at: string;
|
|
229
|
+
answered_at: string | null;
|
|
230
|
+
user: { id: string; name: string; avatar?: string | null };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface ProductQaBlockData {
|
|
234
|
+
product_slug: string;
|
|
235
|
+
limit?: number;
|
|
236
|
+
title?: string | null;
|
|
237
|
+
/** Optional inline preview data — used by theme preview / demo mode when backend is unavailable. */
|
|
238
|
+
_preview?: { questions?: ProductQuestionItem[] };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface BlogBlockData {
|
|
242
|
+
title?: string | null;
|
|
243
|
+
/** Blog category slug. Blank/absent = every published post ("use all"). */
|
|
244
|
+
category?: string | null;
|
|
245
|
+
limit?: number;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export type BlockDataMap = {
|
|
249
|
+
content: ContentBlockData;
|
|
250
|
+
image: ImageBlockData;
|
|
251
|
+
gallery: GalleryBlockData;
|
|
252
|
+
video: VideoBlockData;
|
|
253
|
+
table: TableBlockData;
|
|
254
|
+
button: ButtonBlockData;
|
|
255
|
+
embed: EmbedBlockData;
|
|
256
|
+
divider: DividerBlockData;
|
|
257
|
+
callout: CalloutBlockData;
|
|
258
|
+
quote: QuoteBlockData;
|
|
259
|
+
products: ProductsBlockData;
|
|
260
|
+
categories: CategoriesBlockData;
|
|
261
|
+
contact: ContactBlockData;
|
|
262
|
+
reviews: ReviewsBlockData;
|
|
263
|
+
faq: FaqBlockData;
|
|
264
|
+
product_qa: ProductQaBlockData;
|
|
265
|
+
blog: BlogBlockData;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export type Block<T extends BlockType = BlockType> = {
|
|
269
|
+
type: T;
|
|
270
|
+
data: BlockDataMap[T];
|
|
271
|
+
variant?: string;
|
|
272
|
+
/**
|
|
273
|
+
* Section-scoped design-token overrides — only the keys set here are
|
|
274
|
+
* applied; everything else inherits the global tokens via the CSS
|
|
275
|
+
* cascade. See `expandDesignTokensPartial` in `lib/core/utils/brand.ts`.
|
|
276
|
+
*/
|
|
277
|
+
tokens?: Partial<DesignTokens>;
|
|
278
|
+
/**
|
|
279
|
+
* Stable in-page-scroll target — a menu item's `meta.anchor` links here.
|
|
280
|
+
* Lowercase-kebab, enforced by the backend (ValidStorefrontBlocks).
|
|
281
|
+
*/
|
|
282
|
+
anchor_id?: string;
|
|
283
|
+
/**
|
|
284
|
+
* Server-seeded product data for `products` blocks — set by
|
|
285
|
+
* `hydrateProductBlocks` (lib/core/api/products.ts) before blocks reach
|
|
286
|
+
* `PageRenderer`, which awaits it via `use()` inside a per-block
|
|
287
|
+
* `<Suspense>` boundary. Lives on the block, not inside `data`, so it never
|
|
288
|
+
* gets spread into theme components as a prop. Absent for every other
|
|
289
|
+
* block type and for any route that hasn't been wired to hydrate.
|
|
290
|
+
*/
|
|
291
|
+
productsPromise?: Promise<Product[]>;
|
|
292
|
+
};
|
package/types/cart.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface CartAddon {
|
|
2
|
+
addon_id: string;
|
|
3
|
+
item_id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
price: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface CartItem {
|
|
9
|
+
id: string;
|
|
10
|
+
product_id: string;
|
|
11
|
+
shop_id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
slug?: string | null;
|
|
14
|
+
image: string | null;
|
|
15
|
+
unit_price: number;
|
|
16
|
+
quantity: number;
|
|
17
|
+
addons: CartAddon[];
|
|
18
|
+
variant_id?: string | null;
|
|
19
|
+
/** e.g. "Denim" — the selected variant's display name, when this line has one. */
|
|
20
|
+
variant_title?: string | null;
|
|
21
|
+
}
|
package/types/media.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The srcset-ready tier map emitted by the backend's MediaVariantMap
|
|
3
|
+
* (queek_backend: app/Services/Media/MediaVariantMap.php). Sits BESIDE the
|
|
4
|
+
* existing collapsed url fields (media.image, vendor.logo, collection.image, …),
|
|
5
|
+
* which are unchanged — this is purely additive.
|
|
6
|
+
*
|
|
7
|
+
* Contract (enforced backend-side, relied on here):
|
|
8
|
+
* 1. All 5 keys are ALWAYS present when the map itself is non-null.
|
|
9
|
+
* 2. A tier that doesn't exist is the WHOLE VALUE null — never a partial
|
|
10
|
+
* object, never a fallback url. Skip it; never synthesize a filename.
|
|
11
|
+
* 3. `original` is never null when the map is non-null. Modeled as
|
|
12
|
+
* `ImageVariantTier | null` anyway rather than a type-level invariant:
|
|
13
|
+
* making it non-nullable would buy zero runtime safety against a backend
|
|
14
|
+
* regression while forcing every consumer through a `!` or a cast. The
|
|
15
|
+
* invariant is documented, not encoded.
|
|
16
|
+
* 4. `w`/`h` are `min(tier_cap, original.width)` and the proportional height —
|
|
17
|
+
* so on a small original SEVERAL TIERS LEGITIMATELY SHARE A WIDTH. That is
|
|
18
|
+
* correct backend behavior, and the reason resolveImageSources() dedupes
|
|
19
|
+
* srcset candidates by width (duplicate `w` descriptors make a srcset
|
|
20
|
+
* non-deterministic).
|
|
21
|
+
* 5. `thumb` ALWAYS has `w: null, h: null` — it's a legacy 150→240px tier with
|
|
22
|
+
* a non-uniform crop history, so a computed width would misrepresent it.
|
|
23
|
+
* It must NEVER enter a srcset; it is a fixed `src` tier only.
|
|
24
|
+
* 6. The WHOLE map is null when the image isn't Media-backed (external/off-host
|
|
25
|
+
* url, SVG, some legacy uploads). Consumers fall back to the existing
|
|
26
|
+
* collapsed field, exactly as today.
|
|
27
|
+
*
|
|
28
|
+
* Tier caps (queek_backend config/media.php `variant_widths`):
|
|
29
|
+
* thumb ~240 (uncapped/legacy) · card 480 · card2x 800 · view 1600 · original raw
|
|
30
|
+
*/
|
|
31
|
+
export interface ImageVariantTier {
|
|
32
|
+
url: string;
|
|
33
|
+
/** Rendered width in px. null on `thumb` (always) and when the original has no
|
|
34
|
+
* recorded dimensions. A tier with a null `w` cannot enter a srcset. */
|
|
35
|
+
w: number | null;
|
|
36
|
+
h: number | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ImageVariantTierName = 'thumb' | 'card' | 'card2x' | 'view' | 'original';
|
|
40
|
+
|
|
41
|
+
export type ImageVariants = {
|
|
42
|
+
[K in ImageVariantTierName]: ImageVariantTier | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The flat url→variants sidecar on a Page/Post response
|
|
47
|
+
* (StorePageResource::toArray → 'media_index'). Keyed by the EXACT raw url
|
|
48
|
+
* string as it appears in that same response's `content[]` block JSON —
|
|
49
|
+
* block schemas were not changed, so a consumer looks up
|
|
50
|
+
* `mediaIndex[url] ?? null` per raw url it finds in block content.
|
|
51
|
+
*
|
|
52
|
+
* Serialized as `(object)` server-side: always present, `{}` when empty,
|
|
53
|
+
* NEVER null. Individual values can still be null (rule 6 above).
|
|
54
|
+
*/
|
|
55
|
+
export type MediaIndex = Record<string, ImageVariants | null>;
|
package/types/menu.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type MenuItemType =
|
|
2
|
+
| 'page'
|
|
3
|
+
| 'collection'
|
|
4
|
+
| 'collections'
|
|
5
|
+
| 'shop'
|
|
6
|
+
| 'blog'
|
|
7
|
+
| 'post'
|
|
8
|
+
| 'gallery'
|
|
9
|
+
| 'url'
|
|
10
|
+
| 'group';
|
|
11
|
+
|
|
12
|
+
export interface MenuItem {
|
|
13
|
+
label: string;
|
|
14
|
+
type: MenuItemType;
|
|
15
|
+
ref?: string | null;
|
|
16
|
+
/** `anchor`: for a `page` item, scroll straight to that section (its `anchor_id`) instead of just the top. */
|
|
17
|
+
meta?: (Record<string, unknown> & { anchor?: string | null }) | null;
|
|
18
|
+
children?: MenuItem[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Menu {
|
|
22
|
+
id: string;
|
|
23
|
+
location: string;
|
|
24
|
+
items: MenuItem[];
|
|
25
|
+
created_at?: string | null;
|
|
26
|
+
updated_at?: string | null;
|
|
27
|
+
}
|
package/types/order.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export interface OrderListItem {
|
|
2
|
+
id: string;
|
|
3
|
+
order_no: string;
|
|
4
|
+
status: string;
|
|
5
|
+
is_completed?: boolean;
|
|
6
|
+
total_price: number | string;
|
|
7
|
+
grand_total?: number | string;
|
|
8
|
+
items_count: number | null;
|
|
9
|
+
payment_status: string;
|
|
10
|
+
created_at: string;
|
|
11
|
+
payment_method?: string;
|
|
12
|
+
payment_link?: string | null;
|
|
13
|
+
currency?: string;
|
|
14
|
+
delivery_method?: string;
|
|
15
|
+
/** @deprecated backend sends `delivery_method`; kept for type back-compat. */
|
|
16
|
+
delivery_mode?: string;
|
|
17
|
+
vendor?: { id: string; name: string; slug: string };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OrderItem {
|
|
21
|
+
id: string;
|
|
22
|
+
quantity: number;
|
|
23
|
+
title: string;
|
|
24
|
+
total_price: number | string;
|
|
25
|
+
unit_price: number | string;
|
|
26
|
+
addons: Array<Record<string, unknown>>;
|
|
27
|
+
product?: { id: string; thumbnail_image?: string | null } | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface OrderTimeline {
|
|
31
|
+
time: string | null;
|
|
32
|
+
title: string;
|
|
33
|
+
description: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AmountSummaryRow {
|
|
37
|
+
key: string;
|
|
38
|
+
label: string;
|
|
39
|
+
type?: string;
|
|
40
|
+
amount: number | string;
|
|
41
|
+
is_total?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface OrderDetail {
|
|
45
|
+
id: string;
|
|
46
|
+
order_no: string;
|
|
47
|
+
status: string;
|
|
48
|
+
is_completed?: boolean;
|
|
49
|
+
status_label?: string;
|
|
50
|
+
payment_status: string;
|
|
51
|
+
payment_method?: string;
|
|
52
|
+
total_price: number | string;
|
|
53
|
+
grand_total?: number | string;
|
|
54
|
+
payment_link: string | null;
|
|
55
|
+
pay_link?: string | null;
|
|
56
|
+
items: OrderItem[];
|
|
57
|
+
timeline?: OrderTimeline[];
|
|
58
|
+
stage?: number | null;
|
|
59
|
+
amount_summary?: {
|
|
60
|
+
title?: string;
|
|
61
|
+
items: AmountSummaryRow[];
|
|
62
|
+
};
|
|
63
|
+
subtotal?: number;
|
|
64
|
+
delivery_fee?: number;
|
|
65
|
+
currency?: string;
|
|
66
|
+
delivery_method?: string;
|
|
67
|
+
/** @deprecated backend sends `delivery_method`; kept for type back-compat. */
|
|
68
|
+
delivery_mode?: string;
|
|
69
|
+
delivery_address?: string | null;
|
|
70
|
+
vendor_note?: string | null;
|
|
71
|
+
rider_note?: string | null;
|
|
72
|
+
created_at?: string;
|
|
73
|
+
vendor?: {
|
|
74
|
+
id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
slug: string;
|
|
77
|
+
logo?: string | null;
|
|
78
|
+
address?: string | null;
|
|
79
|
+
phone?: string | null;
|
|
80
|
+
email?: string | null;
|
|
81
|
+
map_lat?: number | string | null;
|
|
82
|
+
map_lng?: number | string | null;
|
|
83
|
+
};
|
|
84
|
+
pickup_address?: string | null;
|
|
85
|
+
table?: { id: string; name: string; section?: string | null } | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface OrderPageMeta {
|
|
89
|
+
current_page: number;
|
|
90
|
+
last_page: number;
|
|
91
|
+
per_page: number;
|
|
92
|
+
total: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface OrderListResponse {
|
|
96
|
+
data: OrderListItem[];
|
|
97
|
+
meta: OrderPageMeta;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface CheckoutPayload {
|
|
101
|
+
delivery_mode: string;
|
|
102
|
+
payment_method: string;
|
|
103
|
+
cart_items: Array<{
|
|
104
|
+
id: string;
|
|
105
|
+
item_id: string;
|
|
106
|
+
title: string;
|
|
107
|
+
price: number;
|
|
108
|
+
quantity: number;
|
|
109
|
+
selectedAddons: Array<{ id: string; name: string; price: number }>;
|
|
110
|
+
}>;
|
|
111
|
+
delivery_address?: string;
|
|
112
|
+
map_lat?: number;
|
|
113
|
+
map_lng?: number;
|
|
114
|
+
order_type?: 'instant' | 'schedule';
|
|
115
|
+
schedule_date?: string;
|
|
116
|
+
schedule_time?: string;
|
|
117
|
+
vendor_note?: string;
|
|
118
|
+
customer_phone?: string;
|
|
119
|
+
}
|
package/types/page.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Block } from './block';
|
|
2
|
+
import type { ImageVariants, MediaIndex } from './media';
|
|
3
|
+
import type { Product } from './product';
|
|
4
|
+
|
|
5
|
+
export type PageType = 'page' | 'post' | 'gallery' | 'policy';
|
|
6
|
+
|
|
7
|
+
export interface Pagination {
|
|
8
|
+
current_page?: number;
|
|
9
|
+
per_page?: number;
|
|
10
|
+
total?: number;
|
|
11
|
+
has_more?: boolean;
|
|
12
|
+
next_cursor?: string | null;
|
|
13
|
+
next_page_url?: string | null;
|
|
14
|
+
prev_cursor?: string | null;
|
|
15
|
+
prev_page_url?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SeoData {
|
|
19
|
+
title?: string | null;
|
|
20
|
+
description?: string | null;
|
|
21
|
+
image?: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Page {
|
|
25
|
+
id: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
type: PageType;
|
|
28
|
+
title: string;
|
|
29
|
+
content: Block[];
|
|
30
|
+
excerpt: string | null;
|
|
31
|
+
cover_image_url: string | null;
|
|
32
|
+
/** StorePageResource:50 — tier map for cover_image_url. */
|
|
33
|
+
cover_image_variants?: ImageVariants | null;
|
|
34
|
+
/**
|
|
35
|
+
* StorePageResource:47 — flat url→tier-map sidecar for every image url
|
|
36
|
+
* referenced inside `content[]`. `content[]` itself is byte-identical to
|
|
37
|
+
* before (block schemas unchanged); a block consumer looks up
|
|
38
|
+
* `mediaIndex[rawUrl] ?? null`. Serialized as an object, so it is `{}` when
|
|
39
|
+
* empty and never null — optional here only because older cached responses
|
|
40
|
+
* and preview/demo fixtures predate the field.
|
|
41
|
+
*/
|
|
42
|
+
media_index?: MediaIndex;
|
|
43
|
+
tags: string[];
|
|
44
|
+
seo: SeoData;
|
|
45
|
+
published_at: string | null;
|
|
46
|
+
created_at: string | null;
|
|
47
|
+
/** 'bare' hides the storefront header/nav/footer — a standalone landing
|
|
48
|
+
* page with nothing but its own content. Defaults to 'full' server-side. */
|
|
49
|
+
chrome?: 'full' | 'bare';
|
|
50
|
+
/** Whether the theme's page-title band (the title as a large heading
|
|
51
|
+
* under the nav, above the section content) should render for this page.
|
|
52
|
+
* Defaults to true server-side; the landing-page skill sets it false. */
|
|
53
|
+
show_page_title?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface BlogCategory {
|
|
57
|
+
name: string;
|
|
58
|
+
slug: string;
|
|
59
|
+
posts_count?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PostAuthor {
|
|
63
|
+
name: string;
|
|
64
|
+
avatar_url?: string | null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface Post extends Page {
|
|
68
|
+
type: 'post';
|
|
69
|
+
blog_categories?: BlogCategory[];
|
|
70
|
+
reading_time?: number | null;
|
|
71
|
+
author?: PostAuthor | null;
|
|
72
|
+
related?: Post[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface Collection {
|
|
76
|
+
id: string;
|
|
77
|
+
slug: string;
|
|
78
|
+
name: string;
|
|
79
|
+
description: string | null;
|
|
80
|
+
image: string | null;
|
|
81
|
+
/** CollectionResource:27 — beside the unchanged `image`. */
|
|
82
|
+
image_variants?: ImageVariants | null;
|
|
83
|
+
mode: string | null;
|
|
84
|
+
products_count: number;
|
|
85
|
+
products?: Product[];
|
|
86
|
+
}
|