@wix/web5-core 1.63.14 → 1.63.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/entity/matchedVariant.js +31 -9
- package/dist/cjs/entity/matchedVariant.js.map +1 -1
- package/dist/cjs/hooks/useResolveShopifyEntityData.js +6 -0
- package/dist/cjs/hooks/useResolveShopifyEntityData.js.map +1 -1
- package/dist/cjs/services/shopify/transformShopifyEntity.js +17 -5
- package/dist/cjs/services/shopify/transformShopifyEntity.js.map +1 -1
- package/dist/esm/entity/matchedVariant.js +31 -9
- package/dist/esm/entity/matchedVariant.js.map +1 -1
- package/dist/esm/hooks/useResolveShopifyEntityData.js +6 -0
- package/dist/esm/hooks/useResolveShopifyEntityData.js.map +1 -1
- package/dist/esm/services/shopify/transformShopifyEntity.js +17 -5
- package/dist/esm/services/shopify/transformShopifyEntity.js.map +1 -1
- package/dist/types/entity/matchedVariant.d.ts +18 -9
- package/dist/types/entity/matchedVariant.d.ts.map +1 -1
- package/dist/types/hooks/useResolveShopifyEntityData.d.ts.map +1 -1
- package/dist/types/services/shopify/transformShopifyEntity.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -123,19 +123,38 @@ function formatPriceField(value, currency) {
|
|
|
123
123
|
|
|
124
124
|
/** The already-formatted price fields a card reads. */
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Whether this row stands for one variant rather than for the whole product.
|
|
128
|
+
*
|
|
129
|
+
* Either mark is enough. `variantId` is the machine answer and `matchedOptions`
|
|
130
|
+
* the human one, and a row can arrive with only one of them: retrieval that
|
|
131
|
+
* resolved a document down to a child sends the pairs, while a row whose
|
|
132
|
+
* identity came from the `?variant=` on its own URL has only the id. Both mean
|
|
133
|
+
* the same thing — the card names a child, so the card must price that child.
|
|
134
|
+
*/
|
|
135
|
+
function namesOneVariant(fields) {
|
|
136
|
+
var _fields$matchedOption;
|
|
137
|
+
return Boolean(fields.variantId) || Boolean((_fields$matchedOption = fields.matchedOptions) == null ? void 0 : _fields$matchedOption.length);
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
/**
|
|
127
141
|
* What a product card should print as its price.
|
|
128
142
|
*
|
|
129
|
-
*
|
|
130
|
-
* the
|
|
131
|
-
*
|
|
132
|
-
* the
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
143
|
+
* A card that names a variant prices THAT variant. `price` belongs to the
|
|
144
|
+
* single child the search resolved to — the same child the card's image, its
|
|
145
|
+
* `?variant=` link and the pairs printed above the price all describe — so it
|
|
146
|
+
* is the only number that can follow them without contradicting them. "from"
|
|
147
|
+
* is wrong there twice over: it claims a range where the card named one thing,
|
|
148
|
+
* and the floor it points at is a different child's price wearing this one's
|
|
149
|
+
* name. That is what shipped: five colourways of one shoe, each naming its own
|
|
150
|
+
* colour and each printing the cheapest colour's price.
|
|
151
|
+
*
|
|
152
|
+
* `priceMin`/`priceMax` describe the whole product, and they are the answer
|
|
153
|
+
* only when the row IS the whole product. Then the span's floor prints
|
|
154
|
+
* prefixed, because no single number can stand for a product that has several.
|
|
136
155
|
*
|
|
137
|
-
* With no span — the
|
|
138
|
-
*
|
|
156
|
+
* With no span — the ends equal, or only one of them known — the row's own
|
|
157
|
+
* price prints as it always did.
|
|
139
158
|
*/
|
|
140
159
|
function formatProductPriceLabel(fields) {
|
|
141
160
|
const {
|
|
@@ -143,6 +162,9 @@ function formatProductPriceLabel(fields) {
|
|
|
143
162
|
priceMin,
|
|
144
163
|
priceMax
|
|
145
164
|
} = fields;
|
|
165
|
+
if (price && namesOneVariant(fields)) {
|
|
166
|
+
return price;
|
|
167
|
+
}
|
|
146
168
|
if (priceMin && priceMax && priceMin !== priceMax) {
|
|
147
169
|
return `from ${priceMin}`;
|
|
148
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["readText","value","trim","Number","isFinite","String","toMatchedOptions","raw","Array","isArray","undefined","pairs","rawName","rawValue","Object","entries","name","push","length","formatMoney","amount","currency","Intl","NumberFormat","style","minimumFractionDigits","isInteger","format","readCurrencyCode","candidates","candidate","code","toUpperCase","test","formatPriceField","
|
|
1
|
+
{"version":3,"names":["readText","value","trim","Number","isFinite","String","toMatchedOptions","raw","Array","isArray","undefined","pairs","rawName","rawValue","Object","entries","name","push","length","formatMoney","amount","currency","Intl","NumberFormat","style","minimumFractionDigits","isInteger","format","readCurrencyCode","candidates","candidate","code","toUpperCase","test","formatPriceField","namesOneVariant","fields","_fields$matchedOption","Boolean","variantId","matchedOptions","formatProductPriceLabel","price","priceMin","priceMax"],"sources":["../../../src/entity/matchedVariant.ts"],"sourcesContent":["/**\n * Presentation helpers for a document that retrieval resolved down to one\n * child.\n *\n * When a query filters on an attribute that lives on the child rather than on\n * the parent, retrieval overlays the matching child's presentation fields onto\n * the parent document before it reaches us: `url`, `img` and `price` already\n * describe the child, while `title`, `description` and `doc_id` stay the\n * parent's. Two fields need shaping before a card can print them, and both\n * live here:\n *\n * - `options` — the child's own `{ name: value }` pairs.\n * - `priceMin` / `priceMax` — the parent's span, against which the child's\n * single `price` has to be read.\n *\n * The overlay itself is done server-side and is deliberately NOT repeated\n * here.\n */\n\n/** One `{ name: value }` pair, exactly as the store named it. */\nexport interface MatchedOption {\n name: string;\n value: string;\n}\n\nfunction readText(value: unknown): string {\n if (typeof value === 'string') {\n return value.trim();\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return String(value);\n }\n return '';\n}\n\n/**\n * Turn a document's raw `options` map into an ordered list of pairs.\n *\n * Every entry the store put in the map is kept, under the store's own name and\n * in the store's own order. Nothing here knows — or may learn — what any\n * particular name means: one catalogue's pairs describe a garment, the next\n * one's a power tool, and both have to come out identical. Entries with an\n * empty name or an empty/non-printable value are dropped, since a card can do\n * nothing with them.\n *\n * Returns `undefined` (not `[]`) when there is nothing to show, so callers can\n * treat it like every other optional field on the entity.\n */\nexport function toMatchedOptions(raw: unknown): MatchedOption[] | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return undefined;\n }\n\n const pairs: MatchedOption[] = [];\n for (const [rawName, rawValue] of Object.entries(\n raw as Record<string, unknown>,\n )) {\n const name = rawName.trim();\n const value = readText(rawValue);\n if (name && value) {\n pairs.push({ name, value });\n }\n }\n\n return pairs.length ? pairs : undefined;\n}\n\n/**\n * Render one amount in the document's currency.\n *\n * With no currency code on the document the amount is printed bare: assuming\n * \"$\" prints a false fact on every store that doesn't sell in dollars. Whole\n * amounts stay whole (\"107\" not \"107.00\"); a real fractional amount keeps its\n * minor units.\n */\nexport function formatMoney(amount: number, currency?: string): string {\n if (!currency) {\n return String(amount);\n }\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,\n }).format(amount);\n } catch {\n // A well-formed but unknown code: show it rather than dropping it.\n return `${amount} ${currency}`;\n }\n}\n\n/** Normalize a document currency to an ISO-4217 code, or drop it. */\nexport function readCurrencyCode(...candidates: unknown[]): string | undefined {\n for (const candidate of candidates) {\n if (typeof candidate !== 'string') {\n continue;\n }\n const code = candidate.trim().toUpperCase();\n if (/^[A-Z]{3}$/.test(code)) {\n return code;\n }\n }\n return undefined;\n}\n\n/**\n * Format one of a document's price fields for display. Numbers are rendered in\n * the document's currency; an already-formatted string is passed through\n * untouched; anything else yields `undefined`.\n */\nexport function formatPriceField(\n value: unknown,\n currency?: string,\n): string | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return formatMoney(value, currency);\n }\n if (typeof value === 'string' && value.trim()) {\n return value.trim();\n }\n return undefined;\n}\n\n/** The already-formatted price fields a card reads. */\nexport interface ProductPriceFields {\n /** Price of the one variant this result matched. */\n price?: string;\n /** Lowest price across the whole product's variants. */\n priceMin?: string;\n /** Highest price across the whole product's variants. */\n priceMax?: string;\n /** Id of the variant this row was resolved down to, when there was one. */\n variantId?: string;\n /** The pairs naming that variant — what the card prints above the price. */\n matchedOptions?: MatchedOption[];\n}\n\n/**\n * Whether this row stands for one variant rather than for the whole product.\n *\n * Either mark is enough. `variantId` is the machine answer and `matchedOptions`\n * the human one, and a row can arrive with only one of them: retrieval that\n * resolved a document down to a child sends the pairs, while a row whose\n * identity came from the `?variant=` on its own URL has only the id. Both mean\n * the same thing — the card names a child, so the card must price that child.\n */\nfunction namesOneVariant(fields: ProductPriceFields): boolean {\n return Boolean(fields.variantId) || Boolean(fields.matchedOptions?.length);\n}\n\n/**\n * What a product card should print as its price.\n *\n * A card that names a variant prices THAT variant. `price` belongs to the\n * single child the search resolved to — the same child the card's image, its\n * `?variant=` link and the pairs printed above the price all describe — so it\n * is the only number that can follow them without contradicting them. \"from\"\n * is wrong there twice over: it claims a range where the card named one thing,\n * and the floor it points at is a different child's price wearing this one's\n * name. That is what shipped: five colourways of one shoe, each naming its own\n * colour and each printing the cheapest colour's price.\n *\n * `priceMin`/`priceMax` describe the whole product, and they are the answer\n * only when the row IS the whole product. Then the span's floor prints\n * prefixed, because no single number can stand for a product that has several.\n *\n * With no span — the ends equal, or only one of them known — the row's own\n * price prints as it always did.\n */\nexport function formatProductPriceLabel(\n fields: ProductPriceFields,\n): string | undefined {\n const { price, priceMin, priceMax } = fields;\n if (price && namesOneVariant(fields)) {\n return price;\n }\n if (priceMin && priceMax && priceMin !== priceMax) {\n return `from ${priceMin}`;\n }\n return price;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -107,6 +107,12 @@ function mergeShopifyEntityData(existing, resolved) {
|
|
|
107
107
|
url: existing.url || resolved.url,
|
|
108
108
|
title: existing.title || resolved.title,
|
|
109
109
|
imageUrl: existing.imageUrl || resolved.imageUrl,
|
|
110
|
+
// The live fetch withholds a price when it was asked about a variant it
|
|
111
|
+
// could not find, rather than answering with the product's floor. Its
|
|
112
|
+
// silence must not erase the turn's number, which was about the right
|
|
113
|
+
// child; a spread would overwrite with `undefined` and leave the card
|
|
114
|
+
// priceless.
|
|
115
|
+
price: resolved.price ?? existing.price,
|
|
110
116
|
variantId: existing.variantId || resolved.variantId,
|
|
111
117
|
matchedOptions: (_existing$matchedOpti = existing.matchedOptions) != null && _existing$matchedOpti.length ? existing.matchedOptions : resolved.matchedOptions
|
|
112
118
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_shopifyStorefrontClient","_shopifyEntityResolver","_transformShopifyEntity","shopifyEntityCache","Map","cacheKey","entityType","entityId","variantId","normalizeShopifyEntityType","lower","toLowerCase","includes","extractHandleFromUrl","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","slice","length","collectionsIndex","variantIdFromUrl","_URLSearchParams$get","query","URLSearchParams","get","getShopifyLookup","entity","_entity$data","_entity$data2","_entity$data3","urlHandle","data","mergeShopifyEntityData","existing","resolved","_existing$matchedOpti","title","imageUrl","matchedOptions","useResolveShopifyEntityData","entities","shopifyConfig","resolvedMap","setResolvedMap","useState","settled","setSettled","clientRef","useRef","configKeyRef","configKey","storeDomain","apiVersion","current","ShopifyStorefrontClient","entityIds","useMemo","map","e","lookup","join","useEffect","ignore","client","entityLookups","key","toFetch","filter","has","cached","forEach","item","set","fetchAll","Promise","allSettled","resolveShopifyEntity","itemData","transformShopifyEntityToItemData","finalMap","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useRef } from 'react';\nimport { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient';\nimport { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver';\nimport { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity';\nimport type { ShopifyStorefrontConfig } from '../services/shopify/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: entityType:entityId:variantId → resolved EntityItemData\nconst shopifyEntityCache = new Map<string, EntityItemData>();\n\nfunction cacheKey(\n entityType: string,\n entityId: string,\n variantId?: string,\n): string {\n return `${entityType}:${entityId}:${variantId || ''}`;\n}\n\nfunction normalizeShopifyEntityType(entityType: string): string {\n const lower = entityType.toLowerCase();\n // A variant is not a Storefront resource of its own — there is no\n // `variantByHandle`. It is fetched as its parent product and then priced at\n // the variant the lookup carries alongside, which is why `variant` and\n // `product` normalize to the same query here rather than to two.\n if (lower.includes('product') || lower.includes('variant')) {\n return 'product';\n }\n if (lower.includes('collection')) {\n return 'collection';\n }\n if (lower.includes('article') || lower.includes('blog')) {\n return 'article';\n }\n return entityType;\n}\n\nfunction extractHandleFromUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n if (productsIndex >= 0) {\n return withoutLeadingSlash.slice(productsIndex + 'products/'.length);\n }\n\n const collectionsIndex = withoutLeadingSlash.indexOf('collections/');\n if (collectionsIndex >= 0) {\n return withoutLeadingSlash.slice(\n collectionsIndex + 'collections/'.length,\n );\n }\n\n return withoutLeadingSlash || undefined;\n}\n\n/**\n * The `?variant=` the row's own URL already carries.\n *\n * A row that names a variant — whether the turn overlaid one onto a product or\n * sent the variant as the entity itself — always links to it, because that is\n * the URL a shopper has to land on. So when the row omits `variantId` as a\n * field, the URL still says which child it means, and pricing the parent's\n * default instead would contradict the link on the same card.\n */\nfunction variantIdFromUrl(url: string | undefined): string | undefined {\n const query = url?.split('#')[0].split('?')[1];\n if (!query) {\n return undefined;\n }\n return new URLSearchParams(query).get('variant')?.trim() || undefined;\n}\n\nexport function getShopifyLookup(entity: {\n entityId: string;\n entityType: string;\n data?: EntityItemData;\n}) {\n const entityType = normalizeShopifyEntityType(entity.entityType);\n const urlHandle = extractHandleFromUrl(entity.data?.url);\n const entityId =\n (entityType === 'product' || entityType === 'collection') && urlHandle\n ? urlHandle\n : entity.entityId;\n return {\n entityType,\n entityId,\n variantId: entity.data?.variantId ?? variantIdFromUrl(entity.data?.url),\n };\n}\n\n/**\n * The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about\n * it.\n *\n * Only the turn saw the shopper's question, so only it knows that the brown\n * variant was the one asked for — its `title`, `imageUrl`, `url`,\n * `matchedOptions` and `variantId` all describe that one variant and must\n * survive enrichment. Everything the Storefront answers with that can go stale\n * between the turn and the render — `price`, `compareAtPrice`, `available`,\n * `priceMin`, `priceMax` — comes from `resolved`.\n */\nexport function mergeShopifyEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n title: existing.title || resolved.title,\n imageUrl: existing.imageUrl || resolved.imageUrl,\n variantId: existing.variantId || resolved.variantId,\n matchedOptions: existing.matchedOptions?.length\n ? existing.matchedOptions\n : resolved.matchedOptions,\n };\n}\n\n/**\n * Shopify-specific implementation of the entity resolution pattern.\n *\n * Fetches entity data from the Shopify Storefront API (tokenless) and\n * transforms it to the standard EntityItemData shape.\n *\n * Designed to be plugged into ComponentDependencies.useResolveGenericEntityData.\n */\nexport function useResolveShopifyEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n shopifyConfig: ShopifyStorefrontConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable client reference — recreated only when config changes\n const clientRef = useRef<ShopifyStorefrontClient | null>(null);\n const configKeyRef = useRef('');\n const configKey = `${shopifyConfig.storeDomain}|${\n shopifyConfig.apiVersion || ''\n }`;\n\n if (!clientRef.current || configKeyRef.current !== configKey) {\n clientRef.current = new ShopifyStorefrontClient(shopifyConfig);\n configKeyRef.current = configKey;\n }\n\n const entityIds = useMemo(\n () =>\n entities\n .map((e) => {\n const lookup = getShopifyLookup(e);\n return `${e.entityType}:${e.entityId}:${cacheKey(\n lookup.entityType,\n lookup.entityId,\n lookup.variantId,\n )}`;\n })\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n const client = clientRef.current;\n\n if (!entities.length || !client || !shopifyConfig.storeDomain) {\n setSettled(true);\n return;\n }\n\n const entityLookups = entities.map((entity) => {\n const lookup = getShopifyLookup(entity);\n return {\n entity,\n lookup,\n // The variant is part of the key: the cached entry carries that\n // variant's live price, so two entities on the same product but\n // different variants must not share it.\n key: cacheKey(lookup.entityType, lookup.entityId, lookup.variantId),\n };\n });\n\n // Identify entities that need fetching. Payload data can provide the handle.\n const toFetch = entityLookups.filter(\n ({ key }) => !shopifyEntityCache.has(key),\n );\n\n // If everything is cached, resolve immediately\n if (toFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n cached.set(entity.entityId, mergeShopifyEntityData(entity.data, item));\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n await Promise.allSettled(\n toFetch.map(async ({ entity, lookup, key }) => {\n const resolved = await resolveShopifyEntity(\n client,\n lookup.entityType,\n lookup.entityId,\n shopifyConfig,\n );\n if (resolved) {\n const itemData = transformShopifyEntityToItemData(\n resolved,\n lookup.entityType,\n lookup.variantId,\n );\n shopifyEntityCache.set(\n key,\n mergeShopifyEntityData(entity.data, itemData),\n );\n }\n }),\n );\n\n if (ignore) {\n return;\n }\n\n // Build the resolved map from cache\n const finalMap = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n finalMap.set(\n entity.entityId,\n mergeShopifyEntityData(entity.data, item),\n );\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityIds, shopifyConfig.storeDomain]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return { ...entity, data: resolved };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["_react","require","_shopifyStorefrontClient","_shopifyEntityResolver","_transformShopifyEntity","shopifyEntityCache","Map","cacheKey","entityType","entityId","variantId","normalizeShopifyEntityType","lower","toLowerCase","includes","extractHandleFromUrl","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","slice","length","collectionsIndex","variantIdFromUrl","_URLSearchParams$get","query","URLSearchParams","get","getShopifyLookup","entity","_entity$data","_entity$data2","_entity$data3","urlHandle","data","mergeShopifyEntityData","existing","resolved","_existing$matchedOpti","title","imageUrl","price","matchedOptions","useResolveShopifyEntityData","entities","shopifyConfig","resolvedMap","setResolvedMap","useState","settled","setSettled","clientRef","useRef","configKeyRef","configKey","storeDomain","apiVersion","current","ShopifyStorefrontClient","entityIds","useMemo","map","e","lookup","join","useEffect","ignore","client","entityLookups","key","toFetch","filter","has","cached","forEach","item","set","fetchAll","Promise","allSettled","resolveShopifyEntity","itemData","transformShopifyEntityToItemData","finalMap","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useRef } from 'react';\nimport { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient';\nimport { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver';\nimport { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity';\nimport type { ShopifyStorefrontConfig } from '../services/shopify/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: entityType:entityId:variantId → resolved EntityItemData\nconst shopifyEntityCache = new Map<string, EntityItemData>();\n\nfunction cacheKey(\n entityType: string,\n entityId: string,\n variantId?: string,\n): string {\n return `${entityType}:${entityId}:${variantId || ''}`;\n}\n\nfunction normalizeShopifyEntityType(entityType: string): string {\n const lower = entityType.toLowerCase();\n // A variant is not a Storefront resource of its own — there is no\n // `variantByHandle`. It is fetched as its parent product and then priced at\n // the variant the lookup carries alongside, which is why `variant` and\n // `product` normalize to the same query here rather than to two.\n if (lower.includes('product') || lower.includes('variant')) {\n return 'product';\n }\n if (lower.includes('collection')) {\n return 'collection';\n }\n if (lower.includes('article') || lower.includes('blog')) {\n return 'article';\n }\n return entityType;\n}\n\nfunction extractHandleFromUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n if (productsIndex >= 0) {\n return withoutLeadingSlash.slice(productsIndex + 'products/'.length);\n }\n\n const collectionsIndex = withoutLeadingSlash.indexOf('collections/');\n if (collectionsIndex >= 0) {\n return withoutLeadingSlash.slice(\n collectionsIndex + 'collections/'.length,\n );\n }\n\n return withoutLeadingSlash || undefined;\n}\n\n/**\n * The `?variant=` the row's own URL already carries.\n *\n * A row that names a variant — whether the turn overlaid one onto a product or\n * sent the variant as the entity itself — always links to it, because that is\n * the URL a shopper has to land on. So when the row omits `variantId` as a\n * field, the URL still says which child it means, and pricing the parent's\n * default instead would contradict the link on the same card.\n */\nfunction variantIdFromUrl(url: string | undefined): string | undefined {\n const query = url?.split('#')[0].split('?')[1];\n if (!query) {\n return undefined;\n }\n return new URLSearchParams(query).get('variant')?.trim() || undefined;\n}\n\nexport function getShopifyLookup(entity: {\n entityId: string;\n entityType: string;\n data?: EntityItemData;\n}) {\n const entityType = normalizeShopifyEntityType(entity.entityType);\n const urlHandle = extractHandleFromUrl(entity.data?.url);\n const entityId =\n (entityType === 'product' || entityType === 'collection') && urlHandle\n ? urlHandle\n : entity.entityId;\n return {\n entityType,\n entityId,\n variantId: entity.data?.variantId ?? variantIdFromUrl(entity.data?.url),\n };\n}\n\n/**\n * The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about\n * it.\n *\n * Only the turn saw the shopper's question, so only it knows that the brown\n * variant was the one asked for — its `title`, `imageUrl`, `url`,\n * `matchedOptions` and `variantId` all describe that one variant and must\n * survive enrichment. Everything the Storefront answers with that can go stale\n * between the turn and the render — `price`, `compareAtPrice`, `available`,\n * `priceMin`, `priceMax` — comes from `resolved`.\n */\nexport function mergeShopifyEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n title: existing.title || resolved.title,\n imageUrl: existing.imageUrl || resolved.imageUrl,\n // The live fetch withholds a price when it was asked about a variant it\n // could not find, rather than answering with the product's floor. Its\n // silence must not erase the turn's number, which was about the right\n // child; a spread would overwrite with `undefined` and leave the card\n // priceless.\n price: resolved.price ?? existing.price,\n variantId: existing.variantId || resolved.variantId,\n matchedOptions: existing.matchedOptions?.length\n ? existing.matchedOptions\n : resolved.matchedOptions,\n };\n}\n\n/**\n * Shopify-specific implementation of the entity resolution pattern.\n *\n * Fetches entity data from the Shopify Storefront API (tokenless) and\n * transforms it to the standard EntityItemData shape.\n *\n * Designed to be plugged into ComponentDependencies.useResolveGenericEntityData.\n */\nexport function useResolveShopifyEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n shopifyConfig: ShopifyStorefrontConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable client reference — recreated only when config changes\n const clientRef = useRef<ShopifyStorefrontClient | null>(null);\n const configKeyRef = useRef('');\n const configKey = `${shopifyConfig.storeDomain}|${\n shopifyConfig.apiVersion || ''\n }`;\n\n if (!clientRef.current || configKeyRef.current !== configKey) {\n clientRef.current = new ShopifyStorefrontClient(shopifyConfig);\n configKeyRef.current = configKey;\n }\n\n const entityIds = useMemo(\n () =>\n entities\n .map((e) => {\n const lookup = getShopifyLookup(e);\n return `${e.entityType}:${e.entityId}:${cacheKey(\n lookup.entityType,\n lookup.entityId,\n lookup.variantId,\n )}`;\n })\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n const client = clientRef.current;\n\n if (!entities.length || !client || !shopifyConfig.storeDomain) {\n setSettled(true);\n return;\n }\n\n const entityLookups = entities.map((entity) => {\n const lookup = getShopifyLookup(entity);\n return {\n entity,\n lookup,\n // The variant is part of the key: the cached entry carries that\n // variant's live price, so two entities on the same product but\n // different variants must not share it.\n key: cacheKey(lookup.entityType, lookup.entityId, lookup.variantId),\n };\n });\n\n // Identify entities that need fetching. Payload data can provide the handle.\n const toFetch = entityLookups.filter(\n ({ key }) => !shopifyEntityCache.has(key),\n );\n\n // If everything is cached, resolve immediately\n if (toFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n cached.set(entity.entityId, mergeShopifyEntityData(entity.data, item));\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n await Promise.allSettled(\n toFetch.map(async ({ entity, lookup, key }) => {\n const resolved = await resolveShopifyEntity(\n client,\n lookup.entityType,\n lookup.entityId,\n shopifyConfig,\n );\n if (resolved) {\n const itemData = transformShopifyEntityToItemData(\n resolved,\n lookup.entityType,\n lookup.variantId,\n );\n shopifyEntityCache.set(\n key,\n mergeShopifyEntityData(entity.data, itemData),\n );\n }\n }),\n );\n\n if (ignore) {\n return;\n }\n\n // Build the resolved map from cache\n const finalMap = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n finalMap.set(\n entity.entityId,\n mergeShopifyEntityData(entity.data, item),\n );\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityIds, shopifyConfig.storeDomain]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return { ...entity, data: resolved };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -87,9 +87,16 @@ function transformShopifyProduct(product, variantId) {
|
|
|
87
87
|
var _product$featuredImag, _product$options;
|
|
88
88
|
const variants = extractVariants(product);
|
|
89
89
|
// The turn decided WHICH variant to show; the live fetch is here for what is
|
|
90
|
-
// true about it right now.
|
|
91
|
-
// since dropped — the product-level range is still the only answer we have.
|
|
90
|
+
// true about it right now.
|
|
92
91
|
const matchedVariant = findLiveVariant(variants, variantId);
|
|
92
|
+
// Whether this fetch is answering about the whole product or about one
|
|
93
|
+
// child of it. A row that named a child but whose child the store no longer
|
|
94
|
+
// returns — dropped since the turn, or past the `variants(first: 100)` page
|
|
95
|
+
// — is NOT a whole-product row: answering it with the product's floor would
|
|
96
|
+
// put a different variant's price under this variant's name, which is the
|
|
97
|
+
// bug the row's own `variantId` exists to prevent. So the live price is
|
|
98
|
+
// simply withheld and the turn's, which was right about this child, stands.
|
|
99
|
+
const pricesWholeProduct = !variantId;
|
|
93
100
|
return {
|
|
94
101
|
url: product.onlineStoreUrl || '',
|
|
95
102
|
title: product.title,
|
|
@@ -98,11 +105,16 @@ function transformShopifyProduct(product, variantId) {
|
|
|
98
105
|
imageUrl: (_product$featuredImag = product.featuredImage) == null ? void 0 : _product$featuredImag.url,
|
|
99
106
|
secondaryImageUrl: getSecondaryImage(product),
|
|
100
107
|
vendor: product.vendor,
|
|
101
|
-
price: (matchedVariant == null ? void 0 : matchedVariant.price)
|
|
102
|
-
//
|
|
108
|
+
price: (matchedVariant == null ? void 0 : matchedVariant.price) ?? (pricesWholeProduct ? formatPrice(product.priceRange.minVariantPrice) : undefined),
|
|
109
|
+
// The span the whole product covers, in the store's live currency. A card
|
|
110
|
+
// reads it to decide whether "from" is honest; it is the product's either
|
|
111
|
+
// way, so it is reported whether or not a child matched.
|
|
112
|
+
priceMin: formatPrice(product.priceRange.minVariantPrice),
|
|
113
|
+
priceMax: formatPrice(product.priceRange.maxVariantPrice),
|
|
114
|
+
// Keyed on the variant, not on its value: a whole-product row falls back
|
|
103
115
|
// to the product-level compare-at, but a matched variant that is simply
|
|
104
116
|
// not on sale must not inherit one and print a false strikethrough.
|
|
105
|
-
compareAtPrice: matchedVariant ? matchedVariant.compareAtPrice : getCompareAtPrice(product),
|
|
117
|
+
compareAtPrice: matchedVariant ? matchedVariant.compareAtPrice : pricesWholeProduct ? getCompareAtPrice(product) : undefined,
|
|
106
118
|
available: matchedVariant == null ? void 0 : matchedVariant.available,
|
|
107
119
|
sizes: extractSizes(product),
|
|
108
120
|
variants,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["formatShopifyDate","isoDate","date","Date","isNaN","getTime","undefined","day","getDate","month","toLocaleString","year","getFullYear","formatPrice","money","amount","parseFloat","formatted","toFixed","currencyCode","extractSizes","product","sizes","variants","edges","flatMap","e","node","selectedOptions","filter","o","name","toLowerCase","map","value","unique","Set","length","extractVariants","sizeOption","find","numericId","id","replace","label","title","variantId","available","availableForSale","price","compareAtPrice","getSecondaryImage","images","url","getAllImages","getCompareAtPrice","_product$compareAtPri","compare","compareAtPriceRange","minVariantPrice","min","priceRange","findLiveVariant","variant","transformShopifyProduct","_product$featuredImag","_product$options","matchedVariant","onlineStoreUrl","type","description","imageUrl","featuredImage","secondaryImageUrl","vendor","allImages","options","tags","transformShopifyCollection","collection","_collection$image","image","transformShopifyArticle","article","_article$image","_article$authorV","excerpt","publishDate","publishedAt","author","authorV2","transformShopifyEntityToItemData","entity","entityType"],"sources":["../../../../src/services/shopify/transformShopifyEntity.ts"],"sourcesContent":["import type { EntityItemData } from '../../component/types';\nimport type {\n ShopifyProduct,\n ShopifyCollection,\n ShopifyArticle,\n} from './types';\n\nfunction formatShopifyDate(isoDate: string): string | undefined {\n const date = new Date(isoDate);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const day = date.getDate();\n const month = date.toLocaleString('en-US', { month: 'short' });\n const year = date.getFullYear();\n return `${day} ${month} ${year}`;\n}\n\nfunction formatPrice(\n money: ShopifyProduct['priceRange']['minVariantPrice'],\n): string {\n const amount = parseFloat(money.amount);\n const formatted = amount.toFixed(2);\n return `${formatted} ${money.currencyCode}`;\n}\n\nfunction extractSizes(product: ShopifyProduct): string[] | undefined {\n const sizes = product.variants.edges.flatMap((e) =>\n e.node.selectedOptions\n .filter((o) => o.name.toLowerCase() === 'size')\n .map((o) => o.value),\n );\n const unique = [...new Set(sizes)];\n return unique.length > 0 ? unique : undefined;\n}\n\n/**\n * Extract variant ID → size label mapping for add-to-cart.\n * Shopify global IDs look like \"gid://shopify/ProductVariant/12345\" —\n * we extract just the numeric part for the Ajax API.\n */\nfunction extractVariants(product: ShopifyProduct): EntityItemData['variants'] {\n const variants = product.variants.edges.map((e) => {\n const sizeOption = e.node.selectedOptions.find(\n (o) => o.name.toLowerCase() === 'size',\n );\n const numericId = e.node.id.replace(\n /^gid:\\/\\/shopify\\/ProductVariant\\//,\n '',\n );\n return {\n label: sizeOption?.value || e.node.title,\n variantId: numericId,\n available: e.node.availableForSale,\n selectedOptions: e.node.selectedOptions.map((o) => ({\n name: o.name,\n value: o.value,\n })),\n price: formatPrice(e.node.price),\n compareAtPrice: e.node.compareAtPrice\n ? formatPrice(e.node.compareAtPrice)\n : undefined,\n };\n });\n return variants.length > 0 ? variants : undefined;\n}\n\nfunction getSecondaryImage(product: ShopifyProduct): string | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 1 ? images[1] : undefined;\n}\n\nfunction getAllImages(product: ShopifyProduct): string[] | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 0 ? images : undefined;\n}\n\nfunction getCompareAtPrice(product: ShopifyProduct): string | undefined {\n const compare = product.compareAtPriceRange?.minVariantPrice;\n if (!compare) {\n return undefined;\n }\n const min = product.priceRange.minVariantPrice;\n if (parseFloat(compare.amount) <= parseFloat(min.amount)) {\n return undefined;\n }\n return formatPrice(compare);\n}\n\n/**\n * The live entry for the variant the turn resolved, if the store still has it.\n * Both sides carry the bare numeric id — `extractVariants` strips the\n * `gid://shopify/ProductVariant/` prefix, and the turn never adds one.\n */\nfunction findLiveVariant(\n variants: EntityItemData['variants'],\n variantId: string | undefined,\n): NonNullable<EntityItemData['variants']>[number] | undefined {\n if (!variantId) {\n return undefined;\n }\n return variants?.find((variant) => variant.variantId === variantId);\n}\n\nexport function transformShopifyProduct(\n product: ShopifyProduct,\n variantId?: string,\n): EntityItemData {\n const variants = extractVariants(product);\n // The turn decided WHICH variant to show; the live fetch is here for what is\n // true about it right now. With no matched variant — or one the store has\n // since dropped — the product-level range is still the only answer we have.\n const matchedVariant = findLiveVariant(variants, variantId);\n\n return {\n url: product.onlineStoreUrl || '',\n title: product.title,\n type: 'product',\n description: product.description,\n imageUrl: product.featuredImage?.url,\n secondaryImageUrl: getSecondaryImage(product),\n vendor: product.vendor,\n price:\n matchedVariant?.price || formatPrice(product.priceRange.minVariantPrice),\n // Keyed on the variant, not on its value: an unmatched product falls back\n // to the product-level compare-at, but a matched variant that is simply\n // not on sale must not inherit one and print a false strikethrough.\n compareAtPrice: matchedVariant\n ? matchedVariant.compareAtPrice\n : getCompareAtPrice(product),\n available: matchedVariant?.available,\n sizes: extractSizes(product),\n variants,\n allImages: getAllImages(product),\n options: product.options?.length ? product.options : undefined,\n tags: product.tags.length ? product.tags : undefined,\n };\n}\n\nexport function transformShopifyCollection(\n collection: ShopifyCollection,\n): EntityItemData {\n return {\n url: collection.onlineStoreUrl || '',\n title: collection.title,\n type: 'collection',\n description: collection.description,\n imageUrl: collection.image?.url,\n };\n}\n\nexport function transformShopifyArticle(\n article: ShopifyArticle,\n): EntityItemData {\n return {\n url: article.onlineStoreUrl || '',\n title: article.title,\n type: 'article',\n description: article.excerpt || undefined,\n imageUrl: article.image?.url,\n publishDate: formatShopifyDate(article.publishedAt),\n author: article.authorV2?.name || undefined,\n tags: article.tags.length ? article.tags : undefined,\n };\n}\n\n/**\n * Transform any resolved Shopify entity to the standard EntityItemData shape.\n * Detects the entity type by checking for type-specific fields.\n */\nexport function transformShopifyEntityToItemData(\n entity: ShopifyProduct | ShopifyCollection | ShopifyArticle,\n entityType: string,\n variantId?: string,\n): EntityItemData {\n switch (entityType.toLowerCase()) {\n case 'product':\n return transformShopifyProduct(entity as ShopifyProduct, variantId);\n case 'collection':\n return transformShopifyCollection(entity as ShopifyCollection);\n case 'article':\n return transformShopifyArticle(entity as ShopifyArticle);\n default:\n return {\n url: '',\n title: (entity as { title?: string }).title || '',\n type: entityType,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["formatShopifyDate","isoDate","date","Date","isNaN","getTime","undefined","day","getDate","month","toLocaleString","year","getFullYear","formatPrice","money","amount","parseFloat","formatted","toFixed","currencyCode","extractSizes","product","sizes","variants","edges","flatMap","e","node","selectedOptions","filter","o","name","toLowerCase","map","value","unique","Set","length","extractVariants","sizeOption","find","numericId","id","replace","label","title","variantId","available","availableForSale","price","compareAtPrice","getSecondaryImage","images","url","getAllImages","getCompareAtPrice","_product$compareAtPri","compare","compareAtPriceRange","minVariantPrice","min","priceRange","findLiveVariant","variant","transformShopifyProduct","_product$featuredImag","_product$options","matchedVariant","pricesWholeProduct","onlineStoreUrl","type","description","imageUrl","featuredImage","secondaryImageUrl","vendor","priceMin","priceMax","maxVariantPrice","allImages","options","tags","transformShopifyCollection","collection","_collection$image","image","transformShopifyArticle","article","_article$image","_article$authorV","excerpt","publishDate","publishedAt","author","authorV2","transformShopifyEntityToItemData","entity","entityType"],"sources":["../../../../src/services/shopify/transformShopifyEntity.ts"],"sourcesContent":["import type { EntityItemData } from '../../component/types';\nimport type {\n ShopifyProduct,\n ShopifyCollection,\n ShopifyArticle,\n} from './types';\n\nfunction formatShopifyDate(isoDate: string): string | undefined {\n const date = new Date(isoDate);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const day = date.getDate();\n const month = date.toLocaleString('en-US', { month: 'short' });\n const year = date.getFullYear();\n return `${day} ${month} ${year}`;\n}\n\nfunction formatPrice(\n money: ShopifyProduct['priceRange']['minVariantPrice'],\n): string {\n const amount = parseFloat(money.amount);\n const formatted = amount.toFixed(2);\n return `${formatted} ${money.currencyCode}`;\n}\n\nfunction extractSizes(product: ShopifyProduct): string[] | undefined {\n const sizes = product.variants.edges.flatMap((e) =>\n e.node.selectedOptions\n .filter((o) => o.name.toLowerCase() === 'size')\n .map((o) => o.value),\n );\n const unique = [...new Set(sizes)];\n return unique.length > 0 ? unique : undefined;\n}\n\n/**\n * Extract variant ID → size label mapping for add-to-cart.\n * Shopify global IDs look like \"gid://shopify/ProductVariant/12345\" —\n * we extract just the numeric part for the Ajax API.\n */\nfunction extractVariants(product: ShopifyProduct): EntityItemData['variants'] {\n const variants = product.variants.edges.map((e) => {\n const sizeOption = e.node.selectedOptions.find(\n (o) => o.name.toLowerCase() === 'size',\n );\n const numericId = e.node.id.replace(\n /^gid:\\/\\/shopify\\/ProductVariant\\//,\n '',\n );\n return {\n label: sizeOption?.value || e.node.title,\n variantId: numericId,\n available: e.node.availableForSale,\n selectedOptions: e.node.selectedOptions.map((o) => ({\n name: o.name,\n value: o.value,\n })),\n price: formatPrice(e.node.price),\n compareAtPrice: e.node.compareAtPrice\n ? formatPrice(e.node.compareAtPrice)\n : undefined,\n };\n });\n return variants.length > 0 ? variants : undefined;\n}\n\nfunction getSecondaryImage(product: ShopifyProduct): string | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 1 ? images[1] : undefined;\n}\n\nfunction getAllImages(product: ShopifyProduct): string[] | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 0 ? images : undefined;\n}\n\nfunction getCompareAtPrice(product: ShopifyProduct): string | undefined {\n const compare = product.compareAtPriceRange?.minVariantPrice;\n if (!compare) {\n return undefined;\n }\n const min = product.priceRange.minVariantPrice;\n if (parseFloat(compare.amount) <= parseFloat(min.amount)) {\n return undefined;\n }\n return formatPrice(compare);\n}\n\n/**\n * The live entry for the variant the turn resolved, if the store still has it.\n * Both sides carry the bare numeric id — `extractVariants` strips the\n * `gid://shopify/ProductVariant/` prefix, and the turn never adds one.\n */\nfunction findLiveVariant(\n variants: EntityItemData['variants'],\n variantId: string | undefined,\n): NonNullable<EntityItemData['variants']>[number] | undefined {\n if (!variantId) {\n return undefined;\n }\n return variants?.find((variant) => variant.variantId === variantId);\n}\n\nexport function transformShopifyProduct(\n product: ShopifyProduct,\n variantId?: string,\n): EntityItemData {\n const variants = extractVariants(product);\n // The turn decided WHICH variant to show; the live fetch is here for what is\n // true about it right now.\n const matchedVariant = findLiveVariant(variants, variantId);\n // Whether this fetch is answering about the whole product or about one\n // child of it. A row that named a child but whose child the store no longer\n // returns — dropped since the turn, or past the `variants(first: 100)` page\n // — is NOT a whole-product row: answering it with the product's floor would\n // put a different variant's price under this variant's name, which is the\n // bug the row's own `variantId` exists to prevent. So the live price is\n // simply withheld and the turn's, which was right about this child, stands.\n const pricesWholeProduct = !variantId;\n\n return {\n url: product.onlineStoreUrl || '',\n title: product.title,\n type: 'product',\n description: product.description,\n imageUrl: product.featuredImage?.url,\n secondaryImageUrl: getSecondaryImage(product),\n vendor: product.vendor,\n price:\n matchedVariant?.price ??\n (pricesWholeProduct\n ? formatPrice(product.priceRange.minVariantPrice)\n : undefined),\n // The span the whole product covers, in the store's live currency. A card\n // reads it to decide whether \"from\" is honest; it is the product's either\n // way, so it is reported whether or not a child matched.\n priceMin: formatPrice(product.priceRange.minVariantPrice),\n priceMax: formatPrice(product.priceRange.maxVariantPrice),\n // Keyed on the variant, not on its value: a whole-product row falls back\n // to the product-level compare-at, but a matched variant that is simply\n // not on sale must not inherit one and print a false strikethrough.\n compareAtPrice: matchedVariant\n ? matchedVariant.compareAtPrice\n : pricesWholeProduct\n ? getCompareAtPrice(product)\n : undefined,\n available: matchedVariant?.available,\n sizes: extractSizes(product),\n variants,\n allImages: getAllImages(product),\n options: product.options?.length ? product.options : undefined,\n tags: product.tags.length ? product.tags : undefined,\n };\n}\n\nexport function transformShopifyCollection(\n collection: ShopifyCollection,\n): EntityItemData {\n return {\n url: collection.onlineStoreUrl || '',\n title: collection.title,\n type: 'collection',\n description: collection.description,\n imageUrl: collection.image?.url,\n };\n}\n\nexport function transformShopifyArticle(\n article: ShopifyArticle,\n): EntityItemData {\n return {\n url: article.onlineStoreUrl || '',\n title: article.title,\n type: 'article',\n description: article.excerpt || undefined,\n imageUrl: article.image?.url,\n publishDate: formatShopifyDate(article.publishedAt),\n author: article.authorV2?.name || undefined,\n tags: article.tags.length ? article.tags : undefined,\n };\n}\n\n/**\n * Transform any resolved Shopify entity to the standard EntityItemData shape.\n * Detects the entity type by checking for type-specific fields.\n */\nexport function transformShopifyEntityToItemData(\n entity: ShopifyProduct | ShopifyCollection | ShopifyArticle,\n entityType: string,\n variantId?: string,\n): EntityItemData {\n switch (entityType.toLowerCase()) {\n case 'product':\n return transformShopifyProduct(entity as ShopifyProduct, variantId);\n case 'collection':\n return transformShopifyCollection(entity as ShopifyCollection);\n case 'article':\n return transformShopifyArticle(entity as ShopifyArticle);\n default:\n return {\n url: '',\n title: (entity as { title?: string }).title || '',\n type: entityType,\n };\n }\n}\n"],"mappings":";;;;;;;AAOA,SAASA,iBAAiBA,CAACC,OAAe,EAAsB;EAC9D,MAAMC,IAAI,GAAG,IAAIC,IAAI,CAACF,OAAO,CAAC;EAC9B,IAAIG,KAAK,CAACF,IAAI,CAACG,OAAO,CAAC,CAAC,CAAC,EAAE;IACzB,OAAOC,SAAS;EAClB;EACA,MAAMC,GAAG,GAAGL,IAAI,CAACM,OAAO,CAAC,CAAC;EAC1B,MAAMC,KAAK,GAAGP,IAAI,CAACQ,cAAc,CAAC,OAAO,EAAE;IAAED,KAAK,EAAE;EAAQ,CAAC,CAAC;EAC9D,MAAME,IAAI,GAAGT,IAAI,CAACU,WAAW,CAAC,CAAC;EAC/B,OAAO,GAAGL,GAAG,IAAIE,KAAK,IAAIE,IAAI,EAAE;AAClC;AAEA,SAASE,WAAWA,CAClBC,KAAsD,EAC9C;EACR,MAAMC,MAAM,GAAGC,UAAU,CAACF,KAAK,CAACC,MAAM,CAAC;EACvC,MAAME,SAAS,GAAGF,MAAM,CAACG,OAAO,CAAC,CAAC,CAAC;EACnC,OAAO,GAAGD,SAAS,IAAIH,KAAK,CAACK,YAAY,EAAE;AAC7C;AAEA,SAASC,YAAYA,CAACC,OAAuB,EAAwB;EACnE,MAAMC,KAAK,GAAGD,OAAO,CAACE,QAAQ,CAACC,KAAK,CAACC,OAAO,CAAEC,CAAC,IAC7CA,CAAC,CAACC,IAAI,CAACC,eAAe,CACnBC,MAAM,CAAEC,CAAC,IAAKA,CAAC,CAACC,IAAI,CAACC,WAAW,CAAC,CAAC,KAAK,MAAM,CAAC,CAC9CC,GAAG,CAAEH,CAAC,IAAKA,CAAC,CAACI,KAAK,CACvB,CAAC;EACD,MAAMC,MAAM,GAAG,CAAC,GAAG,IAAIC,GAAG,CAACd,KAAK,CAAC,CAAC;EAClC,OAAOa,MAAM,CAACE,MAAM,GAAG,CAAC,GAAGF,MAAM,GAAG7B,SAAS;AAC/C;;AAEA;AACA;AACA;AACA;AACA;AACA,SAASgC,eAAeA,CAACjB,OAAuB,EAA8B;EAC5E,MAAME,QAAQ,GAAGF,OAAO,CAACE,QAAQ,CAACC,KAAK,CAACS,GAAG,CAAEP,CAAC,IAAK;IACjD,MAAMa,UAAU,GAAGb,CAAC,CAACC,IAAI,CAACC,eAAe,CAACY,IAAI,CAC3CV,CAAC,IAAKA,CAAC,CAACC,IAAI,CAACC,WAAW,CAAC,CAAC,KAAK,MAClC,CAAC;IACD,MAAMS,SAAS,GAAGf,CAAC,CAACC,IAAI,CAACe,EAAE,CAACC,OAAO,CACjC,oCAAoC,EACpC,EACF,CAAC;IACD,OAAO;MACLC,KAAK,EAAE,CAAAL,UAAU,oBAAVA,UAAU,CAAEL,KAAK,KAAIR,CAAC,CAACC,IAAI,CAACkB,KAAK;MACxCC,SAAS,EAAEL,SAAS;MACpBM,SAAS,EAAErB,CAAC,CAACC,IAAI,CAACqB,gBAAgB;MAClCpB,eAAe,EAAEF,CAAC,CAACC,IAAI,CAACC,eAAe,CAACK,GAAG,CAAEH,CAAC,KAAM;QAClDC,IAAI,EAAED,CAAC,CAACC,IAAI;QACZG,KAAK,EAAEJ,CAAC,CAACI;MACX,CAAC,CAAC,CAAC;MACHe,KAAK,EAAEpC,WAAW,CAACa,CAAC,CAACC,IAAI,CAACsB,KAAK,CAAC;MAChCC,cAAc,EAAExB,CAAC,CAACC,IAAI,CAACuB,cAAc,GACjCrC,WAAW,CAACa,CAAC,CAACC,IAAI,CAACuB,cAAc,CAAC,GAClC5C;IACN,CAAC;EACH,CAAC,CAAC;EACF,OAAOiB,QAAQ,CAACc,MAAM,GAAG,CAAC,GAAGd,QAAQ,GAAGjB,SAAS;AACnD;AAEA,SAAS6C,iBAAiBA,CAAC9B,OAAuB,EAAsB;EACtE,MAAM+B,MAAM,GAAG/B,OAAO,CAAC+B,MAAM,CAAC5B,KAAK,CAACS,GAAG,CAAEP,CAAC,IAAKA,CAAC,CAACC,IAAI,CAAC0B,GAAG,CAAC;EAC1D,OAAOD,MAAM,CAACf,MAAM,GAAG,CAAC,GAAGe,MAAM,CAAC,CAAC,CAAC,GAAG9C,SAAS;AAClD;AAEA,SAASgD,YAAYA,CAACjC,OAAuB,EAAwB;EACnE,MAAM+B,MAAM,GAAG/B,OAAO,CAAC+B,MAAM,CAAC5B,KAAK,CAACS,GAAG,CAAEP,CAAC,IAAKA,CAAC,CAACC,IAAI,CAAC0B,GAAG,CAAC;EAC1D,OAAOD,MAAM,CAACf,MAAM,GAAG,CAAC,GAAGe,MAAM,GAAG9C,SAAS;AAC/C;AAEA,SAASiD,iBAAiBA,CAAClC,OAAuB,EAAsB;EAAA,IAAAmC,qBAAA;EACtE,MAAMC,OAAO,IAAAD,qBAAA,GAAGnC,OAAO,CAACqC,mBAAmB,qBAA3BF,qBAAA,CAA6BG,eAAe;EAC5D,IAAI,CAACF,OAAO,EAAE;IACZ,OAAOnD,SAAS;EAClB;EACA,MAAMsD,GAAG,GAAGvC,OAAO,CAACwC,UAAU,CAACF,eAAe;EAC9C,IAAI3C,UAAU,CAACyC,OAAO,CAAC1C,MAAM,CAAC,IAAIC,UAAU,CAAC4C,GAAG,CAAC7C,MAAM,CAAC,EAAE;IACxD,OAAOT,SAAS;EAClB;EACA,OAAOO,WAAW,CAAC4C,OAAO,CAAC;AAC7B;;AAEA;AACA;AACA;AACA;AACA;AACA,SAASK,eAAeA,CACtBvC,QAAoC,EACpCuB,SAA6B,EACgC;EAC7D,IAAI,CAACA,SAAS,EAAE;IACd,OAAOxC,SAAS;EAClB;EACA,OAAOiB,QAAQ,oBAARA,QAAQ,CAAEiB,IAAI,CAAEuB,OAAO,IAAKA,OAAO,CAACjB,SAAS,KAAKA,SAAS,CAAC;AACrE;AAEO,SAASkB,uBAAuBA,CACrC3C,OAAuB,EACvByB,SAAkB,EACF;EAAA,IAAAmB,qBAAA,EAAAC,gBAAA;EAChB,MAAM3C,QAAQ,GAAGe,eAAe,CAACjB,OAAO,CAAC;EACzC;EACA;EACA,MAAM8C,cAAc,GAAGL,eAAe,CAACvC,QAAQ,EAAEuB,SAAS,CAAC;EAC3D;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMsB,kBAAkB,GAAG,CAACtB,SAAS;EAErC,OAAO;IACLO,GAAG,EAAEhC,OAAO,CAACgD,cAAc,IAAI,EAAE;IACjCxB,KAAK,EAAExB,OAAO,CAACwB,KAAK;IACpByB,IAAI,EAAE,SAAS;IACfC,WAAW,EAAElD,OAAO,CAACkD,WAAW;IAChCC,QAAQ,GAAAP,qBAAA,GAAE5C,OAAO,CAACoD,aAAa,qBAArBR,qBAAA,CAAuBZ,GAAG;IACpCqB,iBAAiB,EAAEvB,iBAAiB,CAAC9B,OAAO,CAAC;IAC7CsD,MAAM,EAAEtD,OAAO,CAACsD,MAAM;IACtB1B,KAAK,EACH,CAAAkB,cAAc,oBAAdA,cAAc,CAAElB,KAAK,MACpBmB,kBAAkB,GACfvD,WAAW,CAACQ,OAAO,CAACwC,UAAU,CAACF,eAAe,CAAC,GAC/CrD,SAAS,CAAC;IAChB;IACA;IACA;IACAsE,QAAQ,EAAE/D,WAAW,CAACQ,OAAO,CAACwC,UAAU,CAACF,eAAe,CAAC;IACzDkB,QAAQ,EAAEhE,WAAW,CAACQ,OAAO,CAACwC,UAAU,CAACiB,eAAe,CAAC;IACzD;IACA;IACA;IACA5B,cAAc,EAAEiB,cAAc,GAC1BA,cAAc,CAACjB,cAAc,GAC7BkB,kBAAkB,GAClBb,iBAAiB,CAAClC,OAAO,CAAC,GAC1Bf,SAAS;IACbyC,SAAS,EAAEoB,cAAc,oBAAdA,cAAc,CAAEpB,SAAS;IACpCzB,KAAK,EAAEF,YAAY,CAACC,OAAO,CAAC;IAC5BE,QAAQ;IACRwD,SAAS,EAAEzB,YAAY,CAACjC,OAAO,CAAC;IAChC2D,OAAO,EAAE,CAAAd,gBAAA,GAAA7C,OAAO,CAAC2D,OAAO,aAAfd,gBAAA,CAAiB7B,MAAM,GAAGhB,OAAO,CAAC2D,OAAO,GAAG1E,SAAS;IAC9D2E,IAAI,EAAE5D,OAAO,CAAC4D,IAAI,CAAC5C,MAAM,GAAGhB,OAAO,CAAC4D,IAAI,GAAG3E;EAC7C,CAAC;AACH;AAEO,SAAS4E,0BAA0BA,CACxCC,UAA6B,EACb;EAAA,IAAAC,iBAAA;EAChB,OAAO;IACL/B,GAAG,EAAE8B,UAAU,CAACd,cAAc,IAAI,EAAE;IACpCxB,KAAK,EAAEsC,UAAU,CAACtC,KAAK;IACvByB,IAAI,EAAE,YAAY;IAClBC,WAAW,EAAEY,UAAU,CAACZ,WAAW;IACnCC,QAAQ,GAAAY,iBAAA,GAAED,UAAU,CAACE,KAAK,qBAAhBD,iBAAA,CAAkB/B;EAC9B,CAAC;AACH;AAEO,SAASiC,uBAAuBA,CACrCC,OAAuB,EACP;EAAA,IAAAC,cAAA,EAAAC,gBAAA;EAChB,OAAO;IACLpC,GAAG,EAAEkC,OAAO,CAAClB,cAAc,IAAI,EAAE;IACjCxB,KAAK,EAAE0C,OAAO,CAAC1C,KAAK;IACpByB,IAAI,EAAE,SAAS;IACfC,WAAW,EAAEgB,OAAO,CAACG,OAAO,IAAIpF,SAAS;IACzCkE,QAAQ,GAAAgB,cAAA,GAAED,OAAO,CAACF,KAAK,qBAAbG,cAAA,CAAenC,GAAG;IAC5BsC,WAAW,EAAE3F,iBAAiB,CAACuF,OAAO,CAACK,WAAW,CAAC;IACnDC,MAAM,EAAE,EAAAJ,gBAAA,GAAAF,OAAO,CAACO,QAAQ,qBAAhBL,gBAAA,CAAkB1D,IAAI,KAAIzB,SAAS;IAC3C2E,IAAI,EAAEM,OAAO,CAACN,IAAI,CAAC5C,MAAM,GAAGkD,OAAO,CAACN,IAAI,GAAG3E;EAC7C,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACO,SAASyF,gCAAgCA,CAC9CC,MAA2D,EAC3DC,UAAkB,EAClBnD,SAAkB,EACF;EAChB,QAAQmD,UAAU,CAACjE,WAAW,CAAC,CAAC;IAC9B,KAAK,SAAS;MACZ,OAAOgC,uBAAuB,CAACgC,MAAM,EAAoBlD,SAAS,CAAC;IACrE,KAAK,YAAY;MACf,OAAOoC,0BAA0B,CAACc,MAA2B,CAAC;IAChE,KAAK,SAAS;MACZ,OAAOV,uBAAuB,CAACU,MAAwB,CAAC;IAC1D;MACE,OAAO;QACL3C,GAAG,EAAE,EAAE;QACPR,KAAK,EAAGmD,MAAM,CAAwBnD,KAAK,IAAI,EAAE;QACjDyB,IAAI,EAAE2B;MACR,CAAC;EACL;AACF","ignoreList":[]}
|
|
@@ -118,19 +118,38 @@ export function formatPriceField(value, currency) {
|
|
|
118
118
|
|
|
119
119
|
/** The already-formatted price fields a card reads. */
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Whether this row stands for one variant rather than for the whole product.
|
|
123
|
+
*
|
|
124
|
+
* Either mark is enough. `variantId` is the machine answer and `matchedOptions`
|
|
125
|
+
* the human one, and a row can arrive with only one of them: retrieval that
|
|
126
|
+
* resolved a document down to a child sends the pairs, while a row whose
|
|
127
|
+
* identity came from the `?variant=` on its own URL has only the id. Both mean
|
|
128
|
+
* the same thing — the card names a child, so the card must price that child.
|
|
129
|
+
*/
|
|
130
|
+
function namesOneVariant(fields) {
|
|
131
|
+
var _fields$matchedOption;
|
|
132
|
+
return Boolean(fields.variantId) || Boolean((_fields$matchedOption = fields.matchedOptions) == null ? void 0 : _fields$matchedOption.length);
|
|
133
|
+
}
|
|
134
|
+
|
|
121
135
|
/**
|
|
122
136
|
* What a product card should print as its price.
|
|
123
137
|
*
|
|
124
|
-
*
|
|
125
|
-
* the
|
|
126
|
-
*
|
|
127
|
-
* the
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
138
|
+
* A card that names a variant prices THAT variant. `price` belongs to the
|
|
139
|
+
* single child the search resolved to — the same child the card's image, its
|
|
140
|
+
* `?variant=` link and the pairs printed above the price all describe — so it
|
|
141
|
+
* is the only number that can follow them without contradicting them. "from"
|
|
142
|
+
* is wrong there twice over: it claims a range where the card named one thing,
|
|
143
|
+
* and the floor it points at is a different child's price wearing this one's
|
|
144
|
+
* name. That is what shipped: five colourways of one shoe, each naming its own
|
|
145
|
+
* colour and each printing the cheapest colour's price.
|
|
146
|
+
*
|
|
147
|
+
* `priceMin`/`priceMax` describe the whole product, and they are the answer
|
|
148
|
+
* only when the row IS the whole product. Then the span's floor prints
|
|
149
|
+
* prefixed, because no single number can stand for a product that has several.
|
|
131
150
|
*
|
|
132
|
-
* With no span — the
|
|
133
|
-
*
|
|
151
|
+
* With no span — the ends equal, or only one of them known — the row's own
|
|
152
|
+
* price prints as it always did.
|
|
134
153
|
*/
|
|
135
154
|
export function formatProductPriceLabel(fields) {
|
|
136
155
|
const {
|
|
@@ -138,6 +157,9 @@ export function formatProductPriceLabel(fields) {
|
|
|
138
157
|
priceMin,
|
|
139
158
|
priceMax
|
|
140
159
|
} = fields;
|
|
160
|
+
if (price && namesOneVariant(fields)) {
|
|
161
|
+
return price;
|
|
162
|
+
}
|
|
141
163
|
if (priceMin && priceMax && priceMin !== priceMax) {
|
|
142
164
|
return `from ${priceMin}`;
|
|
143
165
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["readText","value","trim","Number","isFinite","String","toMatchedOptions","raw","Array","isArray","undefined","pairs","rawName","rawValue","Object","entries","name","push","length","formatMoney","amount","currency","Intl","NumberFormat","style","minimumFractionDigits","isInteger","format","readCurrencyCode","_len","arguments","candidates","_key","candidate","code","toUpperCase","test","formatPriceField","formatProductPriceLabel","fields","price","priceMin","priceMax"],"sources":["../../../src/entity/matchedVariant.ts"],"sourcesContent":["/**\n * Presentation helpers for a document that retrieval resolved down to one\n * child.\n *\n * When a query filters on an attribute that lives on the child rather than on\n * the parent, retrieval overlays the matching child's presentation fields onto\n * the parent document before it reaches us: `url`, `img` and `price` already\n * describe the child, while `title`, `description` and `doc_id` stay the\n * parent's. Two fields need shaping before a card can print them, and both\n * live here:\n *\n * - `options` — the child's own `{ name: value }` pairs.\n * - `priceMin` / `priceMax` — the parent's span, against which the child's\n * single `price` has to be read.\n *\n * The overlay itself is done server-side and is deliberately NOT repeated\n * here.\n */\n\n/** One `{ name: value }` pair, exactly as the store named it. */\nexport interface MatchedOption {\n name: string;\n value: string;\n}\n\nfunction readText(value: unknown): string {\n if (typeof value === 'string') {\n return value.trim();\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return String(value);\n }\n return '';\n}\n\n/**\n * Turn a document's raw `options` map into an ordered list of pairs.\n *\n * Every entry the store put in the map is kept, under the store's own name and\n * in the store's own order. Nothing here knows — or may learn — what any\n * particular name means: one catalogue's pairs describe a garment, the next\n * one's a power tool, and both have to come out identical. Entries with an\n * empty name or an empty/non-printable value are dropped, since a card can do\n * nothing with them.\n *\n * Returns `undefined` (not `[]`) when there is nothing to show, so callers can\n * treat it like every other optional field on the entity.\n */\nexport function toMatchedOptions(raw: unknown): MatchedOption[] | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return undefined;\n }\n\n const pairs: MatchedOption[] = [];\n for (const [rawName, rawValue] of Object.entries(\n raw as Record<string, unknown>,\n )) {\n const name = rawName.trim();\n const value = readText(rawValue);\n if (name && value) {\n pairs.push({ name, value });\n }\n }\n\n return pairs.length ? pairs : undefined;\n}\n\n/**\n * Render one amount in the document's currency.\n *\n * With no currency code on the document the amount is printed bare: assuming\n * \"$\" prints a false fact on every store that doesn't sell in dollars. Whole\n * amounts stay whole (\"107\" not \"107.00\"); a real fractional amount keeps its\n * minor units.\n */\nexport function formatMoney(amount: number, currency?: string): string {\n if (!currency) {\n return String(amount);\n }\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,\n }).format(amount);\n } catch {\n // A well-formed but unknown code: show it rather than dropping it.\n return `${amount} ${currency}`;\n }\n}\n\n/** Normalize a document currency to an ISO-4217 code, or drop it. */\nexport function readCurrencyCode(...candidates: unknown[]): string | undefined {\n for (const candidate of candidates) {\n if (typeof candidate !== 'string') {\n continue;\n }\n const code = candidate.trim().toUpperCase();\n if (/^[A-Z]{3}$/.test(code)) {\n return code;\n }\n }\n return undefined;\n}\n\n/**\n * Format one of a document's price fields for display. Numbers are rendered in\n * the document's currency; an already-formatted string is passed through\n * untouched; anything else yields `undefined`.\n */\nexport function formatPriceField(\n value: unknown,\n currency?: string,\n): string | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return formatMoney(value, currency);\n }\n if (typeof value === 'string' && value.trim()) {\n return value.trim();\n }\n return undefined;\n}\n\n/** The already-formatted price fields a card reads. */\nexport interface ProductPriceFields {\n /** Price of the one variant this result matched. */\n price?: string;\n /** Lowest price across the whole product's variants. */\n priceMin?: string;\n /** Highest price across the whole product's variants. */\n priceMax?: string;\n}\n\n/**\n * What a product card should print as its price.\n *\n * `price` belongs to the single variant the search matched — the same variant\n * the card's image and link point at. `priceMin`/`priceMax` describe the whole\n * product. When those two differ the product has no one price, and printing\n * the matched variant's number on its own would pass it off as the product's;\n * printing it after \"from\" would be worse still, because \"from\" is a claim\n * about the floor of the span and the matched variant is not always the\n * cheapest one. So a span prints its own floor, prefixed.\n *\n * With no span — the two ends equal, or only one of them known — nothing\n * changes: the matched variant's price is printed as it always was.\n */\nexport function formatProductPriceLabel(\n fields: ProductPriceFields,\n): string | undefined {\n const { price, priceMin, priceMax } = fields;\n if (priceMin && priceMax && priceMin !== priceMax) {\n return `from ${priceMin}`;\n }\n return price;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["readText","value","trim","Number","isFinite","String","toMatchedOptions","raw","Array","isArray","undefined","pairs","rawName","rawValue","Object","entries","name","push","length","formatMoney","amount","currency","Intl","NumberFormat","style","minimumFractionDigits","isInteger","format","readCurrencyCode","_len","arguments","candidates","_key","candidate","code","toUpperCase","test","formatPriceField","namesOneVariant","fields","_fields$matchedOption","Boolean","variantId","matchedOptions","formatProductPriceLabel","price","priceMin","priceMax"],"sources":["../../../src/entity/matchedVariant.ts"],"sourcesContent":["/**\n * Presentation helpers for a document that retrieval resolved down to one\n * child.\n *\n * When a query filters on an attribute that lives on the child rather than on\n * the parent, retrieval overlays the matching child's presentation fields onto\n * the parent document before it reaches us: `url`, `img` and `price` already\n * describe the child, while `title`, `description` and `doc_id` stay the\n * parent's. Two fields need shaping before a card can print them, and both\n * live here:\n *\n * - `options` — the child's own `{ name: value }` pairs.\n * - `priceMin` / `priceMax` — the parent's span, against which the child's\n * single `price` has to be read.\n *\n * The overlay itself is done server-side and is deliberately NOT repeated\n * here.\n */\n\n/** One `{ name: value }` pair, exactly as the store named it. */\nexport interface MatchedOption {\n name: string;\n value: string;\n}\n\nfunction readText(value: unknown): string {\n if (typeof value === 'string') {\n return value.trim();\n }\n if (typeof value === 'number' && Number.isFinite(value)) {\n return String(value);\n }\n return '';\n}\n\n/**\n * Turn a document's raw `options` map into an ordered list of pairs.\n *\n * Every entry the store put in the map is kept, under the store's own name and\n * in the store's own order. Nothing here knows — or may learn — what any\n * particular name means: one catalogue's pairs describe a garment, the next\n * one's a power tool, and both have to come out identical. Entries with an\n * empty name or an empty/non-printable value are dropped, since a card can do\n * nothing with them.\n *\n * Returns `undefined` (not `[]`) when there is nothing to show, so callers can\n * treat it like every other optional field on the entity.\n */\nexport function toMatchedOptions(raw: unknown): MatchedOption[] | undefined {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return undefined;\n }\n\n const pairs: MatchedOption[] = [];\n for (const [rawName, rawValue] of Object.entries(\n raw as Record<string, unknown>,\n )) {\n const name = rawName.trim();\n const value = readText(rawValue);\n if (name && value) {\n pairs.push({ name, value });\n }\n }\n\n return pairs.length ? pairs : undefined;\n}\n\n/**\n * Render one amount in the document's currency.\n *\n * With no currency code on the document the amount is printed bare: assuming\n * \"$\" prints a false fact on every store that doesn't sell in dollars. Whole\n * amounts stay whole (\"107\" not \"107.00\"); a real fractional amount keeps its\n * minor units.\n */\nexport function formatMoney(amount: number, currency?: string): string {\n if (!currency) {\n return String(amount);\n }\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,\n }).format(amount);\n } catch {\n // A well-formed but unknown code: show it rather than dropping it.\n return `${amount} ${currency}`;\n }\n}\n\n/** Normalize a document currency to an ISO-4217 code, or drop it. */\nexport function readCurrencyCode(...candidates: unknown[]): string | undefined {\n for (const candidate of candidates) {\n if (typeof candidate !== 'string') {\n continue;\n }\n const code = candidate.trim().toUpperCase();\n if (/^[A-Z]{3}$/.test(code)) {\n return code;\n }\n }\n return undefined;\n}\n\n/**\n * Format one of a document's price fields for display. Numbers are rendered in\n * the document's currency; an already-formatted string is passed through\n * untouched; anything else yields `undefined`.\n */\nexport function formatPriceField(\n value: unknown,\n currency?: string,\n): string | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return formatMoney(value, currency);\n }\n if (typeof value === 'string' && value.trim()) {\n return value.trim();\n }\n return undefined;\n}\n\n/** The already-formatted price fields a card reads. */\nexport interface ProductPriceFields {\n /** Price of the one variant this result matched. */\n price?: string;\n /** Lowest price across the whole product's variants. */\n priceMin?: string;\n /** Highest price across the whole product's variants. */\n priceMax?: string;\n /** Id of the variant this row was resolved down to, when there was one. */\n variantId?: string;\n /** The pairs naming that variant — what the card prints above the price. */\n matchedOptions?: MatchedOption[];\n}\n\n/**\n * Whether this row stands for one variant rather than for the whole product.\n *\n * Either mark is enough. `variantId` is the machine answer and `matchedOptions`\n * the human one, and a row can arrive with only one of them: retrieval that\n * resolved a document down to a child sends the pairs, while a row whose\n * identity came from the `?variant=` on its own URL has only the id. Both mean\n * the same thing — the card names a child, so the card must price that child.\n */\nfunction namesOneVariant(fields: ProductPriceFields): boolean {\n return Boolean(fields.variantId) || Boolean(fields.matchedOptions?.length);\n}\n\n/**\n * What a product card should print as its price.\n *\n * A card that names a variant prices THAT variant. `price` belongs to the\n * single child the search resolved to — the same child the card's image, its\n * `?variant=` link and the pairs printed above the price all describe — so it\n * is the only number that can follow them without contradicting them. \"from\"\n * is wrong there twice over: it claims a range where the card named one thing,\n * and the floor it points at is a different child's price wearing this one's\n * name. That is what shipped: five colourways of one shoe, each naming its own\n * colour and each printing the cheapest colour's price.\n *\n * `priceMin`/`priceMax` describe the whole product, and they are the answer\n * only when the row IS the whole product. Then the span's floor prints\n * prefixed, because no single number can stand for a product that has several.\n *\n * With no span — the ends equal, or only one of them known — the row's own\n * price prints as it always did.\n */\nexport function formatProductPriceLabel(\n fields: ProductPriceFields,\n): string | undefined {\n const { price, priceMin, priceMax } = fields;\n if (price && namesOneVariant(fields)) {\n return price;\n }\n if (priceMin && priceMax && priceMin !== priceMax) {\n return `from ${priceMin}`;\n }\n return price;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -101,6 +101,12 @@ export function mergeShopifyEntityData(existing, resolved) {
|
|
|
101
101
|
url: existing.url || resolved.url,
|
|
102
102
|
title: existing.title || resolved.title,
|
|
103
103
|
imageUrl: existing.imageUrl || resolved.imageUrl,
|
|
104
|
+
// The live fetch withholds a price when it was asked about a variant it
|
|
105
|
+
// could not find, rather than answering with the product's floor. Its
|
|
106
|
+
// silence must not erase the turn's number, which was about the right
|
|
107
|
+
// child; a spread would overwrite with `undefined` and leave the card
|
|
108
|
+
// priceless.
|
|
109
|
+
price: resolved.price ?? existing.price,
|
|
104
110
|
variantId: existing.variantId || resolved.variantId,
|
|
105
111
|
matchedOptions: (_existing$matchedOpti = existing.matchedOptions) != null && _existing$matchedOpti.length ? existing.matchedOptions : resolved.matchedOptions
|
|
106
112
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useState","useEffect","useMemo","useRef","ShopifyStorefrontClient","resolveShopifyEntity","transformShopifyEntityToItemData","shopifyEntityCache","Map","cacheKey","entityType","entityId","variantId","normalizeShopifyEntityType","lower","toLowerCase","includes","extractHandleFromUrl","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","slice","length","collectionsIndex","variantIdFromUrl","_URLSearchParams$get","query","URLSearchParams","get","getShopifyLookup","entity","_entity$data","_entity$data2","_entity$data3","urlHandle","data","mergeShopifyEntityData","existing","resolved","_existing$matchedOpti","title","imageUrl","matchedOptions","useResolveShopifyEntityData","entities","shopifyConfig","resolvedMap","setResolvedMap","settled","setSettled","clientRef","configKeyRef","configKey","storeDomain","apiVersion","current","entityIds","map","e","lookup","join","ignore","client","entityLookups","key","toFetch","filter","_ref","has","cached","forEach","_ref2","item","set","fetchAll","Promise","allSettled","_ref3","itemData","finalMap","_ref4","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useRef } from 'react';\nimport { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient';\nimport { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver';\nimport { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity';\nimport type { ShopifyStorefrontConfig } from '../services/shopify/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: entityType:entityId:variantId → resolved EntityItemData\nconst shopifyEntityCache = new Map<string, EntityItemData>();\n\nfunction cacheKey(\n entityType: string,\n entityId: string,\n variantId?: string,\n): string {\n return `${entityType}:${entityId}:${variantId || ''}`;\n}\n\nfunction normalizeShopifyEntityType(entityType: string): string {\n const lower = entityType.toLowerCase();\n // A variant is not a Storefront resource of its own — there is no\n // `variantByHandle`. It is fetched as its parent product and then priced at\n // the variant the lookup carries alongside, which is why `variant` and\n // `product` normalize to the same query here rather than to two.\n if (lower.includes('product') || lower.includes('variant')) {\n return 'product';\n }\n if (lower.includes('collection')) {\n return 'collection';\n }\n if (lower.includes('article') || lower.includes('blog')) {\n return 'article';\n }\n return entityType;\n}\n\nfunction extractHandleFromUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n if (productsIndex >= 0) {\n return withoutLeadingSlash.slice(productsIndex + 'products/'.length);\n }\n\n const collectionsIndex = withoutLeadingSlash.indexOf('collections/');\n if (collectionsIndex >= 0) {\n return withoutLeadingSlash.slice(\n collectionsIndex + 'collections/'.length,\n );\n }\n\n return withoutLeadingSlash || undefined;\n}\n\n/**\n * The `?variant=` the row's own URL already carries.\n *\n * A row that names a variant — whether the turn overlaid one onto a product or\n * sent the variant as the entity itself — always links to it, because that is\n * the URL a shopper has to land on. So when the row omits `variantId` as a\n * field, the URL still says which child it means, and pricing the parent's\n * default instead would contradict the link on the same card.\n */\nfunction variantIdFromUrl(url: string | undefined): string | undefined {\n const query = url?.split('#')[0].split('?')[1];\n if (!query) {\n return undefined;\n }\n return new URLSearchParams(query).get('variant')?.trim() || undefined;\n}\n\nexport function getShopifyLookup(entity: {\n entityId: string;\n entityType: string;\n data?: EntityItemData;\n}) {\n const entityType = normalizeShopifyEntityType(entity.entityType);\n const urlHandle = extractHandleFromUrl(entity.data?.url);\n const entityId =\n (entityType === 'product' || entityType === 'collection') && urlHandle\n ? urlHandle\n : entity.entityId;\n return {\n entityType,\n entityId,\n variantId: entity.data?.variantId ?? variantIdFromUrl(entity.data?.url),\n };\n}\n\n/**\n * The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about\n * it.\n *\n * Only the turn saw the shopper's question, so only it knows that the brown\n * variant was the one asked for — its `title`, `imageUrl`, `url`,\n * `matchedOptions` and `variantId` all describe that one variant and must\n * survive enrichment. Everything the Storefront answers with that can go stale\n * between the turn and the render — `price`, `compareAtPrice`, `available`,\n * `priceMin`, `priceMax` — comes from `resolved`.\n */\nexport function mergeShopifyEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n title: existing.title || resolved.title,\n imageUrl: existing.imageUrl || resolved.imageUrl,\n variantId: existing.variantId || resolved.variantId,\n matchedOptions: existing.matchedOptions?.length\n ? existing.matchedOptions\n : resolved.matchedOptions,\n };\n}\n\n/**\n * Shopify-specific implementation of the entity resolution pattern.\n *\n * Fetches entity data from the Shopify Storefront API (tokenless) and\n * transforms it to the standard EntityItemData shape.\n *\n * Designed to be plugged into ComponentDependencies.useResolveGenericEntityData.\n */\nexport function useResolveShopifyEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n shopifyConfig: ShopifyStorefrontConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable client reference — recreated only when config changes\n const clientRef = useRef<ShopifyStorefrontClient | null>(null);\n const configKeyRef = useRef('');\n const configKey = `${shopifyConfig.storeDomain}|${\n shopifyConfig.apiVersion || ''\n }`;\n\n if (!clientRef.current || configKeyRef.current !== configKey) {\n clientRef.current = new ShopifyStorefrontClient(shopifyConfig);\n configKeyRef.current = configKey;\n }\n\n const entityIds = useMemo(\n () =>\n entities\n .map((e) => {\n const lookup = getShopifyLookup(e);\n return `${e.entityType}:${e.entityId}:${cacheKey(\n lookup.entityType,\n lookup.entityId,\n lookup.variantId,\n )}`;\n })\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n const client = clientRef.current;\n\n if (!entities.length || !client || !shopifyConfig.storeDomain) {\n setSettled(true);\n return;\n }\n\n const entityLookups = entities.map((entity) => {\n const lookup = getShopifyLookup(entity);\n return {\n entity,\n lookup,\n // The variant is part of the key: the cached entry carries that\n // variant's live price, so two entities on the same product but\n // different variants must not share it.\n key: cacheKey(lookup.entityType, lookup.entityId, lookup.variantId),\n };\n });\n\n // Identify entities that need fetching. Payload data can provide the handle.\n const toFetch = entityLookups.filter(\n ({ key }) => !shopifyEntityCache.has(key),\n );\n\n // If everything is cached, resolve immediately\n if (toFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n cached.set(entity.entityId, mergeShopifyEntityData(entity.data, item));\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n await Promise.allSettled(\n toFetch.map(async ({ entity, lookup, key }) => {\n const resolved = await resolveShopifyEntity(\n client,\n lookup.entityType,\n lookup.entityId,\n shopifyConfig,\n );\n if (resolved) {\n const itemData = transformShopifyEntityToItemData(\n resolved,\n lookup.entityType,\n lookup.variantId,\n );\n shopifyEntityCache.set(\n key,\n mergeShopifyEntityData(entity.data, itemData),\n );\n }\n }),\n );\n\n if (ignore) {\n return;\n }\n\n // Build the resolved map from cache\n const finalMap = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n finalMap.set(\n entity.entityId,\n mergeShopifyEntityData(entity.data, item),\n );\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityIds, shopifyConfig.storeDomain]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return { ...entity, data: resolved };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["useState","useEffect","useMemo","useRef","ShopifyStorefrontClient","resolveShopifyEntity","transformShopifyEntityToItemData","shopifyEntityCache","Map","cacheKey","entityType","entityId","variantId","normalizeShopifyEntityType","lower","toLowerCase","includes","extractHandleFromUrl","url","trimmed","trim","undefined","pathname","test","URL","withoutQuery","split","withoutLeadingSlash","replace","productsIndex","indexOf","slice","length","collectionsIndex","variantIdFromUrl","_URLSearchParams$get","query","URLSearchParams","get","getShopifyLookup","entity","_entity$data","_entity$data2","_entity$data3","urlHandle","data","mergeShopifyEntityData","existing","resolved","_existing$matchedOpti","title","imageUrl","price","matchedOptions","useResolveShopifyEntityData","entities","shopifyConfig","resolvedMap","setResolvedMap","settled","setSettled","clientRef","configKeyRef","configKey","storeDomain","apiVersion","current","entityIds","map","e","lookup","join","ignore","client","entityLookups","key","toFetch","filter","_ref","has","cached","forEach","_ref2","item","set","fetchAll","Promise","allSettled","_ref3","itemData","finalMap","_ref4","enrichedEntities","loading"],"sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"sourcesContent":["import { useState, useEffect, useMemo, useRef } from 'react';\nimport { ShopifyStorefrontClient } from '../services/shopify/shopifyStorefrontClient';\nimport { resolveShopifyEntity } from '../services/shopify/shopifyEntityResolver';\nimport { transformShopifyEntityToItemData } from '../services/shopify/transformShopifyEntity';\nimport type { ShopifyStorefrontConfig } from '../services/shopify/types';\nimport type { EntityItemData } from '../component/types';\n\n// Session-level cache: entityType:entityId:variantId → resolved EntityItemData\nconst shopifyEntityCache = new Map<string, EntityItemData>();\n\nfunction cacheKey(\n entityType: string,\n entityId: string,\n variantId?: string,\n): string {\n return `${entityType}:${entityId}:${variantId || ''}`;\n}\n\nfunction normalizeShopifyEntityType(entityType: string): string {\n const lower = entityType.toLowerCase();\n // A variant is not a Storefront resource of its own — there is no\n // `variantByHandle`. It is fetched as its parent product and then priced at\n // the variant the lookup carries alongside, which is why `variant` and\n // `product` normalize to the same query here rather than to two.\n if (lower.includes('product') || lower.includes('variant')) {\n return 'product';\n }\n if (lower.includes('collection')) {\n return 'collection';\n }\n if (lower.includes('article') || lower.includes('blog')) {\n return 'article';\n }\n return entityType;\n}\n\nfunction extractHandleFromUrl(url: string | undefined): string | undefined {\n const trimmed = url?.trim();\n if (!trimmed) {\n return undefined;\n }\n\n let pathname = trimmed;\n try {\n if (/^https?:\\/\\//i.test(trimmed)) {\n pathname = new URL(trimmed).pathname;\n }\n } catch {\n pathname = trimmed;\n }\n\n const withoutQuery = pathname.split(/[?#]/)[0];\n const withoutLeadingSlash = withoutQuery.replace(/^\\/+/, '');\n const productsIndex = withoutLeadingSlash.indexOf('products/');\n if (productsIndex >= 0) {\n return withoutLeadingSlash.slice(productsIndex + 'products/'.length);\n }\n\n const collectionsIndex = withoutLeadingSlash.indexOf('collections/');\n if (collectionsIndex >= 0) {\n return withoutLeadingSlash.slice(\n collectionsIndex + 'collections/'.length,\n );\n }\n\n return withoutLeadingSlash || undefined;\n}\n\n/**\n * The `?variant=` the row's own URL already carries.\n *\n * A row that names a variant — whether the turn overlaid one onto a product or\n * sent the variant as the entity itself — always links to it, because that is\n * the URL a shopper has to land on. So when the row omits `variantId` as a\n * field, the URL still says which child it means, and pricing the parent's\n * default instead would contradict the link on the same card.\n */\nfunction variantIdFromUrl(url: string | undefined): string | undefined {\n const query = url?.split('#')[0].split('?')[1];\n if (!query) {\n return undefined;\n }\n return new URLSearchParams(query).get('variant')?.trim() || undefined;\n}\n\nexport function getShopifyLookup(entity: {\n entityId: string;\n entityType: string;\n data?: EntityItemData;\n}) {\n const entityType = normalizeShopifyEntityType(entity.entityType);\n const urlHandle = extractHandleFromUrl(entity.data?.url);\n const entityId =\n (entityType === 'product' || entityType === 'collection') && urlHandle\n ? urlHandle\n : entity.entityId;\n return {\n entityType,\n entityId,\n variantId: entity.data?.variantId ?? variantIdFromUrl(entity.data?.url),\n };\n}\n\n/**\n * The turn owns WHICH thing to show; the live fetch owns VOLATILE FACTS about\n * it.\n *\n * Only the turn saw the shopper's question, so only it knows that the brown\n * variant was the one asked for — its `title`, `imageUrl`, `url`,\n * `matchedOptions` and `variantId` all describe that one variant and must\n * survive enrichment. Everything the Storefront answers with that can go stale\n * between the turn and the render — `price`, `compareAtPrice`, `available`,\n * `priceMin`, `priceMax` — comes from `resolved`.\n */\nexport function mergeShopifyEntityData(\n existing: EntityItemData | undefined,\n resolved: EntityItemData,\n): EntityItemData {\n if (!existing) {\n return resolved;\n }\n\n return {\n ...existing,\n ...resolved,\n url: existing.url || resolved.url,\n title: existing.title || resolved.title,\n imageUrl: existing.imageUrl || resolved.imageUrl,\n // The live fetch withholds a price when it was asked about a variant it\n // could not find, rather than answering with the product's floor. Its\n // silence must not erase the turn's number, which was about the right\n // child; a spread would overwrite with `undefined` and leave the card\n // priceless.\n price: resolved.price ?? existing.price,\n variantId: existing.variantId || resolved.variantId,\n matchedOptions: existing.matchedOptions?.length\n ? existing.matchedOptions\n : resolved.matchedOptions,\n };\n}\n\n/**\n * Shopify-specific implementation of the entity resolution pattern.\n *\n * Fetches entity data from the Shopify Storefront API (tokenless) and\n * transforms it to the standard EntityItemData shape.\n *\n * Designed to be plugged into ComponentDependencies.useResolveGenericEntityData.\n */\nexport function useResolveShopifyEntityData<\n TEntity extends {\n entityId: string;\n entityUrl: string;\n entityType: string;\n data?: EntityItemData;\n },\n>(\n entities: TEntity[],\n shopifyConfig: ShopifyStorefrontConfig,\n): { enrichedEntities: TEntity[]; loading: boolean } {\n const [resolvedMap, setResolvedMap] = useState<Map<string, EntityItemData>>(\n new Map(),\n );\n const [settled, setSettled] = useState(false);\n\n // Stable client reference — recreated only when config changes\n const clientRef = useRef<ShopifyStorefrontClient | null>(null);\n const configKeyRef = useRef('');\n const configKey = `${shopifyConfig.storeDomain}|${\n shopifyConfig.apiVersion || ''\n }`;\n\n if (!clientRef.current || configKeyRef.current !== configKey) {\n clientRef.current = new ShopifyStorefrontClient(shopifyConfig);\n configKeyRef.current = configKey;\n }\n\n const entityIds = useMemo(\n () =>\n entities\n .map((e) => {\n const lookup = getShopifyLookup(e);\n return `${e.entityType}:${e.entityId}:${cacheKey(\n lookup.entityType,\n lookup.entityId,\n lookup.variantId,\n )}`;\n })\n .join(','),\n [entities],\n );\n\n useEffect(() => {\n let ignore = false;\n const client = clientRef.current;\n\n if (!entities.length || !client || !shopifyConfig.storeDomain) {\n setSettled(true);\n return;\n }\n\n const entityLookups = entities.map((entity) => {\n const lookup = getShopifyLookup(entity);\n return {\n entity,\n lookup,\n // The variant is part of the key: the cached entry carries that\n // variant's live price, so two entities on the same product but\n // different variants must not share it.\n key: cacheKey(lookup.entityType, lookup.entityId, lookup.variantId),\n };\n });\n\n // Identify entities that need fetching. Payload data can provide the handle.\n const toFetch = entityLookups.filter(\n ({ key }) => !shopifyEntityCache.has(key),\n );\n\n // If everything is cached, resolve immediately\n if (toFetch.length === 0) {\n const cached = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n cached.set(entity.entityId, mergeShopifyEntityData(entity.data, item));\n }\n });\n setResolvedMap(cached);\n setSettled(true);\n return;\n }\n\n setSettled(false);\n\n const fetchAll = async () => {\n await Promise.allSettled(\n toFetch.map(async ({ entity, lookup, key }) => {\n const resolved = await resolveShopifyEntity(\n client,\n lookup.entityType,\n lookup.entityId,\n shopifyConfig,\n );\n if (resolved) {\n const itemData = transformShopifyEntityToItemData(\n resolved,\n lookup.entityType,\n lookup.variantId,\n );\n shopifyEntityCache.set(\n key,\n mergeShopifyEntityData(entity.data, itemData),\n );\n }\n }),\n );\n\n if (ignore) {\n return;\n }\n\n // Build the resolved map from cache\n const finalMap = new Map<string, EntityItemData>();\n entityLookups.forEach(({ entity, key }) => {\n const item = shopifyEntityCache.get(key);\n if (item) {\n finalMap.set(\n entity.entityId,\n mergeShopifyEntityData(entity.data, item),\n );\n }\n });\n\n setResolvedMap(finalMap);\n setSettled(true);\n };\n\n fetchAll();\n return () => {\n ignore = true;\n };\n }, [entityIds, shopifyConfig.storeDomain]);\n\n // Merge resolved data into entities\n const enrichedEntities = useMemo(() => {\n return entities.map((entity) => {\n const resolved = resolvedMap.get(entity.entityId);\n if (resolved) {\n return { ...entity, data: resolved };\n }\n return entity;\n });\n }, [entities, resolvedMap]);\n\n return { enrichedEntities, loading: !settled };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -80,9 +80,16 @@ export function transformShopifyProduct(product, variantId) {
|
|
|
80
80
|
var _product$featuredImag, _product$options;
|
|
81
81
|
const variants = extractVariants(product);
|
|
82
82
|
// The turn decided WHICH variant to show; the live fetch is here for what is
|
|
83
|
-
// true about it right now.
|
|
84
|
-
// since dropped — the product-level range is still the only answer we have.
|
|
83
|
+
// true about it right now.
|
|
85
84
|
const matchedVariant = findLiveVariant(variants, variantId);
|
|
85
|
+
// Whether this fetch is answering about the whole product or about one
|
|
86
|
+
// child of it. A row that named a child but whose child the store no longer
|
|
87
|
+
// returns — dropped since the turn, or past the `variants(first: 100)` page
|
|
88
|
+
// — is NOT a whole-product row: answering it with the product's floor would
|
|
89
|
+
// put a different variant's price under this variant's name, which is the
|
|
90
|
+
// bug the row's own `variantId` exists to prevent. So the live price is
|
|
91
|
+
// simply withheld and the turn's, which was right about this child, stands.
|
|
92
|
+
const pricesWholeProduct = !variantId;
|
|
86
93
|
return {
|
|
87
94
|
url: product.onlineStoreUrl || '',
|
|
88
95
|
title: product.title,
|
|
@@ -91,11 +98,16 @@ export function transformShopifyProduct(product, variantId) {
|
|
|
91
98
|
imageUrl: (_product$featuredImag = product.featuredImage) == null ? void 0 : _product$featuredImag.url,
|
|
92
99
|
secondaryImageUrl: getSecondaryImage(product),
|
|
93
100
|
vendor: product.vendor,
|
|
94
|
-
price: (matchedVariant == null ? void 0 : matchedVariant.price)
|
|
95
|
-
//
|
|
101
|
+
price: (matchedVariant == null ? void 0 : matchedVariant.price) ?? (pricesWholeProduct ? formatPrice(product.priceRange.minVariantPrice) : undefined),
|
|
102
|
+
// The span the whole product covers, in the store's live currency. A card
|
|
103
|
+
// reads it to decide whether "from" is honest; it is the product's either
|
|
104
|
+
// way, so it is reported whether or not a child matched.
|
|
105
|
+
priceMin: formatPrice(product.priceRange.minVariantPrice),
|
|
106
|
+
priceMax: formatPrice(product.priceRange.maxVariantPrice),
|
|
107
|
+
// Keyed on the variant, not on its value: a whole-product row falls back
|
|
96
108
|
// to the product-level compare-at, but a matched variant that is simply
|
|
97
109
|
// not on sale must not inherit one and print a false strikethrough.
|
|
98
|
-
compareAtPrice: matchedVariant ? matchedVariant.compareAtPrice : getCompareAtPrice(product),
|
|
110
|
+
compareAtPrice: matchedVariant ? matchedVariant.compareAtPrice : pricesWholeProduct ? getCompareAtPrice(product) : undefined,
|
|
99
111
|
available: matchedVariant == null ? void 0 : matchedVariant.available,
|
|
100
112
|
sizes: extractSizes(product),
|
|
101
113
|
variants,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["formatShopifyDate","isoDate","date","Date","isNaN","getTime","undefined","day","getDate","month","toLocaleString","year","getFullYear","formatPrice","money","amount","parseFloat","formatted","toFixed","currencyCode","extractSizes","product","sizes","variants","edges","flatMap","e","node","selectedOptions","filter","o","name","toLowerCase","map","value","unique","Set","length","extractVariants","sizeOption","find","numericId","id","replace","label","title","variantId","available","availableForSale","price","compareAtPrice","getSecondaryImage","images","url","getAllImages","getCompareAtPrice","_product$compareAtPri","compare","compareAtPriceRange","minVariantPrice","min","priceRange","findLiveVariant","variant","transformShopifyProduct","_product$featuredImag","_product$options","matchedVariant","onlineStoreUrl","type","description","imageUrl","featuredImage","secondaryImageUrl","vendor","allImages","options","tags","transformShopifyCollection","collection","_collection$image","image","transformShopifyArticle","article","_article$image","_article$authorV","excerpt","publishDate","publishedAt","author","authorV2","transformShopifyEntityToItemData","entity","entityType"],"sources":["../../../../src/services/shopify/transformShopifyEntity.ts"],"sourcesContent":["import type { EntityItemData } from '../../component/types';\nimport type {\n ShopifyProduct,\n ShopifyCollection,\n ShopifyArticle,\n} from './types';\n\nfunction formatShopifyDate(isoDate: string): string | undefined {\n const date = new Date(isoDate);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const day = date.getDate();\n const month = date.toLocaleString('en-US', { month: 'short' });\n const year = date.getFullYear();\n return `${day} ${month} ${year}`;\n}\n\nfunction formatPrice(\n money: ShopifyProduct['priceRange']['minVariantPrice'],\n): string {\n const amount = parseFloat(money.amount);\n const formatted = amount.toFixed(2);\n return `${formatted} ${money.currencyCode}`;\n}\n\nfunction extractSizes(product: ShopifyProduct): string[] | undefined {\n const sizes = product.variants.edges.flatMap((e) =>\n e.node.selectedOptions\n .filter((o) => o.name.toLowerCase() === 'size')\n .map((o) => o.value),\n );\n const unique = [...new Set(sizes)];\n return unique.length > 0 ? unique : undefined;\n}\n\n/**\n * Extract variant ID → size label mapping for add-to-cart.\n * Shopify global IDs look like \"gid://shopify/ProductVariant/12345\" —\n * we extract just the numeric part for the Ajax API.\n */\nfunction extractVariants(product: ShopifyProduct): EntityItemData['variants'] {\n const variants = product.variants.edges.map((e) => {\n const sizeOption = e.node.selectedOptions.find(\n (o) => o.name.toLowerCase() === 'size',\n );\n const numericId = e.node.id.replace(\n /^gid:\\/\\/shopify\\/ProductVariant\\//,\n '',\n );\n return {\n label: sizeOption?.value || e.node.title,\n variantId: numericId,\n available: e.node.availableForSale,\n selectedOptions: e.node.selectedOptions.map((o) => ({\n name: o.name,\n value: o.value,\n })),\n price: formatPrice(e.node.price),\n compareAtPrice: e.node.compareAtPrice\n ? formatPrice(e.node.compareAtPrice)\n : undefined,\n };\n });\n return variants.length > 0 ? variants : undefined;\n}\n\nfunction getSecondaryImage(product: ShopifyProduct): string | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 1 ? images[1] : undefined;\n}\n\nfunction getAllImages(product: ShopifyProduct): string[] | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 0 ? images : undefined;\n}\n\nfunction getCompareAtPrice(product: ShopifyProduct): string | undefined {\n const compare = product.compareAtPriceRange?.minVariantPrice;\n if (!compare) {\n return undefined;\n }\n const min = product.priceRange.minVariantPrice;\n if (parseFloat(compare.amount) <= parseFloat(min.amount)) {\n return undefined;\n }\n return formatPrice(compare);\n}\n\n/**\n * The live entry for the variant the turn resolved, if the store still has it.\n * Both sides carry the bare numeric id — `extractVariants` strips the\n * `gid://shopify/ProductVariant/` prefix, and the turn never adds one.\n */\nfunction findLiveVariant(\n variants: EntityItemData['variants'],\n variantId: string | undefined,\n): NonNullable<EntityItemData['variants']>[number] | undefined {\n if (!variantId) {\n return undefined;\n }\n return variants?.find((variant) => variant.variantId === variantId);\n}\n\nexport function transformShopifyProduct(\n product: ShopifyProduct,\n variantId?: string,\n): EntityItemData {\n const variants = extractVariants(product);\n // The turn decided WHICH variant to show; the live fetch is here for what is\n // true about it right now. With no matched variant — or one the store has\n // since dropped — the product-level range is still the only answer we have.\n const matchedVariant = findLiveVariant(variants, variantId);\n\n return {\n url: product.onlineStoreUrl || '',\n title: product.title,\n type: 'product',\n description: product.description,\n imageUrl: product.featuredImage?.url,\n secondaryImageUrl: getSecondaryImage(product),\n vendor: product.vendor,\n price:\n matchedVariant?.price || formatPrice(product.priceRange.minVariantPrice),\n // Keyed on the variant, not on its value: an unmatched product falls back\n // to the product-level compare-at, but a matched variant that is simply\n // not on sale must not inherit one and print a false strikethrough.\n compareAtPrice: matchedVariant\n ? matchedVariant.compareAtPrice\n : getCompareAtPrice(product),\n available: matchedVariant?.available,\n sizes: extractSizes(product),\n variants,\n allImages: getAllImages(product),\n options: product.options?.length ? product.options : undefined,\n tags: product.tags.length ? product.tags : undefined,\n };\n}\n\nexport function transformShopifyCollection(\n collection: ShopifyCollection,\n): EntityItemData {\n return {\n url: collection.onlineStoreUrl || '',\n title: collection.title,\n type: 'collection',\n description: collection.description,\n imageUrl: collection.image?.url,\n };\n}\n\nexport function transformShopifyArticle(\n article: ShopifyArticle,\n): EntityItemData {\n return {\n url: article.onlineStoreUrl || '',\n title: article.title,\n type: 'article',\n description: article.excerpt || undefined,\n imageUrl: article.image?.url,\n publishDate: formatShopifyDate(article.publishedAt),\n author: article.authorV2?.name || undefined,\n tags: article.tags.length ? article.tags : undefined,\n };\n}\n\n/**\n * Transform any resolved Shopify entity to the standard EntityItemData shape.\n * Detects the entity type by checking for type-specific fields.\n */\nexport function transformShopifyEntityToItemData(\n entity: ShopifyProduct | ShopifyCollection | ShopifyArticle,\n entityType: string,\n variantId?: string,\n): EntityItemData {\n switch (entityType.toLowerCase()) {\n case 'product':\n return transformShopifyProduct(entity as ShopifyProduct, variantId);\n case 'collection':\n return transformShopifyCollection(entity as ShopifyCollection);\n case 'article':\n return transformShopifyArticle(entity as ShopifyArticle);\n default:\n return {\n url: '',\n title: (entity as { title?: string }).title || '',\n type: entityType,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["formatShopifyDate","isoDate","date","Date","isNaN","getTime","undefined","day","getDate","month","toLocaleString","year","getFullYear","formatPrice","money","amount","parseFloat","formatted","toFixed","currencyCode","extractSizes","product","sizes","variants","edges","flatMap","e","node","selectedOptions","filter","o","name","toLowerCase","map","value","unique","Set","length","extractVariants","sizeOption","find","numericId","id","replace","label","title","variantId","available","availableForSale","price","compareAtPrice","getSecondaryImage","images","url","getAllImages","getCompareAtPrice","_product$compareAtPri","compare","compareAtPriceRange","minVariantPrice","min","priceRange","findLiveVariant","variant","transformShopifyProduct","_product$featuredImag","_product$options","matchedVariant","pricesWholeProduct","onlineStoreUrl","type","description","imageUrl","featuredImage","secondaryImageUrl","vendor","priceMin","priceMax","maxVariantPrice","allImages","options","tags","transformShopifyCollection","collection","_collection$image","image","transformShopifyArticle","article","_article$image","_article$authorV","excerpt","publishDate","publishedAt","author","authorV2","transformShopifyEntityToItemData","entity","entityType"],"sources":["../../../../src/services/shopify/transformShopifyEntity.ts"],"sourcesContent":["import type { EntityItemData } from '../../component/types';\nimport type {\n ShopifyProduct,\n ShopifyCollection,\n ShopifyArticle,\n} from './types';\n\nfunction formatShopifyDate(isoDate: string): string | undefined {\n const date = new Date(isoDate);\n if (isNaN(date.getTime())) {\n return undefined;\n }\n const day = date.getDate();\n const month = date.toLocaleString('en-US', { month: 'short' });\n const year = date.getFullYear();\n return `${day} ${month} ${year}`;\n}\n\nfunction formatPrice(\n money: ShopifyProduct['priceRange']['minVariantPrice'],\n): string {\n const amount = parseFloat(money.amount);\n const formatted = amount.toFixed(2);\n return `${formatted} ${money.currencyCode}`;\n}\n\nfunction extractSizes(product: ShopifyProduct): string[] | undefined {\n const sizes = product.variants.edges.flatMap((e) =>\n e.node.selectedOptions\n .filter((o) => o.name.toLowerCase() === 'size')\n .map((o) => o.value),\n );\n const unique = [...new Set(sizes)];\n return unique.length > 0 ? unique : undefined;\n}\n\n/**\n * Extract variant ID → size label mapping for add-to-cart.\n * Shopify global IDs look like \"gid://shopify/ProductVariant/12345\" —\n * we extract just the numeric part for the Ajax API.\n */\nfunction extractVariants(product: ShopifyProduct): EntityItemData['variants'] {\n const variants = product.variants.edges.map((e) => {\n const sizeOption = e.node.selectedOptions.find(\n (o) => o.name.toLowerCase() === 'size',\n );\n const numericId = e.node.id.replace(\n /^gid:\\/\\/shopify\\/ProductVariant\\//,\n '',\n );\n return {\n label: sizeOption?.value || e.node.title,\n variantId: numericId,\n available: e.node.availableForSale,\n selectedOptions: e.node.selectedOptions.map((o) => ({\n name: o.name,\n value: o.value,\n })),\n price: formatPrice(e.node.price),\n compareAtPrice: e.node.compareAtPrice\n ? formatPrice(e.node.compareAtPrice)\n : undefined,\n };\n });\n return variants.length > 0 ? variants : undefined;\n}\n\nfunction getSecondaryImage(product: ShopifyProduct): string | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 1 ? images[1] : undefined;\n}\n\nfunction getAllImages(product: ShopifyProduct): string[] | undefined {\n const images = product.images.edges.map((e) => e.node.url);\n return images.length > 0 ? images : undefined;\n}\n\nfunction getCompareAtPrice(product: ShopifyProduct): string | undefined {\n const compare = product.compareAtPriceRange?.minVariantPrice;\n if (!compare) {\n return undefined;\n }\n const min = product.priceRange.minVariantPrice;\n if (parseFloat(compare.amount) <= parseFloat(min.amount)) {\n return undefined;\n }\n return formatPrice(compare);\n}\n\n/**\n * The live entry for the variant the turn resolved, if the store still has it.\n * Both sides carry the bare numeric id — `extractVariants` strips the\n * `gid://shopify/ProductVariant/` prefix, and the turn never adds one.\n */\nfunction findLiveVariant(\n variants: EntityItemData['variants'],\n variantId: string | undefined,\n): NonNullable<EntityItemData['variants']>[number] | undefined {\n if (!variantId) {\n return undefined;\n }\n return variants?.find((variant) => variant.variantId === variantId);\n}\n\nexport function transformShopifyProduct(\n product: ShopifyProduct,\n variantId?: string,\n): EntityItemData {\n const variants = extractVariants(product);\n // The turn decided WHICH variant to show; the live fetch is here for what is\n // true about it right now.\n const matchedVariant = findLiveVariant(variants, variantId);\n // Whether this fetch is answering about the whole product or about one\n // child of it. A row that named a child but whose child the store no longer\n // returns — dropped since the turn, or past the `variants(first: 100)` page\n // — is NOT a whole-product row: answering it with the product's floor would\n // put a different variant's price under this variant's name, which is the\n // bug the row's own `variantId` exists to prevent. So the live price is\n // simply withheld and the turn's, which was right about this child, stands.\n const pricesWholeProduct = !variantId;\n\n return {\n url: product.onlineStoreUrl || '',\n title: product.title,\n type: 'product',\n description: product.description,\n imageUrl: product.featuredImage?.url,\n secondaryImageUrl: getSecondaryImage(product),\n vendor: product.vendor,\n price:\n matchedVariant?.price ??\n (pricesWholeProduct\n ? formatPrice(product.priceRange.minVariantPrice)\n : undefined),\n // The span the whole product covers, in the store's live currency. A card\n // reads it to decide whether \"from\" is honest; it is the product's either\n // way, so it is reported whether or not a child matched.\n priceMin: formatPrice(product.priceRange.minVariantPrice),\n priceMax: formatPrice(product.priceRange.maxVariantPrice),\n // Keyed on the variant, not on its value: a whole-product row falls back\n // to the product-level compare-at, but a matched variant that is simply\n // not on sale must not inherit one and print a false strikethrough.\n compareAtPrice: matchedVariant\n ? matchedVariant.compareAtPrice\n : pricesWholeProduct\n ? getCompareAtPrice(product)\n : undefined,\n available: matchedVariant?.available,\n sizes: extractSizes(product),\n variants,\n allImages: getAllImages(product),\n options: product.options?.length ? product.options : undefined,\n tags: product.tags.length ? product.tags : undefined,\n };\n}\n\nexport function transformShopifyCollection(\n collection: ShopifyCollection,\n): EntityItemData {\n return {\n url: collection.onlineStoreUrl || '',\n title: collection.title,\n type: 'collection',\n description: collection.description,\n imageUrl: collection.image?.url,\n };\n}\n\nexport function transformShopifyArticle(\n article: ShopifyArticle,\n): EntityItemData {\n return {\n url: article.onlineStoreUrl || '',\n title: article.title,\n type: 'article',\n description: article.excerpt || undefined,\n imageUrl: article.image?.url,\n publishDate: formatShopifyDate(article.publishedAt),\n author: article.authorV2?.name || undefined,\n tags: article.tags.length ? article.tags : undefined,\n };\n}\n\n/**\n * Transform any resolved Shopify entity to the standard EntityItemData shape.\n * Detects the entity type by checking for type-specific fields.\n */\nexport function transformShopifyEntityToItemData(\n entity: ShopifyProduct | ShopifyCollection | ShopifyArticle,\n entityType: string,\n variantId?: string,\n): EntityItemData {\n switch (entityType.toLowerCase()) {\n case 'product':\n return transformShopifyProduct(entity as ShopifyProduct, variantId);\n case 'collection':\n return transformShopifyCollection(entity as ShopifyCollection);\n case 'article':\n return transformShopifyArticle(entity as ShopifyArticle);\n default:\n return {\n url: '',\n title: (entity as { title?: string }).title || '',\n type: entityType,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -60,20 +60,29 @@ export interface ProductPriceFields {
|
|
|
60
60
|
priceMin?: string;
|
|
61
61
|
/** Highest price across the whole product's variants. */
|
|
62
62
|
priceMax?: string;
|
|
63
|
+
/** Id of the variant this row was resolved down to, when there was one. */
|
|
64
|
+
variantId?: string;
|
|
65
|
+
/** The pairs naming that variant — what the card prints above the price. */
|
|
66
|
+
matchedOptions?: MatchedOption[];
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
69
|
* What a product card should print as its price.
|
|
66
70
|
*
|
|
67
|
-
*
|
|
68
|
-
* the
|
|
69
|
-
*
|
|
70
|
-
* the
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* A card that names a variant prices THAT variant. `price` belongs to the
|
|
72
|
+
* single child the search resolved to — the same child the card's image, its
|
|
73
|
+
* `?variant=` link and the pairs printed above the price all describe — so it
|
|
74
|
+
* is the only number that can follow them without contradicting them. "from"
|
|
75
|
+
* is wrong there twice over: it claims a range where the card named one thing,
|
|
76
|
+
* and the floor it points at is a different child's price wearing this one's
|
|
77
|
+
* name. That is what shipped: five colourways of one shoe, each naming its own
|
|
78
|
+
* colour and each printing the cheapest colour's price.
|
|
74
79
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
80
|
+
* `priceMin`/`priceMax` describe the whole product, and they are the answer
|
|
81
|
+
* only when the row IS the whole product. Then the span's floor prints
|
|
82
|
+
* prefixed, because no single number can stand for a product that has several.
|
|
83
|
+
*
|
|
84
|
+
* With no span — the ends equal, or only one of them known — the row's own
|
|
85
|
+
* price prints as it always did.
|
|
77
86
|
*/
|
|
78
87
|
export declare function formatProductPriceLabel(fields: ProductPriceFields): string | undefined;
|
|
79
88
|
//# sourceMappingURL=matchedVariant.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matchedVariant.d.ts","sourceRoot":"","sources":["../../../src/entity/matchedVariant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,SAAS,CAiB1E;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAcrE;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,GAAG,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,SAAS,CAW7E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAQpB;AAED,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"matchedVariant.d.ts","sourceRoot":"","sources":["../../../src/entity/matchedVariant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,SAAS,CAiB1E;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAcrE;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,GAAG,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,SAAS,CAW7E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAQpB;AAED,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAeD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,GACzB,MAAM,GAAG,SAAS,CASpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useResolveShopifyEntityData.d.ts","sourceRoot":"","sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAgFzD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB;;;;EAYA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,QAAQ,EAAE,cAAc,GACvB,cAAc,
|
|
1
|
+
{"version":3,"file":"useResolveShopifyEntityData.d.ts","sourceRoot":"","sources":["../../../src/hooks/useResolveShopifyEntityData.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAgFzD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB;;;;EAYA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,QAAQ,EAAE,cAAc,GACvB,cAAc,CAsBhB;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,SAAS;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,EAED,QAAQ,EAAE,OAAO,EAAE,EACnB,aAAa,EAAE,uBAAuB,GACrC;IAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAwInD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformShopifyEntity.d.ts","sourceRoot":"","sources":["../../../../src/services/shopify/transformShopifyEntity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,cAAc,EACf,MAAM,SAAS,CAAC;AAmGjB,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,cAAc,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,
|
|
1
|
+
{"version":3,"file":"transformShopifyEntity.d.ts","sourceRoot":"","sources":["../../../../src/services/shopify/transformShopifyEntity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,cAAc,EACf,MAAM,SAAS,CAAC;AAmGjB,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,cAAc,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CA+ChB;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,iBAAiB,GAC5B,cAAc,CAQhB;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,cAAc,GACtB,cAAc,CAWhB;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,cAAc,GAAG,iBAAiB,GAAG,cAAc,EAC3D,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAehB"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/web5-core",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "1.63.
|
|
4
|
+
"version": "1.63.15",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tsachis",
|
|
7
7
|
"email": "tsachis@wix.com"
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"wallaby": {
|
|
101
101
|
"autoDetect": true
|
|
102
102
|
},
|
|
103
|
-
"falconPackageHash": "
|
|
103
|
+
"falconPackageHash": "4d59bfb7cfd9bcc6126c8ff5d1bb6e416ab8fe9b2b13a07266ab763e"
|
|
104
104
|
}
|