@ecomiq/storefront 2.0.22 → 2.1.3
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/package.json +4 -2
- package/src/cart-session.ts +13 -15
- package/src/client.ts +1 -1
- package/src/cloud-runtime-client.ts +165 -5
- package/src/codegen.mjs +0 -1
- package/src/components/checkout/checkout-session-view.tsx +4 -4
- package/src/components/checkout/format.ts +2 -2
- package/src/components/checkout/use-checkout-customer-prefill.ts +4 -4
- package/src/components/checkout/use-checkout-session.ts +37 -38
- package/src/config.mjs +1 -0
- package/src/hooks/recently-viewed.ts +2 -2
- package/src/index.ts +2 -4
- package/src/pages/checkout.$checkoutId.tsx +1 -1
- package/src/pages/pages.$pageSlug.tsx +68 -31
- package/src/pages/thank-you.tsx +1 -1
- package/src/providers/cart.tsx +17 -17
- package/src/server-config-store.ts +1 -0
- package/src/server.ts +16 -7
- package/src/storefront-definition/data.ts +188 -0
- package/src/storefront-definition/renderer.tsx +610 -34
- package/src/storefront-layout.ts +33 -11
- package/src/storefront-runtime.ts +83 -187
- package/src/styles.css +9 -1
package/src/storefront-layout.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { useRouterState } from '@tanstack/react-router'
|
|
2
2
|
import {
|
|
3
3
|
StorefrontDefinitionSchema,
|
|
4
|
+
resolveStorefrontRoute,
|
|
4
5
|
type StorefrontDefinition,
|
|
6
|
+
type StorefrontPage,
|
|
5
7
|
} from '@ecomiq/storefront-definition'
|
|
6
8
|
|
|
7
|
-
export
|
|
9
|
+
export type StorefrontDefinitionRoute = {
|
|
10
|
+
definition: StorefrontDefinition
|
|
11
|
+
page: StorefrontPage
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function definitionRouteFor(
|
|
8
15
|
pathname: string,
|
|
9
16
|
loaderData: unknown,
|
|
10
|
-
):
|
|
11
|
-
if (
|
|
12
|
-
pathname !== '/' ||
|
|
13
|
-
loaderData === null ||
|
|
14
|
-
typeof loaderData !== 'object'
|
|
15
|
-
) {
|
|
17
|
+
): StorefrontDefinitionRoute | null {
|
|
18
|
+
if (loaderData === null || typeof loaderData !== 'object') {
|
|
16
19
|
return null
|
|
17
20
|
}
|
|
18
21
|
if (!('kind' in loaderData) || loaderData.kind !== 'definition') {
|
|
@@ -22,16 +25,35 @@ export function definitionForRoute(
|
|
|
22
25
|
return null
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
const
|
|
26
|
-
|
|
28
|
+
const parsed = StorefrontDefinitionSchema.safeParse(loaderData.definition)
|
|
29
|
+
if (!parsed.success) return null
|
|
30
|
+
|
|
31
|
+
const resolution = resolveStorefrontRoute(pathname, parsed.data.pages)
|
|
32
|
+
if (resolution.kind !== 'page') return null
|
|
33
|
+
const page = parsed.data.pages.find(
|
|
34
|
+
(candidate) => candidate.id === resolution.pageId,
|
|
35
|
+
)
|
|
36
|
+
if (!page) return null
|
|
37
|
+
return { definition: parsed.data, page }
|
|
27
38
|
}
|
|
28
39
|
|
|
29
|
-
export function
|
|
40
|
+
export function definitionForRoute(
|
|
41
|
+
pathname: string,
|
|
42
|
+
loaderData: unknown,
|
|
43
|
+
): StorefrontDefinition | null {
|
|
44
|
+
return definitionRouteFor(pathname, loaderData)?.definition ?? null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useDefinitionRoute(): StorefrontDefinitionRoute | null {
|
|
30
48
|
return useRouterState({
|
|
31
49
|
select: (state) =>
|
|
32
|
-
|
|
50
|
+
definitionRouteFor(
|
|
33
51
|
state.location.pathname,
|
|
34
52
|
state.matches.at(-1)?.loaderData,
|
|
35
53
|
),
|
|
36
54
|
})
|
|
37
55
|
}
|
|
56
|
+
|
|
57
|
+
export function useDefinition(): StorefrontDefinition | null {
|
|
58
|
+
return useDefinitionRoute()?.definition ?? null
|
|
59
|
+
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Collection,
|
|
3
|
-
Collections,
|
|
4
|
-
ProductsParamsInput,
|
|
5
|
-
ProductsResponse,
|
|
6
|
-
ProductSummary,
|
|
7
|
-
} from '@ecomiq/core'
|
|
8
1
|
import {
|
|
9
2
|
CloudRuntimeStoreSlugSchema,
|
|
10
3
|
getCloudRuntimeClient,
|
|
@@ -15,21 +8,24 @@ import {
|
|
|
15
8
|
getServerConfig,
|
|
16
9
|
type ServerEcomiqConfig,
|
|
17
10
|
} from './server-config-store'
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
import {
|
|
12
|
+
resolveStorefrontRoute,
|
|
13
|
+
type StorefrontDefinition,
|
|
14
|
+
type StorefrontPage,
|
|
22
15
|
} from '@ecomiq/storefront-definition'
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
16
|
+
import { storefrontPageFor } from './storefront-definition/renderer'
|
|
17
|
+
import {
|
|
18
|
+
emptyRendererData,
|
|
19
|
+
resolveStorefrontDefinitionData,
|
|
20
|
+
type ResolvedStorefrontDefinitionData,
|
|
21
|
+
type StorefrontRuntimeCatalogClient,
|
|
22
|
+
} from './storefront-definition/data'
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
resolveStorefrontDefinitionData,
|
|
26
|
+
type ResolvedStorefrontDefinitionData,
|
|
27
|
+
type StorefrontRuntimeCatalogClient,
|
|
28
|
+
} from './storefront-definition/data'
|
|
33
29
|
|
|
34
30
|
export interface StorefrontRuntimeCloudClient {
|
|
35
31
|
resolveActiveStorefront(
|
|
@@ -37,12 +33,6 @@ export interface StorefrontRuntimeCloudClient {
|
|
|
37
33
|
): Promise<ActiveStorefrontRuntime | null>
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
export interface ResolvedStorefrontDefinitionData
|
|
41
|
-
extends DefinitionData {
|
|
42
|
-
productsBySectionId: Record<string, ProductSummary[]>
|
|
43
|
-
collectionsBySectionId: Record<string, Collection[]>
|
|
44
|
-
}
|
|
45
|
-
|
|
46
36
|
export type ActiveStorefrontHome =
|
|
47
37
|
| {
|
|
48
38
|
kind: 'legacy'
|
|
@@ -50,165 +40,25 @@ export type ActiveStorefrontHome =
|
|
|
50
40
|
| {
|
|
51
41
|
kind: 'definition'
|
|
52
42
|
definition: StorefrontDefinition
|
|
43
|
+
page: StorefrontPage
|
|
53
44
|
publication: ActiveStorefrontRuntime['publication']
|
|
54
45
|
data: ResolvedStorefrontDefinitionData
|
|
55
46
|
}
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
collectionsBySectionId: {},
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function productSourceKey(
|
|
65
|
-
section: ProductGridStorefrontSection,
|
|
66
|
-
): string | null {
|
|
67
|
-
if (section.source.kind === 'manual') return null
|
|
68
|
-
if (section.source.kind === 'collection') {
|
|
69
|
-
return `collection:${section.source.slug}`
|
|
70
|
-
}
|
|
71
|
-
return 'all'
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function loadProductSource(
|
|
75
|
-
section: ProductGridStorefrontSection,
|
|
76
|
-
client: StorefrontRuntimeCatalogClient,
|
|
77
|
-
): Promise<ProductSummary[]> {
|
|
78
|
-
const params: ProductsParamsInput = {
|
|
79
|
-
page: 1,
|
|
80
|
-
pageSize: section.limit,
|
|
81
|
-
sort: 'newest',
|
|
82
|
-
}
|
|
83
|
-
if (section.source.kind === 'collection') {
|
|
84
|
-
params.collection = section.source.slug
|
|
85
|
-
}
|
|
86
|
-
const response = await client.getProducts(params)
|
|
87
|
-
return response.products.slice(0, section.limit)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function scanManualProducts(
|
|
91
|
-
productIds: ReadonlySet<string>,
|
|
92
|
-
client: StorefrontRuntimeCatalogClient,
|
|
93
|
-
): Promise<Map<string, ProductSummary>> {
|
|
94
|
-
const found = new Map<string, ProductSummary>()
|
|
95
|
-
|
|
96
|
-
for (let page = 1; page <= MAX_MANUAL_PRODUCT_SCAN_PAGES; page += 1) {
|
|
97
|
-
const response = await client.getProducts({
|
|
98
|
-
page,
|
|
99
|
-
pageSize: MANUAL_PRODUCT_PAGE_SIZE,
|
|
100
|
-
sort: 'newest',
|
|
101
|
-
})
|
|
102
|
-
for (const product of response.products) {
|
|
103
|
-
if (productIds.has(product.id)) found.set(product.id, product)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
found.size === productIds.size ||
|
|
108
|
-
page >= response.totalPages ||
|
|
109
|
-
response.products.length === 0
|
|
110
|
-
) {
|
|
111
|
-
break
|
|
48
|
+
export type ActiveStorefrontPage =
|
|
49
|
+
| {
|
|
50
|
+
kind: 'unpublished'
|
|
112
51
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return found
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export async function resolveStorefrontDefinitionData(
|
|
119
|
-
definition: StorefrontDefinition,
|
|
120
|
-
client: StorefrontRuntimeCatalogClient,
|
|
121
|
-
): Promise<ResolvedStorefrontDefinitionData> {
|
|
122
|
-
const data = emptyRendererData()
|
|
123
|
-
const productSections = definition.sections.filter(
|
|
124
|
-
(section): section is ProductGridStorefrontSection =>
|
|
125
|
-
section.enabled && section.type === 'product-grid',
|
|
126
|
-
)
|
|
127
|
-
const collectionSections = definition.sections.filter(
|
|
128
|
-
(section): section is CollectionGridStorefrontSection =>
|
|
129
|
-
section.enabled && section.type === 'collection-grid',
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
const sourceSections = new Map<string, ProductGridStorefrontSection>()
|
|
133
|
-
for (const section of productSections) {
|
|
134
|
-
const sourceKey = productSourceKey(section)
|
|
135
|
-
if (!sourceKey) continue
|
|
136
|
-
const current = sourceSections.get(sourceKey)
|
|
137
|
-
if (current) {
|
|
138
|
-
if (section.limit > current.limit) sourceSections.set(sourceKey, section)
|
|
139
|
-
continue
|
|
52
|
+
| {
|
|
53
|
+
kind: 'not-found'
|
|
140
54
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
sourceRequests.set(
|
|
148
|
-
sourceKey,
|
|
149
|
-
loadProductSource(section, client).catch(() => []),
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const manualIds = new Set(
|
|
154
|
-
productSections.flatMap((section) =>
|
|
155
|
-
section.source.kind === 'manual' ? section.source.productIds : [],
|
|
156
|
-
),
|
|
157
|
-
)
|
|
158
|
-
const manualProducts =
|
|
159
|
-
manualIds.size > 0
|
|
160
|
-
? scanManualProducts(manualIds, client).catch(
|
|
161
|
-
() => new Map<string, ProductSummary>(),
|
|
162
|
-
)
|
|
163
|
-
: Promise.resolve(new Map<string, ProductSummary>())
|
|
164
|
-
|
|
165
|
-
await Promise.all(
|
|
166
|
-
productSections.map(async (section) => {
|
|
167
|
-
if (section.source.kind === 'manual') {
|
|
168
|
-
const productsById = await manualProducts
|
|
169
|
-
data.productsBySectionId[section.id] = section.source.productIds
|
|
170
|
-
.map((productId) => productsById.get(productId))
|
|
171
|
-
.filter(
|
|
172
|
-
(product): product is ProductSummary => product !== undefined,
|
|
173
|
-
)
|
|
174
|
-
.slice(0, section.limit)
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const sourceKey = productSourceKey(section)
|
|
179
|
-
const products = sourceKey
|
|
180
|
-
? await (sourceRequests.get(sourceKey) ?? Promise.resolve([]))
|
|
181
|
-
: []
|
|
182
|
-
data.productsBySectionId[section.id] = products.slice(0, section.limit)
|
|
183
|
-
}),
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
if (collectionSections.length > 0) {
|
|
187
|
-
const collections = await client.getCollections().catch(() => [])
|
|
188
|
-
const collectionsBySlug = new Map(
|
|
189
|
-
collections.map((collection) => [collection.slug, collection]),
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
for (const section of collectionSections) {
|
|
193
|
-
if (section.source.kind === 'manual') {
|
|
194
|
-
data.collectionsBySectionId[section.id] = section.source.slugs
|
|
195
|
-
.map((slug) => collectionsBySlug.get(slug))
|
|
196
|
-
.filter(
|
|
197
|
-
(collection): collection is Collection =>
|
|
198
|
-
collection !== undefined,
|
|
199
|
-
)
|
|
200
|
-
.slice(0, section.limit)
|
|
201
|
-
continue
|
|
202
|
-
}
|
|
203
|
-
data.collectionsBySectionId[section.id] = collections.slice(
|
|
204
|
-
0,
|
|
205
|
-
section.limit,
|
|
206
|
-
)
|
|
55
|
+
| {
|
|
56
|
+
kind: 'definition'
|
|
57
|
+
definition: StorefrontDefinition
|
|
58
|
+
page: StorefrontPage
|
|
59
|
+
publication: ActiveStorefrontRuntime['publication']
|
|
60
|
+
data: ResolvedStorefrontDefinitionData
|
|
207
61
|
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return data
|
|
211
|
-
}
|
|
212
62
|
|
|
213
63
|
export function deriveCloudStoreSlug(domain: string): string | null {
|
|
214
64
|
const normalized = domain.trim().toLowerCase()
|
|
@@ -234,43 +84,89 @@ export interface LoadActiveStorefrontHomeDependencies {
|
|
|
234
84
|
catalogClient?: StorefrontRuntimeCatalogClient
|
|
235
85
|
}
|
|
236
86
|
|
|
237
|
-
|
|
238
|
-
dependencies: LoadActiveStorefrontHomeDependencies
|
|
239
|
-
): Promise<
|
|
87
|
+
async function resolveActiveRuntime(
|
|
88
|
+
dependencies: LoadActiveStorefrontHomeDependencies,
|
|
89
|
+
): Promise<ActiveStorefrontRuntime | null> {
|
|
240
90
|
const config = dependencies.config ?? getServerConfig()
|
|
241
91
|
const cloudConfig = config.cloud
|
|
242
|
-
if (!cloudConfig?.baseUrl) return
|
|
92
|
+
if (!cloudConfig?.baseUrl) return null
|
|
243
93
|
|
|
244
94
|
const storeSlug =
|
|
245
95
|
cloudConfig.storeSlug ?? deriveCloudStoreSlug(config.domain)
|
|
246
|
-
if (!storeSlug) return
|
|
96
|
+
if (!storeSlug) return null
|
|
247
97
|
|
|
248
98
|
const cloudClient =
|
|
249
99
|
dependencies.cloudClient ??
|
|
250
100
|
getCloudRuntimeClient({
|
|
251
101
|
baseUrl: cloudConfig.baseUrl,
|
|
102
|
+
artifactBaseUrl: cloudConfig.artifactBaseUrl,
|
|
252
103
|
timeoutMs: cloudConfig.timeoutMs,
|
|
253
104
|
cacheTtlMs: cloudConfig.cacheTtlMs,
|
|
254
105
|
})
|
|
255
106
|
|
|
256
|
-
let runtime: ActiveStorefrontRuntime | null
|
|
257
107
|
try {
|
|
258
|
-
|
|
108
|
+
return await cloudClient.resolveActiveStorefront(storeSlug)
|
|
259
109
|
} catch {
|
|
260
|
-
return
|
|
110
|
+
return null
|
|
261
111
|
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function loadActiveStorefrontHome(
|
|
115
|
+
dependencies: LoadActiveStorefrontHomeDependencies = {},
|
|
116
|
+
): Promise<ActiveStorefrontHome> {
|
|
117
|
+
const runtime = await resolveActiveRuntime(dependencies)
|
|
262
118
|
if (!runtime) return { kind: 'legacy' }
|
|
263
119
|
|
|
120
|
+
const page = storefrontPageFor(runtime.definition)
|
|
121
|
+
if (!page) return { kind: 'legacy' }
|
|
122
|
+
|
|
123
|
+
const catalogClient =
|
|
124
|
+
dependencies.catalogClient ?? getStorefrontClient()
|
|
125
|
+
const data = await resolveStorefrontDefinitionData(
|
|
126
|
+
runtime.definition,
|
|
127
|
+
catalogClient,
|
|
128
|
+
page.id,
|
|
129
|
+
).catch(emptyRendererData)
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
kind: 'definition',
|
|
133
|
+
definition: runtime.definition,
|
|
134
|
+
page,
|
|
135
|
+
publication: runtime.publication,
|
|
136
|
+
data,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function loadActiveStorefrontPage(
|
|
141
|
+
pathname: string,
|
|
142
|
+
dependencies: LoadActiveStorefrontHomeDependencies = {},
|
|
143
|
+
): Promise<ActiveStorefrontPage> {
|
|
144
|
+
const runtime = await resolveActiveRuntime(dependencies)
|
|
145
|
+
if (!runtime) return { kind: 'unpublished' }
|
|
146
|
+
|
|
147
|
+
const resolution = resolveStorefrontRoute(
|
|
148
|
+
pathname,
|
|
149
|
+
runtime.definition.pages,
|
|
150
|
+
)
|
|
151
|
+
if (resolution.kind !== 'page') return { kind: 'not-found' }
|
|
152
|
+
|
|
153
|
+
const page = runtime.definition.pages.find(
|
|
154
|
+
(candidate) => candidate.id === resolution.pageId,
|
|
155
|
+
)
|
|
156
|
+
if (!page) return { kind: 'not-found' }
|
|
157
|
+
|
|
264
158
|
const catalogClient =
|
|
265
159
|
dependencies.catalogClient ?? getStorefrontClient()
|
|
266
160
|
const data = await resolveStorefrontDefinitionData(
|
|
267
161
|
runtime.definition,
|
|
268
162
|
catalogClient,
|
|
163
|
+
page.id,
|
|
269
164
|
).catch(emptyRendererData)
|
|
270
165
|
|
|
271
166
|
return {
|
|
272
167
|
kind: 'definition',
|
|
273
168
|
definition: runtime.definition,
|
|
169
|
+
page,
|
|
274
170
|
publication: runtime.publication,
|
|
275
171
|
data,
|
|
276
172
|
}
|
package/src/styles.css
CHANGED
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
|
|
79
79
|
.storefront-definition {
|
|
80
80
|
font-family: var(--storefront-font-body);
|
|
81
|
+
--storefront-content-width: 72rem;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
.storefront-definition :where(h1, h2, h3, h4, h5, h6) {
|
|
@@ -85,5 +86,12 @@
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
.storefront-definition-section {
|
|
88
|
-
padding-block: var(
|
|
89
|
+
padding-block: var(
|
|
90
|
+
--storefront-section-padding-top,
|
|
91
|
+
var(--storefront-section-padding)
|
|
92
|
+
)
|
|
93
|
+
var(
|
|
94
|
+
--storefront-section-padding-bottom,
|
|
95
|
+
var(--storefront-section-padding)
|
|
96
|
+
);
|
|
89
97
|
}
|