@zenky/storefront-vue 0.10.7 → 0.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +4 -18
- package/dist/cart/index.d.ts +167 -160
- package/dist/cart/index.js +7 -7
- package/dist/catalog/index.d.ts +18 -21
- package/dist/catalog/index.js +2 -2
- package/dist/{chunk-RO25KNM5.js → chunk-2Y6QTHXI.js} +2 -2
- package/dist/chunk-2Y6QTHXI.js.map +1 -0
- package/dist/{chunk-5YD4AIY2.js → chunk-46FNHMWC.js} +4 -4
- package/dist/chunk-46FNHMWC.js.map +1 -0
- package/dist/{chunk-Z3IW4ODZ.js → chunk-6QCFHSLX.js} +2 -2
- package/dist/chunk-6QCFHSLX.js.map +1 -0
- package/dist/{chunk-2SMSLZ6G.js → chunk-AVES4WJZ.js} +15 -11
- package/dist/chunk-AVES4WJZ.js.map +1 -0
- package/dist/{chunk-4IFPVEO7.js → chunk-BUNI6MGH.js} +3 -3
- package/dist/chunk-BUNI6MGH.js.map +1 -0
- package/dist/{chunk-PTMSPB34.js → chunk-FXKG5TFQ.js} +3 -3
- package/dist/chunk-FXKG5TFQ.js.map +1 -0
- package/dist/{chunk-MOG6UMFM.js → chunk-GILJFP55.js} +2 -2
- package/dist/chunk-GILJFP55.js.map +1 -0
- package/dist/{chunk-2CI4RW7V.js → chunk-GLQXTMVW.js} +2 -2
- package/dist/{chunk-2CI4RW7V.js.map → chunk-GLQXTMVW.js.map} +1 -1
- package/dist/{chunk-MZ66ARDL.js → chunk-GXDTRTUT.js} +2 -2
- package/dist/chunk-GXDTRTUT.js.map +1 -0
- package/dist/{chunk-E3G7NGCH.js → chunk-KFCO46RB.js} +2 -2
- package/dist/{chunk-E3G7NGCH.js.map → chunk-KFCO46RB.js.map} +1 -1
- package/dist/{chunk-VFMVQR6Y.js → chunk-QZ3S3NN3.js} +5 -3
- package/dist/chunk-QZ3S3NN3.js.map +1 -0
- package/dist/{chunk-B3UU7HF5.js → chunk-RF4ML3Q3.js} +47 -7
- package/dist/chunk-RF4ML3Q3.js.map +1 -0
- package/dist/{chunk-NG7RUIWW.js → chunk-RK5CGOM7.js} +2 -2
- package/dist/chunk-RK5CGOM7.js.map +1 -0
- package/dist/{chunk-Z247QRDJ.js → chunk-XULHV6SS.js} +3 -3
- package/dist/chunk-XULHV6SS.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +14 -14
- package/dist/money/index.d.ts +8 -8
- package/dist/money/index.js +1 -1
- package/dist/order/index.d.ts +3 -3
- package/dist/order/index.js.map +1 -1
- package/dist/pagination/index.d.ts +29 -37
- package/dist/pagination/index.js.map +1 -1
- package/dist/ports/index.d.ts +23 -22
- package/dist/ports/index.js +1 -1
- package/dist/products/index.d.ts +20 -9
- package/dist/products/index.js +3 -3
- package/dist/promotions/index.d.ts +5 -5
- package/dist/promotions/index.js +3 -3
- package/dist/seo/index.d.ts +14 -12
- package/dist/seo/index.js +5 -5
- package/dist/seo/index.js.map +1 -1
- package/dist/snippets/index.js +1 -1
- package/dist/stores/index.d.ts +4 -5
- package/dist/stores/index.js +4 -4
- package/dist/theme/index.d.ts +3 -3
- package/dist/theme/index.js +1 -1
- package/dist/utils/index.d.ts +16 -16
- package/dist/utils/index.js +1 -1
- package/docs/architecture.md +84 -0
- package/docs/consumer-setup.md +13 -54
- package/docs/ports.md +12 -13
- package/package.json +1 -1
- package/dist/chunk-2SMSLZ6G.js.map +0 -1
- package/dist/chunk-4IFPVEO7.js.map +0 -1
- package/dist/chunk-5YD4AIY2.js.map +0 -1
- package/dist/chunk-B3UU7HF5.js.map +0 -1
- package/dist/chunk-MOG6UMFM.js.map +0 -1
- package/dist/chunk-MZ66ARDL.js.map +0 -1
- package/dist/chunk-NG7RUIWW.js.map +0 -1
- package/dist/chunk-PTMSPB34.js.map +0 -1
- package/dist/chunk-RO25KNM5.js.map +0 -1
- package/dist/chunk-VFMVQR6Y.js.map +0 -1
- package/dist/chunk-Z247QRDJ.js.map +0 -1
- package/dist/chunk-Z3IW4ODZ.js.map +0 -1
|
@@ -2,12 +2,12 @@ interface PromotionsChecker {
|
|
|
2
2
|
dispatchCheck: () => Promise<void>;
|
|
3
3
|
}
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Checks promotions. Calls dispatchPromotionsChecker after the cart changes.
|
|
6
|
+
* The result arrives over WebSocket (handled in useOrderChannel).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Resolve this synchronously in setup, and call only `dispatchCheck` after an
|
|
9
|
+
* await — like any core composable, see "Call composables and stores
|
|
10
|
+
* synchronously in setup" in the README.
|
|
11
11
|
*
|
|
12
12
|
* ```ts
|
|
13
13
|
* const { dispatchCheck } = usePromotionsChecker()
|
package/dist/promotions/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { usePromotionsChecker } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import '../chunk-
|
|
1
|
+
export { usePromotionsChecker } from '../chunk-46FNHMWC.js';
|
|
2
|
+
import '../chunk-XULHV6SS.js';
|
|
3
|
+
import '../chunk-QZ3S3NN3.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/dist/seo/index.d.ts
CHANGED
|
@@ -29,44 +29,46 @@ declare function stripHtml(html: string): string;
|
|
|
29
29
|
declare function prepareOgDescription(text: string | null | undefined, maxLength?: number): string | null;
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* Canonical public origin of the site, from `store.domain`. The fallback (request host)
|
|
33
|
+
* is supplied by the consumer — the core doesn't know about `useRequestURL`.
|
|
34
34
|
*/
|
|
35
35
|
declare function resolveSiteOrigin(storeDomain: string | null | undefined, fallbackOrigin: string): string;
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* JSON-LD schema.org/Product. Google
|
|
39
|
-
*
|
|
38
|
+
* JSON-LD schema.org/Product. Google uses it for Rich Results (price, availability,
|
|
39
|
+
* rating in search listings).
|
|
40
|
+
* Returns an array of 0 or 1 objects — the consumer wraps it in a `<script ld+json>` tag.
|
|
40
41
|
* Spec: https://schema.org/Product
|
|
41
42
|
*/
|
|
42
43
|
declare function buildProductSchema(product: Product, currency: string): Record<string, unknown>[];
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
* JSON-LD
|
|
46
|
-
*
|
|
46
|
+
* JSON-LD for a category page: BreadcrumbList + ItemList (first 10 products).
|
|
47
|
+
* Returns an array of 0-2 JSON-LD objects.
|
|
47
48
|
* Spec: https://schema.org/ItemList · https://schema.org/BreadcrumbList
|
|
48
49
|
*/
|
|
49
50
|
declare function buildCategorySchemas(category: NestedCategory, products: Product[], breadcrumbs: BreadcrumbItem[], origin: string): Record<string, unknown>[];
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
|
-
* JSON-LD schema.org/Article
|
|
53
|
+
* JSON-LD schema.org/Article for the offer page's editorial copy.
|
|
53
54
|
* Spec: https://schema.org/Article
|
|
54
55
|
*/
|
|
55
56
|
declare function buildOfferArticleSchema(article: OfferArticle, storeName: string, origin: string): Record<string, unknown>[];
|
|
56
57
|
/**
|
|
57
|
-
* JSON-LD schema.org/BlogPosting
|
|
58
|
+
* JSON-LD schema.org/BlogPosting for a blog article — enables Google Rich Results.
|
|
58
59
|
* Spec: https://schema.org/BlogPosting
|
|
59
60
|
*/
|
|
60
61
|
declare function buildBlogPostSchema(article: Article, storeName: string, origin: string): Record<string, unknown>[];
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
|
-
* JSON-LD schema.org/Organization (
|
|
64
|
-
*
|
|
64
|
+
* JSON-LD schema.org/Organization (or LocalBusiness if there's a physical location)
|
|
65
|
+
* for the homepage. Feeds Google's Knowledge Panel. All data comes from the
|
|
66
|
+
* already-loaded profile.
|
|
65
67
|
* Spec: https://schema.org/Organization · https://schema.org/LocalBusiness
|
|
66
68
|
*/
|
|
67
69
|
declare function buildOrganizationSchema(store: Store, origin: string): Record<string, unknown>[];
|
|
68
70
|
|
|
69
|
-
/**
|
|
71
|
+
/** Already-resolved OG/Twitter tag values (with the consumer's defaults applied). */
|
|
70
72
|
interface OgTagsInput {
|
|
71
73
|
title: string | null;
|
|
72
74
|
description?: string | null;
|
|
@@ -79,7 +81,7 @@ declare function buildOgTags(input: OgTagsInput): Record<string, string>[];
|
|
|
79
81
|
|
|
80
82
|
/** SDK `Seo.meta` tags mapped to framework-neutral head meta records. */
|
|
81
83
|
declare function mapSeoMetaTags(tags: SeoMetaTag[]): Record<string, string>[];
|
|
82
|
-
/**
|
|
84
|
+
/** Meta tags from a `Seo` entity (description + `Seo.meta` mapping). The consumer resolves the title. */
|
|
83
85
|
declare function buildEntitySeoMeta(seo: Seo | null | undefined): Record<string, string>[];
|
|
84
86
|
|
|
85
87
|
export { type BreadcrumbItem, type OgTagsInput, buildBlogPostSchema, buildCategorySchemas, buildEntitySeoMeta, buildOfferArticleSchema, buildOgTags, buildOrganizationSchema, buildProductSchema, mapSeoMetaTags, prepareOgDescription, resolveMediaUrl, resolveSiteOrigin, resolveStoreLogoUrl, stripHtml };
|
package/dist/seo/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { getSocialLinks } from '../chunk-
|
|
2
|
-
import { minorToMajorString } from '../chunk-
|
|
3
|
-
import { resolveVariantPrice } from '../chunk-
|
|
1
|
+
import { getSocialLinks } from '../chunk-GXDTRTUT.js';
|
|
2
|
+
import { minorToMajorString } from '../chunk-2Y6QTHXI.js';
|
|
3
|
+
import { resolveVariantPrice } from '../chunk-RF4ML3Q3.js';
|
|
4
4
|
import '../chunk-XUGXLJZB.js';
|
|
5
5
|
import '../chunk-5QCUH3FF.js';
|
|
6
|
-
import '../chunk-
|
|
7
|
-
import '../chunk-
|
|
6
|
+
import '../chunk-6QCFHSLX.js';
|
|
7
|
+
import '../chunk-QZ3S3NN3.js';
|
|
8
8
|
import { ContactType } from '@zenky/storefront-api';
|
|
9
9
|
|
|
10
10
|
// src/seo/utils.ts
|
package/dist/seo/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/seo/utils.ts","../../src/seo/siteOrigin.ts","../../src/seo/productSchema.ts","../../src/seo/categorySchema.ts","../../src/seo/articleSchema.ts","../../src/seo/organizationSchema.ts","../../src/seo/ogTags.ts","../../src/seo/entitySeo.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Media, Store } from '@zenky/storefront-api'\n\n/**\n * Resolves the best available URL from a Media object.\n */\nexport function resolveMediaUrl(media: Media): string | null {\n return (\n media.hd?.url ||\n media.xlarge?.url ||\n media.large?.url ||\n media.medium?.url ||\n media.placeholder?.url ||\n null\n )\n}\n\n/**\n * Resolves store logo URL, preferring square logo over rectangular.\n * Tries all available size variants so CDN assets with only hd/xlarge are found too.\n */\nexport function resolveStoreLogoUrl(store: Store | null | undefined): string | null {\n if (!store) return null\n if (store.square_logo) {\n const url = resolveMediaUrl(store.square_logo)\n if (url) return url\n }\n if (store.logo) {\n return resolveMediaUrl(store.logo)\n }\n return null\n}\n\n/**\n * Strips HTML tags from a string.\n */\nexport function stripHtml(html: string): string {\n return html.replace(/<[^>]*>/g, '').trim()\n}\n\n/**\n * Prepares a string for use as og:description or meta description:\n * strips HTML tags and truncates to maxLength (default 160).\n */\nexport function prepareOgDescription(\n text: string | null | undefined,\n maxLength = 160\n): string | null {\n if (!text) return null\n const plain = stripHtml(text)\n if (!plain) return null\n return plain.length > maxLength ? `${plain.slice(0, maxLength - 3)}...` : plain\n}\n","/**\n * Каноничный публичный origin сайта из `store.domain`. Fallback (хост запроса)\n * приносит потребитель — ядро не знает про useRequestURL.\n */\nexport function resolveSiteOrigin(\n storeDomain: string | null | undefined,\n fallbackOrigin: string\n): string {\n const domain = storeDomain?.trim()\n if (domain) {\n const host = domain.replace(/^https?:\\/\\//, '').replace(/\\/+$/, '')\n return `https://${host}`\n }\n return fallbackOrigin\n}\n","import type { Product, ProductVariant } from '@zenky/storefront-api'\n\nimport { minorToMajorString } from '../money'\nimport { resolveVariantPrice } from '../products'\nimport { resolveMediaUrl } from './utils'\n\nfunction isProductInStock(variants: ProductVariant[]): boolean {\n if (!variants.length) return false\n return variants.some((variant) => {\n const remainders = variant.remainders\n if (!remainders?.length) return true\n return remainders.some((remainder) => remainder.quantity > 0)\n })\n}\n\n/**\n * JSON-LD schema.org/Product. Google → Rich Results (цена/наличие/рейтинг в выдаче).\n * Возвращает массив (0 или 1 объект) — потребитель оборачивает в `<script ld+json>`.\n * Spec: https://schema.org/Product\n */\nexport function buildProductSchema(product: Product, currency: string): Record<string, unknown>[] {\n const variants = product.variants ?? []\n\n const firstVariant = variants[0]\n let offerBlock: Record<string, unknown> | undefined\n\n if (firstVariant) {\n const resolved = resolveVariantPrice(firstVariant)\n\n offerBlock = {\n '@type': 'Offer',\n price: minorToMajorString(resolved.price, currency),\n priceCurrency: currency,\n availability: isProductInStock(variants)\n ? 'https://schema.org/InStock'\n : 'https://schema.org/OutOfStock',\n ...(firstVariant.sku ? { sku: firstVariant.sku } : {}),\n }\n }\n\n const images = (product.images ?? []).map(resolveMediaUrl).filter((url): url is string => !!url)\n\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: product.name,\n }\n\n if (product.description) schema.description = product.description\n if (images.length) schema.image = images\n if (offerBlock) schema.offers = offerBlock\n\n return [schema]\n}\n","import type { NestedCategory, Product } from '@zenky/storefront-api'\n\nimport { resolveMediaUrl } from './utils'\nimport type { BreadcrumbItem } from './types'\n\n/**\n * JSON-LD для страницы категории: BreadcrumbList (хлебные крошки) + ItemList\n * (первые 10 товаров). Возвращает массив JSON-LD объектов (0–2).\n * Spec: https://schema.org/ItemList · https://schema.org/BreadcrumbList\n */\nexport function buildCategorySchemas(\n category: NestedCategory,\n products: Product[],\n breadcrumbs: BreadcrumbItem[],\n origin: string\n): Record<string, unknown>[] {\n const schemas: Record<string, unknown>[] = []\n\n if (breadcrumbs.length) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: breadcrumbs.map((crumb, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: crumb.label,\n ...(crumb.to ? { item: `${origin}${crumb.to}` } : {}),\n })),\n })\n }\n\n const items = products.slice(0, 10)\n if (items.length) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'ItemList',\n name: category.name,\n numberOfItems: items.length,\n itemListElement: items.map((product, index) => {\n const url = `${origin}/products/${product.short_id}/${product.slug || product.short_id}`\n const firstImage = product.images?.[0]\n const image = firstImage ? resolveMediaUrl(firstImage) : null\n\n const item: Record<string, unknown> = {\n '@type': 'ListItem',\n position: index + 1,\n url,\n name: product.name,\n }\n\n if (image) item.image = image\n\n return item\n }),\n })\n }\n\n return schemas\n}\n","import type { Article, Media, OfferArticle } from '@zenky/storefront-api'\n\nimport { prepareOgDescription, resolveMediaUrl } from './utils'\n\nfunction articleSchema(\n type: 'Article' | 'BlogPosting',\n opts: {\n title: string | null | undefined\n intro: string | null | undefined\n cover: Media | null | undefined\n storeName: string\n origin: string\n }\n): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': type,\n headline: opts.title ?? '',\n author: {\n '@type': 'Organization',\n name: opts.storeName,\n url: opts.origin,\n },\n }\n\n const description = prepareOgDescription(opts.intro)\n if (description) schema.description = description\n\n const image = opts.cover ? resolveMediaUrl(opts.cover) : null\n if (image) schema.image = image\n\n return schema\n}\n\n/**\n * JSON-LD schema.org/Article для страницы акции (редакционный контент).\n * Spec: https://schema.org/Article\n */\nexport function buildOfferArticleSchema(\n article: OfferArticle,\n storeName: string,\n origin: string\n): Record<string, unknown>[] {\n if (!article.title) return []\n\n return [\n articleSchema('Article', {\n title: article.title,\n intro: article.intro,\n cover: article.cover,\n storeName,\n origin,\n }),\n ]\n}\n\n/**\n * JSON-LD schema.org/BlogPosting для статьи блога (Google Rich Results).\n * Spec: https://schema.org/BlogPosting\n */\nexport function buildBlogPostSchema(\n article: Article,\n storeName: string,\n origin: string\n): Record<string, unknown>[] {\n const schema = articleSchema('BlogPosting', {\n title: article.title,\n intro: article.intro,\n cover: article.cover,\n storeName,\n origin,\n })\n\n if (article.updated_at) {\n schema.dateModified = article.updated_at\n // Нет отдельного datePublished — используем updated_at.\n schema.datePublished = article.updated_at\n }\n\n return [schema]\n}\n","import { ContactType } from '@zenky/storefront-api'\n\nimport type { Schedule, Store } from '@zenky/storefront-api'\n\nimport { getSocialLinks } from '../utils'\n\n/** Ключ дня в Schedule.days → schema.org DayOfWeek. */\nconst SCHEMA_DAY: Record<string, string> = {\n monday: 'Monday',\n tuesday: 'Tuesday',\n wednesday: 'Wednesday',\n thursday: 'Thursday',\n friday: 'Friday',\n saturday: 'Saturday',\n sunday: 'Sunday',\n}\n\n/**\n * Schedule (по дням `hours` — «10:00–22:00») → массив schema.org/OpeningHoursSpecification.\n * Дни без распознаваемого интервала (выходной / круглосуточно / пусто) пропускаем.\n */\nfunction buildOpeningHours(schedule: Schedule | null | undefined): Record<string, unknown>[] {\n if (!schedule) return []\n\n const result: Record<string, unknown>[] = []\n for (const key of Object.keys(SCHEMA_DAY)) {\n const day = schedule.days[key as keyof typeof schedule.days]\n const match = day?.hours?.match(/(\\d{1,2}:\\d{2}).*?(\\d{1,2}:\\d{2})/)\n if (!match) continue\n\n result.push({\n '@type': 'OpeningHoursSpecification',\n dayOfWeek: SCHEMA_DAY[key],\n opens: match[1],\n closes: match[2],\n })\n }\n\n return result\n}\n\n/**\n * JSON-LD schema.org/Organization (или LocalBusiness при наличии физической точки)\n * для главной. Google → Knowledge Panel. Все данные из уже загруженного профиля.\n * Spec: https://schema.org/Organization · https://schema.org/LocalBusiness\n */\nexport function buildOrganizationSchema(store: Store, origin: string): Record<string, unknown>[] {\n const logoUrl =\n store.square_logo?.medium?.url ||\n store.square_logo?.large?.url ||\n store.logo?.medium?.url ||\n store.logo?.large?.url ||\n null\n\n const phones = (store.phones ?? []).map((p) => p.number).filter((n): n is string => !!n)\n const email = store.links?.find((c) => c.type === ContactType.Email)?.value ?? null\n const socialLinks = getSocialLinks(store.links ?? []).map((l) => l.url)\n\n const city = (store.cities ?? []).find((c) => c.is_default) ?? store.cities?.[0] ?? null\n const stock = city?.stocks?.find((st) => st.address) ?? city?.stocks?.[0] ?? null\n\n const streetAddress = stock?.address ?? null\n const geo = stock?.coordinates ?? city?.coordinates ?? null\n const openingHours = buildOpeningHours(stock?.schedule ?? city?.schedule)\n\n const isLocalBusiness = Boolean(streetAddress || geo || openingHours.length)\n\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': isLocalBusiness ? 'LocalBusiness' : 'Organization',\n name: store.name,\n url: origin,\n }\n\n if (logoUrl) {\n schema.logo = logoUrl\n schema.image = logoUrl\n }\n\n if (phones.length) schema.telephone = phones.length === 1 ? phones[0] : phones\n if (email) schema.email = email\n if (socialLinks.length) schema.sameAs = socialLinks\n\n if (streetAddress || city?.name) {\n schema.address = {\n '@type': 'PostalAddress',\n ...(streetAddress ? { streetAddress } : {}),\n ...(city?.name ? { addressLocality: city.name } : {}),\n addressCountry: 'RU',\n }\n }\n\n if (geo) {\n schema.geo = {\n '@type': 'GeoCoordinates',\n latitude: geo.latitude,\n longitude: geo.longitude,\n }\n }\n\n if (openingHours.length) schema.openingHoursSpecification = openingHours\n\n return [schema]\n}\n","/** Уже разрешённые (с учётом дефолтов потребителя) значения OG/Twitter-тегов. */\nexport interface OgTagsInput {\n title: string | null\n description?: string | null\n image?: string | null\n type?: string\n url?: string | null\n}\n\n/** Framework-neutral OG + Twitter meta records. The consumer controls head priority. */\nexport function buildOgTags(input: OgTagsInput): Record<string, string>[] {\n const tags: Record<string, string>[] = []\n\n if (input.title) {\n tags.push({ property: 'og:title', content: input.title })\n tags.push({ name: 'twitter:title', content: input.title })\n }\n\n if (input.description) {\n tags.push({ property: 'og:description', content: input.description })\n tags.push({ name: 'twitter:description', content: input.description })\n }\n\n if (input.image) {\n tags.push({ property: 'og:image', content: input.image })\n tags.push({ name: 'twitter:image', content: input.image })\n }\n\n tags.push({ property: 'og:type', content: input.type ?? 'website' })\n\n if (input.url) {\n tags.push({ property: 'og:url', content: input.url })\n }\n\n return tags\n}\n","import type { Seo, SeoMetaTag } from '@zenky/storefront-api'\n\n/** SDK `Seo.meta` tags mapped to framework-neutral head meta records. */\nexport function mapSeoMetaTags(tags: SeoMetaTag[]): Record<string, string>[] {\n return tags.map((tag) => {\n const attrs: Record<string, string> = {\n [tag.name.attribute]: tag.name.value,\n [tag.value.attribute]: tag.value.value,\n }\n\n for (const additional of tag.additional) {\n attrs[additional.attribute] = additional.value\n }\n\n return attrs\n })\n}\n\n/** meta-теги из `Seo`-сущности (description + маппинг `Seo.meta`). Title резолвит потребитель. */\nexport function buildEntitySeoMeta(seo: Seo | null | undefined): Record<string, string>[] {\n const tags: Record<string, string>[] = []\n\n if (seo?.description) {\n tags.push({ name: 'description', content: seo.description })\n }\n\n if (seo?.meta?.length) {\n tags.push(...mapSeoMetaTags(seo.meta))\n }\n\n return tags\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/seo/utils.ts","../../src/seo/siteOrigin.ts","../../src/seo/productSchema.ts","../../src/seo/categorySchema.ts","../../src/seo/articleSchema.ts","../../src/seo/organizationSchema.ts","../../src/seo/ogTags.ts","../../src/seo/entitySeo.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Media, Store } from '@zenky/storefront-api'\n\n/**\n * Resolves the best available URL from a Media object.\n */\nexport function resolveMediaUrl(media: Media): string | null {\n return (\n media.hd?.url ||\n media.xlarge?.url ||\n media.large?.url ||\n media.medium?.url ||\n media.placeholder?.url ||\n null\n )\n}\n\n/**\n * Resolves store logo URL, preferring square logo over rectangular.\n * Tries all available size variants so CDN assets with only hd/xlarge are found too.\n */\nexport function resolveStoreLogoUrl(store: Store | null | undefined): string | null {\n if (!store) return null\n if (store.square_logo) {\n const url = resolveMediaUrl(store.square_logo)\n if (url) return url\n }\n if (store.logo) {\n return resolveMediaUrl(store.logo)\n }\n return null\n}\n\n/**\n * Strips HTML tags from a string.\n */\nexport function stripHtml(html: string): string {\n return html.replace(/<[^>]*>/g, '').trim()\n}\n\n/**\n * Prepares a string for use as og:description or meta description:\n * strips HTML tags and truncates to maxLength (default 160).\n */\nexport function prepareOgDescription(\n text: string | null | undefined,\n maxLength = 160\n): string | null {\n if (!text) return null\n const plain = stripHtml(text)\n if (!plain) return null\n return plain.length > maxLength ? `${plain.slice(0, maxLength - 3)}...` : plain\n}\n","/**\n * Canonical public origin of the site, from `store.domain`. The fallback (request host)\n * is supplied by the consumer — the core doesn't know about `useRequestURL`.\n */\nexport function resolveSiteOrigin(\n storeDomain: string | null | undefined,\n fallbackOrigin: string\n): string {\n const domain = storeDomain?.trim()\n if (domain) {\n const host = domain.replace(/^https?:\\/\\//, '').replace(/\\/+$/, '')\n return `https://${host}`\n }\n return fallbackOrigin\n}\n","import type { Product, ProductVariant } from '@zenky/storefront-api'\n\nimport { minorToMajorString } from '../money'\nimport { resolveVariantPrice } from '../products'\nimport { resolveMediaUrl } from './utils'\n\nfunction isProductInStock(variants: ProductVariant[]): boolean {\n if (!variants.length) return false\n return variants.some((variant) => {\n const remainders = variant.remainders\n if (!remainders?.length) return true\n return remainders.some((remainder) => remainder.quantity > 0)\n })\n}\n\n/**\n * JSON-LD schema.org/Product. Google uses it for Rich Results (price, availability,\n * rating in search listings).\n * Returns an array of 0 or 1 objects — the consumer wraps it in a `<script ld+json>` tag.\n * Spec: https://schema.org/Product\n */\nexport function buildProductSchema(product: Product, currency: string): Record<string, unknown>[] {\n const variants = product.variants ?? []\n\n const firstVariant = variants[0]\n let offerBlock: Record<string, unknown> | undefined\n\n if (firstVariant) {\n const resolved = resolveVariantPrice(firstVariant)\n\n offerBlock = {\n '@type': 'Offer',\n price: minorToMajorString(resolved.price, currency),\n priceCurrency: currency,\n availability: isProductInStock(variants)\n ? 'https://schema.org/InStock'\n : 'https://schema.org/OutOfStock',\n ...(firstVariant.sku ? { sku: firstVariant.sku } : {}),\n }\n }\n\n const images = (product.images ?? []).map(resolveMediaUrl).filter((url): url is string => !!url)\n\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: product.name,\n }\n\n if (product.description) schema.description = product.description\n if (images.length) schema.image = images\n if (offerBlock) schema.offers = offerBlock\n\n return [schema]\n}\n","import type { NestedCategory, Product } from '@zenky/storefront-api'\n\nimport { resolveMediaUrl } from './utils'\nimport type { BreadcrumbItem } from './types'\n\n/**\n * JSON-LD for a category page: BreadcrumbList + ItemList (first 10 products).\n * Returns an array of 0-2 JSON-LD objects.\n * Spec: https://schema.org/ItemList · https://schema.org/BreadcrumbList\n */\nexport function buildCategorySchemas(\n category: NestedCategory,\n products: Product[],\n breadcrumbs: BreadcrumbItem[],\n origin: string\n): Record<string, unknown>[] {\n const schemas: Record<string, unknown>[] = []\n\n if (breadcrumbs.length) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: breadcrumbs.map((crumb, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: crumb.label,\n ...(crumb.to ? { item: `${origin}${crumb.to}` } : {}),\n })),\n })\n }\n\n const items = products.slice(0, 10)\n if (items.length) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'ItemList',\n name: category.name,\n numberOfItems: items.length,\n itemListElement: items.map((product, index) => {\n const url = `${origin}/products/${product.short_id}/${product.slug || product.short_id}`\n const firstImage = product.images?.[0]\n const image = firstImage ? resolveMediaUrl(firstImage) : null\n\n const item: Record<string, unknown> = {\n '@type': 'ListItem',\n position: index + 1,\n url,\n name: product.name,\n }\n\n if (image) item.image = image\n\n return item\n }),\n })\n }\n\n return schemas\n}\n","import type { Article, Media, OfferArticle } from '@zenky/storefront-api'\n\nimport { prepareOgDescription, resolveMediaUrl } from './utils'\n\nfunction articleSchema(\n type: 'Article' | 'BlogPosting',\n opts: {\n title: string | null | undefined\n intro: string | null | undefined\n cover: Media | null | undefined\n storeName: string\n origin: string\n }\n): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': type,\n headline: opts.title ?? '',\n author: {\n '@type': 'Organization',\n name: opts.storeName,\n url: opts.origin,\n },\n }\n\n const description = prepareOgDescription(opts.intro)\n if (description) schema.description = description\n\n const image = opts.cover ? resolveMediaUrl(opts.cover) : null\n if (image) schema.image = image\n\n return schema\n}\n\n/**\n * JSON-LD schema.org/Article for the offer page's editorial copy.\n * Spec: https://schema.org/Article\n */\nexport function buildOfferArticleSchema(\n article: OfferArticle,\n storeName: string,\n origin: string\n): Record<string, unknown>[] {\n if (!article.title) return []\n\n return [\n articleSchema('Article', {\n title: article.title,\n intro: article.intro,\n cover: article.cover,\n storeName,\n origin,\n }),\n ]\n}\n\n/**\n * JSON-LD schema.org/BlogPosting for a blog article — enables Google Rich Results.\n * Spec: https://schema.org/BlogPosting\n */\nexport function buildBlogPostSchema(\n article: Article,\n storeName: string,\n origin: string\n): Record<string, unknown>[] {\n const schema = articleSchema('BlogPosting', {\n title: article.title,\n intro: article.intro,\n cover: article.cover,\n storeName,\n origin,\n })\n\n if (article.updated_at) {\n schema.dateModified = article.updated_at\n // No separate datePublished field is available — reuse updated_at.\n schema.datePublished = article.updated_at\n }\n\n return [schema]\n}\n","import { ContactType } from '@zenky/storefront-api'\n\nimport type { Schedule, Store } from '@zenky/storefront-api'\n\nimport { getSocialLinks } from '../utils'\n\n/** Schedule.days key → schema.org DayOfWeek. */\nconst SCHEMA_DAY: Record<string, string> = {\n monday: 'Monday',\n tuesday: 'Tuesday',\n wednesday: 'Wednesday',\n thursday: 'Thursday',\n friday: 'Friday',\n saturday: 'Saturday',\n sunday: 'Sunday',\n}\n\n/**\n * Schedule (per-day `hours` string like \"10:00-22:00\") → array of schema.org/OpeningHoursSpecification.\n * Days without a recognizable interval (day off / 24 hours / empty) are skipped.\n */\nfunction buildOpeningHours(schedule: Schedule | null | undefined): Record<string, unknown>[] {\n if (!schedule) return []\n\n const result: Record<string, unknown>[] = []\n for (const key of Object.keys(SCHEMA_DAY)) {\n const day = schedule.days[key as keyof typeof schedule.days]\n const match = day?.hours?.match(/(\\d{1,2}:\\d{2}).*?(\\d{1,2}:\\d{2})/)\n if (!match) continue\n\n result.push({\n '@type': 'OpeningHoursSpecification',\n dayOfWeek: SCHEMA_DAY[key],\n opens: match[1],\n closes: match[2],\n })\n }\n\n return result\n}\n\n/**\n * JSON-LD schema.org/Organization (or LocalBusiness if there's a physical location)\n * for the homepage. Feeds Google's Knowledge Panel. All data comes from the\n * already-loaded profile.\n * Spec: https://schema.org/Organization · https://schema.org/LocalBusiness\n */\nexport function buildOrganizationSchema(store: Store, origin: string): Record<string, unknown>[] {\n const logoUrl =\n store.square_logo?.medium?.url ||\n store.square_logo?.large?.url ||\n store.logo?.medium?.url ||\n store.logo?.large?.url ||\n null\n\n const phones = (store.phones ?? []).map((p) => p.number).filter((n): n is string => !!n)\n const email = store.links?.find((c) => c.type === ContactType.Email)?.value ?? null\n const socialLinks = getSocialLinks(store.links ?? []).map((l) => l.url)\n\n const city = (store.cities ?? []).find((c) => c.is_default) ?? store.cities?.[0] ?? null\n const stock = city?.stocks?.find((st) => st.address) ?? city?.stocks?.[0] ?? null\n\n const streetAddress = stock?.address ?? null\n const geo = stock?.coordinates ?? city?.coordinates ?? null\n const openingHours = buildOpeningHours(stock?.schedule ?? city?.schedule)\n\n const isLocalBusiness = Boolean(streetAddress || geo || openingHours.length)\n\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': isLocalBusiness ? 'LocalBusiness' : 'Organization',\n name: store.name,\n url: origin,\n }\n\n if (logoUrl) {\n schema.logo = logoUrl\n schema.image = logoUrl\n }\n\n if (phones.length) schema.telephone = phones.length === 1 ? phones[0] : phones\n if (email) schema.email = email\n if (socialLinks.length) schema.sameAs = socialLinks\n\n if (streetAddress || city?.name) {\n schema.address = {\n '@type': 'PostalAddress',\n ...(streetAddress ? { streetAddress } : {}),\n ...(city?.name ? { addressLocality: city.name } : {}),\n addressCountry: 'RU',\n }\n }\n\n if (geo) {\n schema.geo = {\n '@type': 'GeoCoordinates',\n latitude: geo.latitude,\n longitude: geo.longitude,\n }\n }\n\n if (openingHours.length) schema.openingHoursSpecification = openingHours\n\n return [schema]\n}\n","/** Already-resolved OG/Twitter tag values (with the consumer's defaults applied). */\nexport interface OgTagsInput {\n title: string | null\n description?: string | null\n image?: string | null\n type?: string\n url?: string | null\n}\n\n/** Framework-neutral OG + Twitter meta records. The consumer controls head priority. */\nexport function buildOgTags(input: OgTagsInput): Record<string, string>[] {\n const tags: Record<string, string>[] = []\n\n if (input.title) {\n tags.push({ property: 'og:title', content: input.title })\n tags.push({ name: 'twitter:title', content: input.title })\n }\n\n if (input.description) {\n tags.push({ property: 'og:description', content: input.description })\n tags.push({ name: 'twitter:description', content: input.description })\n }\n\n if (input.image) {\n tags.push({ property: 'og:image', content: input.image })\n tags.push({ name: 'twitter:image', content: input.image })\n }\n\n tags.push({ property: 'og:type', content: input.type ?? 'website' })\n\n if (input.url) {\n tags.push({ property: 'og:url', content: input.url })\n }\n\n return tags\n}\n","import type { Seo, SeoMetaTag } from '@zenky/storefront-api'\n\n/** SDK `Seo.meta` tags mapped to framework-neutral head meta records. */\nexport function mapSeoMetaTags(tags: SeoMetaTag[]): Record<string, string>[] {\n return tags.map((tag) => {\n const attrs: Record<string, string> = {\n [tag.name.attribute]: tag.name.value,\n [tag.value.attribute]: tag.value.value,\n }\n\n for (const additional of tag.additional) {\n attrs[additional.attribute] = additional.value\n }\n\n return attrs\n })\n}\n\n/** Meta tags from a `Seo` entity (description + `Seo.meta` mapping). The consumer resolves the title. */\nexport function buildEntitySeoMeta(seo: Seo | null | undefined): Record<string, string>[] {\n const tags: Record<string, string>[] = []\n\n if (seo?.description) {\n tags.push({ name: 'description', content: seo.description })\n }\n\n if (seo?.meta?.length) {\n tags.push(...mapSeoMetaTags(seo.meta))\n }\n\n return tags\n}\n"]}
|
package/dist/snippets/index.js
CHANGED
package/dist/stores/index.d.ts
CHANGED
|
@@ -82,12 +82,12 @@ interface CartCredentials {
|
|
|
82
82
|
id: string;
|
|
83
83
|
token: string;
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
85
|
+
/** Bonus intent (business state) — used for headless reconciliation. */
|
|
86
86
|
interface AppliedBonusesIntent {
|
|
87
87
|
enabled: boolean;
|
|
88
|
-
/** null
|
|
88
|
+
/** null means "redeem the maximum available". */
|
|
89
89
|
desiredAmountMinor: number | null;
|
|
90
|
-
/** PIN
|
|
90
|
+
/** PIN flow: bonuses were reset and need to be reconfirmed. */
|
|
91
91
|
requiresConfirmation: boolean;
|
|
92
92
|
}
|
|
93
93
|
interface MinPriceViolation {
|
|
@@ -177,14 +177,13 @@ interface PromotionsStore {
|
|
|
177
177
|
}
|
|
178
178
|
declare const usePromotionsStore: pinia.StoreDefinition<"zenky-promotions", Pick<PromotionsStore, "rewards" | "checking">, Pick<PromotionsStore, "hasRewards" | "totalEmptySlots" | "rewardSelection" | "needsSelection">, Pick<PromotionsStore, "addOrUpdateReward" | "removeRewardByPromotion" | "clearRewards" | "setChecking">>;
|
|
179
179
|
|
|
180
|
-
/**
|
|
180
|
+
/** Accessor for the loaded store/city/stock (store/city are guaranteed to be loaded). */
|
|
181
181
|
declare function useStore(): {
|
|
182
182
|
store: ComputedRef<Store>;
|
|
183
183
|
city: ComputedRef<City>;
|
|
184
184
|
stock: ComputedRef<Stock | null>;
|
|
185
185
|
};
|
|
186
186
|
|
|
187
|
-
/** Реактивный доступ к профилю (store/city/stock/categories + флаги готовности). */
|
|
188
187
|
declare function useStorefrontProfile(): {
|
|
189
188
|
store: vue.Ref<_zenky_storefront_api.Store | null, _zenky_storefront_api.Store | null>;
|
|
190
189
|
city: vue.Ref<_zenky_storefront_api.City | null, _zenky_storefront_api.City | null>;
|
package/dist/stores/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { useProductsStore, useStore, useStorefrontProfile } from '../chunk-
|
|
2
|
-
export { useCartStore, useOrderSettingsStore, usePromotionsStore } from '../chunk-
|
|
3
|
-
export { useCatalogStore } from '../chunk-
|
|
4
|
-
export { useAuthStore, useStoreProfileStore } from '../chunk-
|
|
1
|
+
export { useProductsStore, useStore, useStorefrontProfile } from '../chunk-FXKG5TFQ.js';
|
|
2
|
+
export { useCartStore, useOrderSettingsStore, usePromotionsStore } from '../chunk-XULHV6SS.js';
|
|
3
|
+
export { useCatalogStore } from '../chunk-6QCFHSLX.js';
|
|
4
|
+
export { useAuthStore, useStoreProfileStore } from '../chunk-QZ3S3NN3.js';
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
package/dist/theme/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ interface ThemeColorConfig {
|
|
|
10
10
|
danger?: string;
|
|
11
11
|
warning?: string;
|
|
12
12
|
info?: string;
|
|
13
|
-
/**
|
|
13
|
+
/** Text color over the primary background. Computed automatically if not set. */
|
|
14
14
|
onPrimary?: string;
|
|
15
15
|
headerBg?: string;
|
|
16
16
|
footerBg?: string;
|
|
@@ -30,7 +30,7 @@ interface PrimaryShades {
|
|
|
30
30
|
tintPressed: string;
|
|
31
31
|
ink: string;
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
33
|
+
/** Generic 11-step shade scale generator from a single hex color. */
|
|
34
34
|
declare function generateShades(hex: string, bg?: string, text?: string): PrimaryShades;
|
|
35
35
|
declare function generatePrimaryShades(hex: string, bg?: string, text?: string): PrimaryShades;
|
|
36
36
|
declare function buildCssVariableMap(config: ThemeColorConfig): Record<string, string>;
|
|
@@ -44,7 +44,7 @@ interface ThemeData {
|
|
|
44
44
|
fontFamily: ComputedRef<string | null>;
|
|
45
45
|
fontUrl: ComputedRef<string | null>;
|
|
46
46
|
}
|
|
47
|
-
/** CSS
|
|
47
|
+
/** Theme CSS variables and font from color/font config. Injecting them into `<head>` is the app's job. */
|
|
48
48
|
declare function useTheme(colorConfig: ThemeColorConfig, fontConfig: FontConfig): ThemeData;
|
|
49
49
|
|
|
50
50
|
export { type FontConfig, type PrimaryShades, type ThemeColorConfig, type ThemeData, buildCssVariableMap, generatePrimaryShades, generateShades, useTheme };
|
package/dist/theme/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { buildCssVariableMap, generatePrimaryShades, generateShades, useTheme } from '../chunk-
|
|
1
|
+
export { buildCssVariableMap, generatePrimaryShades, generateShades, useTheme } from '../chunk-BUNI6MGH.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -11,8 +11,8 @@ declare function createOperationChain(): OperationChain;
|
|
|
11
11
|
declare function formatPhoneNational(phone: Phone | null | undefined): string | null;
|
|
12
12
|
declare function getPhoneTelLink(phone: Phone | null | undefined): string | null;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Loose-spaced phone format: `+7 (999) 999-99-99` → `+7 999 999 99 99`.
|
|
15
|
+
* Parens and dashes become spaces, repeated spaces collapse to one.
|
|
16
16
|
*/
|
|
17
17
|
declare function formatPhoneSpaced(formatted: string | null | undefined): string;
|
|
18
18
|
|
|
@@ -41,32 +41,32 @@ declare function getWeeklySchedule(schedule: Schedule | null | undefined, locale
|
|
|
41
41
|
declare function formatScheduleStatus(schedule: Schedule | null | undefined, locale?: string): string | null;
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Builds a QR code image URL through the Zenky media API.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* The content is base64-encoded and passed in the `content` query param.
|
|
47
|
+
* This implementation doesn't depend on `window`, `btoa`, or Node's `Buffer`,
|
|
48
|
+
* so the same code works in the browser, Node SSR, and edge runtimes.
|
|
49
49
|
*/
|
|
50
50
|
declare function buildQrCodeUrl(target: string, apiBaseUrl: string, size?: number): string;
|
|
51
51
|
|
|
52
52
|
declare function formatDate(date: string): string;
|
|
53
53
|
declare function formatDateTime(date: string): string;
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
55
|
+
* Extracts YYYY-MM-DD from an ISO string (e.g. "1998-06-02T03:00:00+03:00" → "1998-06-02").
|
|
56
|
+
* For use in `<input type="date">`.
|
|
57
57
|
*/
|
|
58
58
|
declare function toDateInputValue(value: string | null): string;
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* Today's date in the given timezone, as YYYY-MM-DD.
|
|
61
|
+
* Falls back to the browser's local timezone if `timezone` is invalid or omitted.
|
|
62
62
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
* Use this to match "today" against a backend date that's relative to the
|
|
64
|
+
* store's timezone, not the user's.
|
|
65
65
|
*/
|
|
66
66
|
declare function todayInTimezone(timezone?: string | null): string;
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Noun declension by count, for Russian grammar (one/few/many forms).
|
|
70
70
|
*
|
|
71
71
|
* @example
|
|
72
72
|
* pluralize(1, 'позиция', 'позиции', 'позиций') // 'позиция'
|
|
@@ -75,13 +75,13 @@ declare function todayInTimezone(timezone?: string | null): string;
|
|
|
75
75
|
*/
|
|
76
76
|
declare function pluralize(count: number, one: string, few: string, many: string): string;
|
|
77
77
|
|
|
78
|
-
/**
|
|
78
|
+
/** The message from a ZenkyError, or a fallback. */
|
|
79
79
|
declare function getApiErrorMessage(e: unknown, fallback: string): string;
|
|
80
|
-
/** Field-level
|
|
80
|
+
/** Field-level errors from an API response's `meta.errors` (422 validation). */
|
|
81
81
|
declare function getApiFieldErrors(e: unknown): Record<string, string[]> | null;
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
84
|
+
* Moves the city to the end of an address string.
|
|
85
85
|
* "г Улан-Удэ, ул Ленина, д 29" → "ул Ленина, д 29, г Улан-Удэ"
|
|
86
86
|
*/
|
|
87
87
|
declare function formatAddressCityLast(raw: string): string;
|
package/dist/utils/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildQrCodeUrl, createOperationChain, formatAddressCityLast, formatDate, formatDateTime, formatPhoneNational, formatPhoneSpaced, formatScheduleStatus, getApiErrorMessage, getApiFieldErrors, getEmailLink, getPhoneTelLink, getSocialLinks, getTodayInfo, getWebsiteLink, getWeeklySchedule, pluralize, toDateInputValue, todayInTimezone } from '../chunk-
|
|
1
|
+
export { buildQrCodeUrl, createOperationChain, formatAddressCityLast, formatDate, formatDateTime, formatPhoneNational, formatPhoneSpaced, formatScheduleStatus, getApiErrorMessage, getApiFieldErrors, getEmailLink, getPhoneTelLink, getSocialLinks, getTodayInfo, getWebsiteLink, getWeeklySchedule, pluralize, toDateInputValue, todayInTimezone } from '../chunk-GXDTRTUT.js';
|
|
2
2
|
export { createDebounce } from '../chunk-XUGXLJZB.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This guide is for contributors working inside this package. It answers two questions:
|
|
4
|
+
what lives in each domain folder, and how to decide whether new logic belongs here or in
|
|
5
|
+
the consumer app.
|
|
6
|
+
|
|
7
|
+
## Domain map
|
|
8
|
+
|
|
9
|
+
Each folder under `src/` is one subpath export (see the README's Subpath Imports
|
|
10
|
+
section). This list says what each one owns.
|
|
11
|
+
|
|
12
|
+
- **`cart/`** — the largest domain. Cart mutation and quantities, checkout submission
|
|
13
|
+
and confirmation, delivery and payment selection, bonuses, promocodes, customer
|
|
14
|
+
attachment, and the credentials (order id/token) every cart request needs. Most
|
|
15
|
+
checkout logic lives here.
|
|
16
|
+
- **`catalog/`** — resolving which remote catalog snapshot applies to a city and stock,
|
|
17
|
+
and pure lookup helpers (find a category, group products by subcategory).
|
|
18
|
+
- **`money/`** — converting between minor units (kopecks) and major units (rubles).
|
|
19
|
+
Nothing else in the package divides or multiplies a price by 100; everything else
|
|
20
|
+
calls these functions.
|
|
21
|
+
- **`order/`** — building free delivery-time slots from store schedule data.
|
|
22
|
+
- **`pagination/`** — a generic paginated-list composable, used by product listings and
|
|
23
|
+
reused wherever the consumer needs page-by-page loading.
|
|
24
|
+
- **`ports/`** — the runtime contract itself: every port interface, the no-op defaults
|
|
25
|
+
(`NOOP_NOTIFIER`, `NOOP_ACTIVITY_REPORTER`, `NOOP_RUM`), and `createMemoryStorage()`
|
|
26
|
+
for tests. See [Runtime Ports](ports.md) for the full contract.
|
|
27
|
+
- **`products/`** — variant and modifier selection, stock-aware pricing
|
|
28
|
+
(`resolveVariantPrice`), the single-product and product-list fetches
|
|
29
|
+
(`useProductDetail`, `useProductList`), and related-product recommendations.
|
|
30
|
+
- **`promotions/`** — checking whether the cart still qualifies for a promotion reward
|
|
31
|
+
after it changes.
|
|
32
|
+
- **`seo/`** — JSON-LD builders (`Product`, `Organization`, `BreadcrumbList` + `ItemList`,
|
|
33
|
+
`BlogPosting`/`Article`) and OG-tag records. These return plain data; turning that data
|
|
34
|
+
into `<head>` tags is the consumer's job (see the README's note on `useHead`).
|
|
35
|
+
- **`snippets/`** — normalizing the snippet records a website carries (tracking scripts,
|
|
36
|
+
custom CSS) into a consistent shape.
|
|
37
|
+
- **`stores/`** — the seven Pinia stores: `auth`, `cart`, `catalog`, `products`,
|
|
38
|
+
`promotions`, `store-profile`, `order-settings`. State and business logic only, no UI.
|
|
39
|
+
- **`theme/`** — generating a full color palette from one brand hex value, and the CSS
|
|
40
|
+
variable map the consumer injects into `<head>`.
|
|
41
|
+
- **`utils/`** — small pure helpers with no domain attached: phone/date/address
|
|
42
|
+
formatting, debouncing, pluralization, an operation chain for sequencing async work.
|
|
43
|
+
- **`website-settings/`** — only the **runtime** shape of a website (what the storefront
|
|
44
|
+
reads and renders: settings values, pages, snippets, integrations). The website
|
|
45
|
+
builder's admin schema is a different concern — see below.
|
|
46
|
+
|
|
47
|
+
## Deciding where new logic goes
|
|
48
|
+
|
|
49
|
+
Default: new domain logic goes here, in core, behind a subpath. The consumer gets a thin
|
|
50
|
+
wrapper that adds the one Nuxt-specific (or Vue-SPA-specific) piece — `useHead`,
|
|
51
|
+
`useAsyncData`, a route param — and calls straight into core for everything else.
|
|
52
|
+
|
|
53
|
+
A capability stays in the consumer only when one of these is true:
|
|
54
|
+
|
|
55
|
+
- **It is a port, and the consumer owns the concrete implementation.** Core does not
|
|
56
|
+
implement the queue, transport, or vendor SDK behind a port — it only declares the
|
|
57
|
+
interface and calls it. `rum` and `commerceEvents` are both ports: core calls
|
|
58
|
+
`rum.mark()` / `rum.measure()` and `commerceEvents.trackAddedToCart()`, but the actual
|
|
59
|
+
web-vitals reporter and the `$fetch`-based event queue are consumer code. If you are
|
|
60
|
+
adding a capability that core logic needs to call into, check whether it should be a
|
|
61
|
+
new port before writing it as consumer-only code.
|
|
62
|
+
- **It has no meaningful shape without a specific framework or a specific site.** The
|
|
63
|
+
website builder's admin schema (`Setting`, `PageSettings`, the constructor UI) is tied
|
|
64
|
+
to one site's page-builder implementation and to Nuxt's `useRoute()` — there is no
|
|
65
|
+
framework-neutral version of it to extract. The same is true for the designer/preview
|
|
66
|
+
mode and for Nuxt page routing itself (`pages/`, `website-pages/` in the consumer app).
|
|
67
|
+
These are not ports with a missing adapter; they are consumer-only by nature.
|
|
68
|
+
|
|
69
|
+
When in doubt, prefer the port. A port with one adapter today costs little and keeps the
|
|
70
|
+
door open for a second consumer (a QR-menu, a future template) to supply its own.
|
|
71
|
+
|
|
72
|
+
## Port vs. parameter
|
|
73
|
+
|
|
74
|
+
Not every external dependency needs a port. Use a port when the dependency is needed
|
|
75
|
+
deep in the call graph — inside a composable that itself calls other composables, where
|
|
76
|
+
threading it through every function signature would be noisy. Use a plain function
|
|
77
|
+
parameter when the dependency is only needed at the surface, in the one function that
|
|
78
|
+
directly uses it.
|
|
79
|
+
|
|
80
|
+
`recaptcha` is the parameter case: only the checkout-submit call needs a token, so it is
|
|
81
|
+
passed as an argument to that one function. `sdk`, `storage`, and the rest of the runtime
|
|
82
|
+
are the port case: dozens of composables across every domain need them, so they are
|
|
83
|
+
resolved once through `useRuntime()` instead of being passed down through every caller in
|
|
84
|
+
between.
|
package/docs/consumer-setup.md
CHANGED
|
@@ -22,9 +22,6 @@ npm install vue@^3.5 pinia@^3 @zenky/storefront-api@^1
|
|
|
22
22
|
|
|
23
23
|
Install `@zenky/integrations-sdk` when the consumer sends Zenky integration events.
|
|
24
24
|
|
|
25
|
-
Do not commit a `file:.yalc/...` dependency. `yalc` is only for local development; CI
|
|
26
|
-
and deployed environments must install a published package version.
|
|
27
|
-
|
|
28
25
|
## 2. Decide which domains the consumer needs
|
|
29
26
|
|
|
30
27
|
Import public subpaths instead of application source files:
|
|
@@ -74,19 +71,9 @@ const runtime: StorefrontRuntime = {
|
|
|
74
71
|
set: (key, value) => localStorage.setItem(key, value),
|
|
75
72
|
remove: (key) => localStorage.removeItem(key),
|
|
76
73
|
},
|
|
77
|
-
notifier:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
warning: (message) => notifications.warning(message),
|
|
81
|
-
message: (title, options) => notifications.message(title, options),
|
|
82
|
-
},
|
|
83
|
-
messages: {
|
|
84
|
-
format: (key, params) => i18n.t(key, params),
|
|
85
|
-
},
|
|
86
|
-
activity: {
|
|
87
|
-
begin: (kind) => busy.begin(kind),
|
|
88
|
-
end: (kind) => busy.end(kind),
|
|
89
|
-
},
|
|
74
|
+
notifier: createNotifierAdapter(),
|
|
75
|
+
messages: createMessageFormatter(),
|
|
76
|
+
activity: createActivityAdapter(),
|
|
90
77
|
}
|
|
91
78
|
|
|
92
79
|
app.use(pinia)
|
|
@@ -94,6 +81,12 @@ app.use(createStorefront(runtime))
|
|
|
94
81
|
app.mount('#app')
|
|
95
82
|
```
|
|
96
83
|
|
|
84
|
+
`notifier`, `messages`, and `activity` are usually small adapter modules an app already
|
|
85
|
+
has (a toast library, i18n, a busy-state store) — write the factory once and reuse it in
|
|
86
|
+
every composition root; see the README's [Quick Start](../README.md#quick-start) for what
|
|
87
|
+
each one looks like inline. `storage` is shown inline above because it is the one port a
|
|
88
|
+
Vue SPA and a Nuxt app implement differently (compare with step 4 below).
|
|
89
|
+
|
|
97
90
|
Use `NOOP_NOTIFIER` and `NOOP_ACTIVITY_REPORTER` from the `ports` subpath when a
|
|
98
91
|
read-only consumer intentionally has no notifications or busy UI. Keep that decision
|
|
99
92
|
explicit.
|
|
@@ -187,40 +180,7 @@ Do not keep runtime or Pinia instances in module-global server state. Runtime-ow
|
|
|
187
180
|
mutation versions, request deduplication, restriction gates, and related-product state
|
|
188
181
|
must not cross request or application boundaries.
|
|
189
182
|
|
|
190
|
-
## 7.
|
|
191
|
-
|
|
192
|
-
Use `yalc` only when changing the package and consumer together.
|
|
193
|
-
|
|
194
|
-
In this repository:
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
npm run build
|
|
198
|
-
npx yalc publish
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
In the consumer:
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
npx yalc add @zenky/storefront-vue
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
After another package change:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
npm run build
|
|
211
|
-
npx yalc push
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
Before committing the consumer:
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
npx yalc remove @zenky/storefront-vue
|
|
218
|
-
npm install --save-exact @zenky/storefront-vue@<published-version>
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Verify that `package.json` and `package-lock.json` no longer reference `.yalc`.
|
|
222
|
-
|
|
223
|
-
## 8. Consumer quality gate
|
|
183
|
+
## 7. Consumer quality gate
|
|
224
184
|
|
|
225
185
|
Before requesting review:
|
|
226
186
|
|
|
@@ -248,7 +208,7 @@ For cart or checkout changes, cover:
|
|
|
248
208
|
- product restrictions;
|
|
249
209
|
- cleanup of busy state.
|
|
250
210
|
|
|
251
|
-
##
|
|
211
|
+
## 8. Upgrade checklist
|
|
252
212
|
|
|
253
213
|
1. Read the package changelog and migration notes.
|
|
254
214
|
2. Install the exact target version.
|
|
@@ -259,7 +219,7 @@ For cart or checkout changes, cover:
|
|
|
259
219
|
7. Run the same smoke suite against the deployed URL.
|
|
260
220
|
8. Only then widen the version range or promote a prerelease.
|
|
261
221
|
|
|
262
|
-
##
|
|
222
|
+
## 9. Common failures
|
|
263
223
|
|
|
264
224
|
### `createStorefront() is not installed`
|
|
265
225
|
|
|
@@ -272,8 +232,7 @@ Install Pinia before `createStorefront(runtime)` and before calling package stor
|
|
|
272
232
|
|
|
273
233
|
### The app builds locally but CI cannot install the package
|
|
274
234
|
|
|
275
|
-
Check `package.json` and the lockfile for
|
|
276
|
-
version.
|
|
235
|
+
Check `package.json` and the lockfile for a local path or an unpublished version.
|
|
277
236
|
|
|
278
237
|
### SSR users share cart or restriction state
|
|
279
238
|
|