@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.
Files changed (120) hide show
  1. package/README.md +37 -0
  2. package/api/collections.ts +93 -0
  3. package/api/orders.ts +23 -0
  4. package/apps/analytics-events.ts +32 -0
  5. package/apps/apps.css +613 -0
  6. package/apps/follow-card.tsx +192 -0
  7. package/apps/gtag.ts +19 -0
  8. package/apps/subscribe-form.tsx +124 -0
  9. package/client.ts +52 -0
  10. package/components/auth/auth-email-login-step.tsx +46 -0
  11. package/components/auth/auth-email-otp-step.tsx +54 -0
  12. package/components/auth/auth-email-otp-verify-step.tsx +82 -0
  13. package/components/auth/auth-email-register-step.tsx +59 -0
  14. package/components/auth/auth-flow.tsx +26 -0
  15. package/components/auth/auth-signup-step.tsx +53 -0
  16. package/components/blog/category-filter.tsx +92 -0
  17. package/components/blog/index.ts +12 -0
  18. package/components/blog/pagination.tsx +73 -0
  19. package/components/blog/post-categories.tsx +42 -0
  20. package/components/blog/post-meta.tsx +49 -0
  21. package/components/blog/related-posts.tsx +54 -0
  22. package/components/blog/share-buttons.tsx +89 -0
  23. package/components/cart/cart-panel-controller.tsx +67 -0
  24. package/components/cart/default-cart-shell.tsx +207 -0
  25. package/components/checkout/checkout-contact.tsx +84 -0
  26. package/components/checkout/default-checkout-shell.tsx +177 -0
  27. package/components/checkout/policy-links.tsx +98 -0
  28. package/components/image.tsx +149 -0
  29. package/components/markdown.tsx +54 -0
  30. package/components/product-media-frame.tsx +56 -0
  31. package/components/variant-picker.css +84 -0
  32. package/components/variant-picker.tsx +107 -0
  33. package/design-token-preview.tsx +81 -0
  34. package/edit-mode-active.tsx +324 -0
  35. package/edit-mode.tsx +80 -0
  36. package/hooks/use-address-autocomplete.ts +62 -0
  37. package/hooks/use-auth-flow.tsx +346 -0
  38. package/hooks/use-auth.ts +17 -0
  39. package/hooks/use-cart.ts +14 -0
  40. package/hooks/use-categories.ts +112 -0
  41. package/hooks/use-collection-params.ts +67 -0
  42. package/hooks/use-href.ts +15 -0
  43. package/hooks/use-posts.ts +75 -0
  44. package/hooks/use-product-detail.ts +69 -0
  45. package/hooks/use-product-questions.ts +77 -0
  46. package/hooks/use-product-sections.ts +165 -0
  47. package/hooks/use-products.ts +216 -0
  48. package/hooks/use-related-products.ts +56 -0
  49. package/hooks/use-resolve-coordinates.ts +48 -0
  50. package/hooks/use-reviews.ts +167 -0
  51. package/hooks/use-search.ts +55 -0
  52. package/hooks/use-shop-cart.ts +31 -0
  53. package/hooks/use-shop-params.ts +68 -0
  54. package/hooks/use-shop.ts +186 -0
  55. package/hooks/use-variant-selection.ts +98 -0
  56. package/index.ts +49 -0
  57. package/package.json +41 -0
  58. package/page-renderer.tsx +365 -0
  59. package/provider.tsx +91 -0
  60. package/sdk/client.ts +221 -0
  61. package/sdk/error-handler.ts +37 -0
  62. package/sdk/platform.ts +78 -0
  63. package/sdk/queek-client.ts +104 -0
  64. package/sdk/reauth-guard.ts +27 -0
  65. package/sdk/server-store-client.ts +165 -0
  66. package/shared-blocks/button.tsx +41 -0
  67. package/shared-blocks/callout.tsx +35 -0
  68. package/shared-blocks/content-default.tsx +23 -0
  69. package/shared-blocks/core-blocks.css +1792 -0
  70. package/shared-blocks/divider.tsx +17 -0
  71. package/shared-blocks/embed.tsx +40 -0
  72. package/shared-blocks/faq.tsx +44 -0
  73. package/shared-blocks/image.tsx +29 -0
  74. package/shared-blocks/index.ts +24 -0
  75. package/shared-blocks/manifest.ts +76 -0
  76. package/shared-blocks/product-qa.tsx +203 -0
  77. package/shared-blocks/quote.tsx +20 -0
  78. package/shared-blocks/reviews.tsx +436 -0
  79. package/shared-blocks/table.tsx +45 -0
  80. package/shared-blocks/video.tsx +60 -0
  81. package/stores/address-modal-store.ts +15 -0
  82. package/stores/auth-modal-store.ts +17 -0
  83. package/stores/cart-panel-store.ts +17 -0
  84. package/stores/cart-store.ts +172 -0
  85. package/stores/checkout-store.ts +167 -0
  86. package/stores/edit-preview-store.ts +61 -0
  87. package/stores/location-store.ts +41 -0
  88. package/stores/orders-sheet-store.ts +21 -0
  89. package/stores/policy-modal-store.ts +15 -0
  90. package/stores/product-modal-store.ts +18 -0
  91. package/stores/search-modal-store.ts +17 -0
  92. package/stores/toast-store.ts +46 -0
  93. package/stores/user-store.ts +88 -0
  94. package/theme-context.tsx +28 -0
  95. package/theme-variant-resolver.ts +44 -0
  96. package/types/block.ts +292 -0
  97. package/types/cart.ts +21 -0
  98. package/types/media.ts +55 -0
  99. package/types/menu.ts +27 -0
  100. package/types/order.ts +119 -0
  101. package/types/page.ts +86 -0
  102. package/types/product.ts +279 -0
  103. package/types/theme.ts +380 -0
  104. package/types/vendor.ts +243 -0
  105. package/utils/addon-selection.ts +31 -0
  106. package/utils/benefit-icon.tsx +145 -0
  107. package/utils/brand-palette.ts +64 -0
  108. package/utils/brand.ts +518 -0
  109. package/utils/contact-location.ts +44 -0
  110. package/utils/format.ts +64 -0
  111. package/utils/hero-content.ts +35 -0
  112. package/utils/image-srcset.ts +157 -0
  113. package/utils/markdown.tsx +237 -0
  114. package/utils/match-variant.ts +31 -0
  115. package/utils/menu-link.ts +41 -0
  116. package/utils/placeholder-resolver.ts +184 -0
  117. package/utils/product-media.ts +77 -0
  118. package/utils/product-normalizer.ts +323 -0
  119. package/utils/vendor-host.ts +173 -0
  120. 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
+ }