@laioutr/app-shopware 0.14.4
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/LICENSE.md +21 -0
- package/README.md +146 -0
- package/dist/module.d.mts +88 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +60 -0
- package/dist/runtime/app/components/ShopwareEmbedFrame.d.vue.ts +14 -0
- package/dist/runtime/app/components/ShopwareEmbedFrame.vue +57 -0
- package/dist/runtime/app/components/ShopwareEmbedFrame.vue.d.ts +14 -0
- package/dist/runtime/app/composables/useShopwareEmbedBridge.d.ts +24 -0
- package/dist/runtime/app/composables/useShopwareEmbedBridge.js +22 -0
- package/dist/runtime/app/const/bridge.d.ts +63 -0
- package/dist/runtime/app/const/bridge.js +9 -0
- package/dist/runtime/app/image/providers/shopware.d.ts +7 -0
- package/dist/runtime/app/image/providers/shopware.js +44 -0
- package/dist/runtime/app/lib/createBridgeHandler.d.ts +44 -0
- package/dist/runtime/app/lib/createBridgeHandler.js +40 -0
- package/dist/runtime/app/lib/isTrustedOrigin.d.ts +9 -0
- package/dist/runtime/app/lib/isTrustedOrigin.js +8 -0
- package/dist/runtime/app/lib/parseBridgeMessage.d.ts +11 -0
- package/dist/runtime/app/lib/parseBridgeMessage.js +34 -0
- package/dist/runtime/app/plugins/pagetypes.d.ts +7 -0
- package/dist/runtime/app/plugins/pagetypes.js +6 -0
- package/dist/runtime/app/public/app-shopware/image-placeholder.svg +7 -0
- package/dist/runtime/app/public/app-shopware/shopware-logo.svg +10 -0
- package/dist/runtime/app/sections/SectionShopwareCheckout.d.vue.ts +0 -0
- package/dist/runtime/app/sections/SectionShopwareCheckout.vue +44 -0
- package/dist/runtime/app/sections/SectionShopwareCheckout.vue.d.ts +0 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/server/client/shopwareAdminClientFactory.d.ts +4 -0
- package/dist/runtime/server/client/shopwareAdminClientFactory.js +13 -0
- package/dist/runtime/server/client/shopwareClientFactory.d.ts +3 -0
- package/dist/runtime/server/client/shopwareClientFactory.js +18 -0
- package/dist/runtime/server/composable/useGetProductParentId.d.ts +4 -0
- package/dist/runtime/server/composable/useGetProductParentId.js +41 -0
- package/dist/runtime/server/const/checkout.d.ts +7 -0
- package/dist/runtime/server/const/checkout.js +2 -0
- package/dist/runtime/server/const/cookieKeys.d.ts +1 -0
- package/dist/runtime/server/const/cookieKeys.js +1 -0
- package/dist/runtime/server/const/fallbacks.d.ts +5 -0
- package/dist/runtime/server/const/fallbacks.js +14 -0
- package/dist/runtime/server/const/includes.d.ts +1 -0
- package/dist/runtime/server/const/includes.js +1 -0
- package/dist/runtime/server/const/passthroughTokens.d.ts +14 -0
- package/dist/runtime/server/const/passthroughTokens.js +10 -0
- package/dist/runtime/server/media-libraries/shopware.d.ts +20 -0
- package/dist/runtime/server/media-libraries/shopware.js +90 -0
- package/dist/runtime/server/middleware/defineShopware.d.ts +7 -0
- package/dist/runtime/server/middleware/defineShopware.js +33 -0
- package/dist/runtime/server/orchestr/auth/login.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/auth/login.action.js +17 -0
- package/dist/runtime/server/orchestr/cart/add-item.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/add-item.action.js +46 -0
- package/dist/runtime/server/orchestr/cart/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/base.resolver.js +28 -0
- package/dist/runtime/server/orchestr/cart/cart-items.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/cart-items.link.js +10 -0
- package/dist/runtime/server/orchestr/cart/get-checkout-url.action.d.ts +10 -0
- package/dist/runtime/server/orchestr/cart/get-checkout-url.action.js +6 -0
- package/dist/runtime/server/orchestr/cart/get-current.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/get-current.query.js +9 -0
- package/dist/runtime/server/orchestr/cart/remove-items.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/remove-items.action.js +14 -0
- package/dist/runtime/server/orchestr/cart/update-items.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart/update-items.action.js +16 -0
- package/dist/runtime/server/orchestr/cart-item/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/cart-item/base.resolver.js +33 -0
- package/dist/runtime/server/orchestr/category/all.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/all.query.js +15 -0
- package/dist/runtime/server/orchestr/category/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/base.resolver.js +47 -0
- package/dist/runtime/server/orchestr/category/bySlug.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/bySlug.query.js +12 -0
- package/dist/runtime/server/orchestr/category/child-categories.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/child-categories.link.js +36 -0
- package/dist/runtime/server/orchestr/category/listing-page.page-index.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/listing-page.page-index.js +79 -0
- package/dist/runtime/server/orchestr/category/menu-item.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/category/menu-item.link.js +10 -0
- package/dist/runtime/server/orchestr/menu/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/menu/base.resolver.js +63 -0
- package/dist/runtime/server/orchestr/menu/byAlias.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/menu/byAlias.query.js +29 -0
- package/dist/runtime/server/orchestr/menu/byAlias.template.d.ts +2 -0
- package/dist/runtime/server/orchestr/menu/byAlias.template.js +25 -0
- package/dist/runtime/server/orchestr/newsletter/subscribe.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/newsletter/subscribe.action.js +27 -0
- package/dist/runtime/server/orchestr/product/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/base.resolver.js +119 -0
- package/dist/runtime/server/orchestr/product/breadcrumb.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/breadcrumb.link.js +57 -0
- package/dist/runtime/server/orchestr/product/byCategoryId.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/byCategoryId.query.js +47 -0
- package/dist/runtime/server/orchestr/product/byCategorySlug.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/byCategorySlug.query.js +59 -0
- package/dist/runtime/server/orchestr/product/byCategorySlug.template.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/byCategorySlug.template.js +40 -0
- package/dist/runtime/server/orchestr/product/bySlug.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/bySlug.query.js +19 -0
- package/dist/runtime/server/orchestr/product/detail-page.page-index.d.ts +3 -0
- package/dist/runtime/server/orchestr/product/detail-page.page-index.js +58 -0
- package/dist/runtime/server/orchestr/product/reviews.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/reviews.link.js +27 -0
- package/dist/runtime/server/orchestr/product/search.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/search.query.js +43 -0
- package/dist/runtime/server/orchestr/product/variants.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/product/variants.link.js +18 -0
- package/dist/runtime/server/orchestr/product-variant/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/product-variant/base.resolver.js +129 -0
- package/dist/runtime/server/orchestr/review/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/review/base.resolver.js +38 -0
- package/dist/runtime/server/orchestr/review/create-review.action.d.ts +2 -0
- package/dist/runtime/server/orchestr/review/create-review.action.js +14 -0
- package/dist/runtime/server/orchestr/suggested-search/suggested-search-entry.link.d.ts +2 -0
- package/dist/runtime/server/orchestr/suggested-search/suggested-search-entry.link.js +14 -0
- package/dist/runtime/server/orchestr/suggested-search/suggested-search.query.d.ts +2 -0
- package/dist/runtime/server/orchestr/suggested-search/suggested-search.query.js +54 -0
- package/dist/runtime/server/orchestr/suggested-search-entry/base.resolver.d.ts +2 -0
- package/dist/runtime/server/orchestr/suggested-search-entry/base.resolver.js +22 -0
- package/dist/runtime/server/orchestr-helper/isLinkSingle.d.ts +2 -0
- package/dist/runtime/server/orchestr-helper/isLinkSingle.js +1 -0
- package/dist/runtime/server/orchestr-helper/matchAndMap.d.ts +1 -0
- package/dist/runtime/server/orchestr-helper/matchAndMap.js +15 -0
- package/dist/runtime/server/orchestr-helper/requestedFields.d.ts +18 -0
- package/dist/runtime/server/orchestr-helper/requestedFields.js +145 -0
- package/dist/runtime/server/routes/adopt-session.post.d.ts +8 -0
- package/dist/runtime/server/routes/adopt-session.post.js +16 -0
- package/dist/runtime/server/routes/checkout.d.ts +16 -0
- package/dist/runtime/server/routes/checkout.js +31 -0
- package/dist/runtime/server/shopware-helper/adoptSession.d.ts +18 -0
- package/dist/runtime/server/shopware-helper/adoptSession.js +23 -0
- package/dist/runtime/server/shopware-helper/bootstrapContextToken.d.ts +18 -0
- package/dist/runtime/server/shopware-helper/bootstrapContextToken.js +22 -0
- package/dist/runtime/server/shopware-helper/cartErrors.d.ts +11 -0
- package/dist/runtime/server/shopware-helper/cartErrors.js +21 -0
- package/dist/runtime/server/shopware-helper/cartMapper.d.ts +14 -0
- package/dist/runtime/server/shopware-helper/cartMapper.js +83 -0
- package/dist/runtime/server/shopware-helper/categoryFlattener.d.ts +75 -0
- package/dist/runtime/server/shopware-helper/categoryFlattener.js +11 -0
- package/dist/runtime/server/shopware-helper/checkoutUrl.d.ts +12 -0
- package/dist/runtime/server/shopware-helper/checkoutUrl.js +5 -0
- package/dist/runtime/server/shopware-helper/contextTokenCookie.d.ts +11 -0
- package/dist/runtime/server/shopware-helper/contextTokenCookie.js +7 -0
- package/dist/runtime/server/shopware-helper/facetMapper.d.ts +38 -0
- package/dist/runtime/server/shopware-helper/facetMapper.js +79 -0
- package/dist/runtime/server/shopware-helper/fetchAllProductVariants.d.ts +137 -0
- package/dist/runtime/server/shopware-helper/fetchAllProductVariants.js +16 -0
- package/dist/runtime/server/shopware-helper/getCart.d.ts +10 -0
- package/dist/runtime/server/shopware-helper/getCart.js +4 -0
- package/dist/runtime/server/shopware-helper/isShopwareId.d.ts +4 -0
- package/dist/runtime/server/shopware-helper/isShopwareId.js +2 -0
- package/dist/runtime/server/shopware-helper/mappers/slugMapper.d.ts +10 -0
- package/dist/runtime/server/shopware-helper/mappers/slugMapper.js +9 -0
- package/dist/runtime/server/shopware-helper/mediaMapper.d.ts +8 -0
- package/dist/runtime/server/shopware-helper/mediaMapper.js +42 -0
- package/dist/runtime/server/shopware-helper/pageIndexEntries.d.ts +20 -0
- package/dist/runtime/server/shopware-helper/pageIndexEntries.js +22 -0
- package/dist/runtime/server/shopware-helper/persistContextToken.d.ts +13 -0
- package/dist/runtime/server/shopware-helper/persistContextToken.js +12 -0
- package/dist/runtime/server/shopware-helper/productLocate.d.ts +14 -0
- package/dist/runtime/server/shopware-helper/productLocate.js +9 -0
- package/dist/runtime/server/shopware-helper/readCategoryPageMeta.d.ts +10 -0
- package/dist/runtime/server/shopware-helper/readCategoryPageMeta.js +19 -0
- package/dist/runtime/server/shopware-helper/readProductPageMeta.d.ts +10 -0
- package/dist/runtime/server/shopware-helper/readProductPageMeta.js +20 -0
- package/dist/runtime/server/shopware-helper/resolveAdoptSession.d.ts +23 -0
- package/dist/runtime/server/shopware-helper/resolveAdoptSession.js +13 -0
- package/dist/runtime/server/shopware-helper/resolveCheckout.d.ts +39 -0
- package/dist/runtime/server/shopware-helper/resolveCheckout.js +24 -0
- package/dist/runtime/server/shopware-helper/sessionHandoff.d.ts +27 -0
- package/dist/runtime/server/shopware-helper/sessionHandoff.js +29 -0
- package/dist/runtime/server/shopware-helper/sortingMapper.d.ts +3 -0
- package/dist/runtime/server/shopware-helper/sortingMapper.js +7 -0
- package/dist/runtime/server/shopware-helper/storeApiPageFetcher.d.ts +20 -0
- package/dist/runtime/server/shopware-helper/storeApiPageFetcher.js +10 -0
- package/dist/runtime/server/shopware-helper/swTranslated.d.ts +7 -0
- package/dist/runtime/server/shopware-helper/swTranslated.js +4 -0
- package/dist/runtime/server/shopware-helper/system/getCurrentSystemEntities.d.ts +20 -0
- package/dist/runtime/server/shopware-helper/system/getCurrentSystemEntities.js +39 -0
- package/dist/runtime/server/shopware-helper/system/getSystemEntities.d.ts +31 -0
- package/dist/runtime/server/shopware-helper/system/getSystemEntities.js +47 -0
- package/dist/runtime/server/shopware-helper/useSeoResolver.d.ts +11 -0
- package/dist/runtime/server/shopware-helper/useSeoResolver.js +70 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/types/shopware.d.ts +29 -0
- package/dist/runtime/server/types/shopware.js +0 -0
- package/dist/runtime/server/types/storeApiTypes.d.ts +5677 -0
- package/dist/runtime/shared/const/checkout.d.ts +18 -0
- package/dist/runtime/shared/const/checkout.js +2 -0
- package/dist/runtime/shared/pageTypes/checkout.pagetype.d.ts +21 -0
- package/dist/runtime/shared/pageTypes/checkout.pagetype.js +12 -0
- package/dist/types.d.mts +3 -0
- package/package.json +96 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CartItemsLink } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { cartFragmentToken } from "../../const/passthroughTokens.js";
|
|
3
|
+
import { defineShopwareLink } from "../../middleware/defineShopware.js";
|
|
4
|
+
import { getCart } from "../../shopware-helper/getCart.js";
|
|
5
|
+
export default defineShopwareLink(CartItemsLink, async ({ context, passthrough }) => {
|
|
6
|
+
const cart = passthrough.get(cartFragmentToken) ?? await getCart(context.storefrontClient);
|
|
7
|
+
passthrough.set(cartFragmentToken, cart);
|
|
8
|
+
const targetIds = (cart.lineItems ?? []).filter((li) => li.type === "product").map((li) => li.id);
|
|
9
|
+
return { links: [{ sourceId: cart.token ?? "", targetIds }] };
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the stable, same-origin checkout URL. The single-use session-handoff code
|
|
3
|
+
* is minted server-side when the browser navigates to this route (see
|
|
4
|
+
* `server/routes/checkout.ts`), so the URL itself is safe to render and cache.
|
|
5
|
+
*
|
|
6
|
+
* This mirrors the `checkoutLink` on the Cart component resolver — both resolve to
|
|
7
|
+
* {@link CHECKOUT_ENDPOINT_PATH}.
|
|
8
|
+
*/
|
|
9
|
+
declare const _default: any;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { GetCheckoutUrlAction } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { CHECKOUT_ENDPOINT_PATH } from "../../const/checkout.js";
|
|
3
|
+
import { defineShopwareAction } from "../../middleware/defineShopware.js";
|
|
4
|
+
export default defineShopwareAction(GetCheckoutUrlAction, async () => ({
|
|
5
|
+
checkoutUrl: CHECKOUT_ENDPOINT_PATH
|
|
6
|
+
}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GetCurrentCartQuery } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { cartFragmentToken } from "../../const/passthroughTokens.js";
|
|
3
|
+
import { defineShopwareQuery } from "../../middleware/defineShopware.js";
|
|
4
|
+
import { getCart } from "../../shopware-helper/getCart.js";
|
|
5
|
+
export default defineShopwareQuery(GetCurrentCartQuery, async ({ context, passthrough }) => {
|
|
6
|
+
const cart = passthrough.get(cartFragmentToken) ?? await getCart(context.storefrontClient);
|
|
7
|
+
passthrough.set(cartFragmentToken, cart);
|
|
8
|
+
return { id: cart.token ?? "" };
|
|
9
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CartRemoveItemsAction } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { defineShopwareAction } from "../../middleware/defineShopware.js";
|
|
3
|
+
import { handleCartMutationErrors } from "../../shopware-helper/cartErrors.js";
|
|
4
|
+
import { persistContextToken } from "../../shopware-helper/persistContextToken.js";
|
|
5
|
+
export default defineShopwareAction(CartRemoveItemsAction, async ({ event, context, input }) => {
|
|
6
|
+
const { storefrontClient } = context;
|
|
7
|
+
if (input.length === 0) return;
|
|
8
|
+
const cart = await storefrontClient.invoke("removeLineItem post /checkout/cart/line-item/delete", {
|
|
9
|
+
// The store-api types the ids as a non-empty tuple; the length guard above satisfies it.
|
|
10
|
+
body: { ids: input }
|
|
11
|
+
});
|
|
12
|
+
await persistContextToken(event, cart.data.token);
|
|
13
|
+
handleCartMutationErrors(cart.data.errors);
|
|
14
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CartUpdateItemsAction } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { defineShopwareAction } from "../../middleware/defineShopware.js";
|
|
3
|
+
import { handleCartMutationErrors } from "../../shopware-helper/cartErrors.js";
|
|
4
|
+
import { persistContextToken } from "../../shopware-helper/persistContextToken.js";
|
|
5
|
+
export default defineShopwareAction(CartUpdateItemsAction, async ({ event, context, input }) => {
|
|
6
|
+
const { storefrontClient } = context;
|
|
7
|
+
const items = input.filter((item) => item.quantity !== void 0).map((item) => ({ id: item.itemId, quantity: item.quantity }));
|
|
8
|
+
if (items.length === 0) return;
|
|
9
|
+
const cart = await storefrontClient.invoke("updateLineItem patch /checkout/cart/line-item", {
|
|
10
|
+
// Shopware only needs id + quantity to update a line item; the generated LineItem
|
|
11
|
+
// type over-specifies required fields, so we send the minimal shape and cast.
|
|
12
|
+
body: { items }
|
|
13
|
+
});
|
|
14
|
+
await persistContextToken(event, cart.data.token);
|
|
15
|
+
handleCartMutationErrors(cart.data.errors);
|
|
16
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CartItemAvailability,
|
|
3
|
+
CartItemBase,
|
|
4
|
+
CartItemCost,
|
|
5
|
+
CartItemProductData,
|
|
6
|
+
CartItemQuantityRule
|
|
7
|
+
} from "@laioutr-core/canonical-types/entity/cart-item";
|
|
8
|
+
import { cartFragmentToken } from "../../const/passthroughTokens.js";
|
|
9
|
+
import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
|
|
10
|
+
import { mapCartItem } from "../../shopware-helper/cartMapper.js";
|
|
11
|
+
import { getCart } from "../../shopware-helper/getCart.js";
|
|
12
|
+
export default defineShopwareComponentResolver({
|
|
13
|
+
entityType: "CartItem",
|
|
14
|
+
label: "Shopware Cart Item Component Resolver",
|
|
15
|
+
provides: [CartItemBase, CartItemCost, CartItemAvailability, CartItemQuantityRule, CartItemProductData],
|
|
16
|
+
resolve: async ({ entityIds, context, $entity, passthrough }) => {
|
|
17
|
+
const cart = passthrough.get(cartFragmentToken) ?? await getCart(context.storefrontClient);
|
|
18
|
+
passthrough.set(cartFragmentToken, cart);
|
|
19
|
+
const lineItemsById = new Map((cart.lineItems ?? []).map((li) => [li.id, li]));
|
|
20
|
+
const entities = entityIds.map((id) => lineItemsById.get(id)).filter((li) => !!li && li.type === "product").map((li) => {
|
|
21
|
+
const mapped = mapCartItem(li, context.swCurrency);
|
|
22
|
+
return $entity({
|
|
23
|
+
id: li.id,
|
|
24
|
+
base: () => mapped.base,
|
|
25
|
+
cost: () => mapped.cost,
|
|
26
|
+
availability: () => mapped.availability,
|
|
27
|
+
quantityRule: () => mapped.quantityRule,
|
|
28
|
+
productData: () => mapped.productData
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
return { entities };
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CategoryAllQuery } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { defineShopwareQuery } from "../../middleware/defineShopware.js";
|
|
3
|
+
export default defineShopwareQuery(CategoryAllQuery, async ({ context, pagination }) => {
|
|
4
|
+
const { storefrontClient } = context;
|
|
5
|
+
const response = await storefrontClient.invoke("readCategoryList post /category", {
|
|
6
|
+
body: {
|
|
7
|
+
page: pagination.page,
|
|
8
|
+
limit: pagination.limit,
|
|
9
|
+
includes: {
|
|
10
|
+
category: ["id"]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
return { ids: (response.data.elements ?? [])?.map((element) => element.id) };
|
|
15
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CategoryBase, CategoryContent, CategoryMedia, CategorySeo } from "@laioutr-core/canonical-types/entity/category";
|
|
2
|
+
import { categoriesToken } from "../../const/passthroughTokens.js";
|
|
3
|
+
import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
|
|
4
|
+
import { entitySlug } from "../../shopware-helper/mappers/slugMapper.js";
|
|
5
|
+
import { mapMedia } from "../../shopware-helper/mediaMapper.js";
|
|
6
|
+
import { swTranslated } from "../../shopware-helper/swTranslated.js";
|
|
7
|
+
export default defineShopwareComponentResolver({
|
|
8
|
+
entityType: "Category",
|
|
9
|
+
label: "Shopware Category Connector",
|
|
10
|
+
provides: [CategoryBase, CategoryContent, CategoryMedia, CategorySeo],
|
|
11
|
+
resolve: async ({ entityIds, context, passthrough, $entity }) => {
|
|
12
|
+
const { storefrontClient } = context;
|
|
13
|
+
const categories = passthrough.has(categoriesToken) ? passthrough.get(categoriesToken) : (await storefrontClient.invoke("readCategoryList post /category", {
|
|
14
|
+
// Neither association is returned by default; without `media` the media component is always empty.
|
|
15
|
+
body: { ids: entityIds, associations: { seoUrls: {}, media: {} } }
|
|
16
|
+
})).data.elements;
|
|
17
|
+
if (!categories) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"Categories not found in passthrough. The component resolver does not request categories from shopware at the moment."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
entities: categories.filter((category) => entityIds.includes(category.id)).map(
|
|
24
|
+
(category) => $entity({
|
|
25
|
+
id: category.id,
|
|
26
|
+
base: () => ({
|
|
27
|
+
slug: entitySlug(category),
|
|
28
|
+
title: swTranslated(category, "name") ?? category.name
|
|
29
|
+
}),
|
|
30
|
+
content: () => ({
|
|
31
|
+
description: { html: swTranslated(category, "description") ?? category.description ?? "" }
|
|
32
|
+
}),
|
|
33
|
+
media: () => ({
|
|
34
|
+
media: category.media ? [mapMedia(category.media)] : []
|
|
35
|
+
}),
|
|
36
|
+
seo: () => ({
|
|
37
|
+
title: category.metaTitle,
|
|
38
|
+
description: category.metaDescription
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
cache: {
|
|
45
|
+
ttl: "10 minutes"
|
|
46
|
+
}
|
|
47
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CategoryBySlugQuery } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { defineShopwareQuery } from "../../middleware/defineShopware.js";
|
|
3
|
+
import { useSeoResolver } from "../../shopware-helper/useSeoResolver.js";
|
|
4
|
+
export default defineShopwareQuery(CategoryBySlugQuery, async ({ context, input }) => {
|
|
5
|
+
const { slug } = input;
|
|
6
|
+
const seoResolver = useSeoResolver(context.storefrontClient);
|
|
7
|
+
const seoEntry = await seoResolver.resolve("category", slug);
|
|
8
|
+
if (!seoEntry) {
|
|
9
|
+
throw new Error(`No seo url found for category slug: ${slug}`);
|
|
10
|
+
}
|
|
11
|
+
return { id: seoEntry.id };
|
|
12
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ChildCategoriesLink } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { defineShopwareLink } from "../../middleware/defineShopware.js";
|
|
3
|
+
const readChildren = async (storefrontClient, categoryId) => {
|
|
4
|
+
const response = await storefrontClient.invoke("readNavigation post /navigation/{activeId}/{rootId}", {
|
|
5
|
+
pathParams: { activeId: categoryId, rootId: categoryId },
|
|
6
|
+
body: {
|
|
7
|
+
depth: 1,
|
|
8
|
+
// A projection, so unlike a filter it removes no rows and cannot disturb the server's order.
|
|
9
|
+
includes: { category: ["id"] }
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
return response.data ?? [];
|
|
13
|
+
};
|
|
14
|
+
export default defineShopwareLink({
|
|
15
|
+
implements: ChildCategoriesLink,
|
|
16
|
+
run: async ({ entityIds, context, pagination }) => {
|
|
17
|
+
if (entityIds.length === 0) return { links: [] };
|
|
18
|
+
const childrenPerSource = await Promise.all(entityIds.map((categoryId) => readChildren(context.storefrontClient, categoryId)));
|
|
19
|
+
return {
|
|
20
|
+
links: entityIds.map((sourceId, index) => {
|
|
21
|
+
const children = childrenPerSource[index] ?? [];
|
|
22
|
+
return {
|
|
23
|
+
sourceId,
|
|
24
|
+
targetIds: children.slice(pagination.offset, pagination.offset + pagination.limit).map((child) => child.id),
|
|
25
|
+
entityTotal: children.length
|
|
26
|
+
};
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
cache: {
|
|
31
|
+
strategy: "ttl",
|
|
32
|
+
ttl: "10 minutes",
|
|
33
|
+
// `linkRunner` already prefixes a client-env key, so locale, market and currency are covered.
|
|
34
|
+
buildCacheKey: ({ entityIds, pagination }) => `${[...entityIds].sort().join(",")}:${pagination.offset}:${pagination.limit}`
|
|
35
|
+
}
|
|
36
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { paginate } from "#imports";
|
|
2
|
+
import { ProductListingPage } from "@laioutr-core/canonical-types/ecommerce";
|
|
3
|
+
import { defineShopwarePageIndex } from "../../middleware/defineShopware.js";
|
|
4
|
+
import { toCategoryPageEntry } from "../../shopware-helper/pageIndexEntries.js";
|
|
5
|
+
import { readCategoryPageMeta } from "../../shopware-helper/readCategoryPageMeta.js";
|
|
6
|
+
import { SHOPWARE_MAX_LIMIT, storeApiPageFetcher } from "../../shopware-helper/storeApiPageFetcher.js";
|
|
7
|
+
import { useSeoResolver } from "../../shopware-helper/useSeoResolver.js";
|
|
8
|
+
const membershipFilter = [
|
|
9
|
+
{ type: "equals", field: "type", value: "page" },
|
|
10
|
+
{ type: "equals", field: "active", value: true },
|
|
11
|
+
{ type: "range", field: "level", parameters: { gt: 1 } }
|
|
12
|
+
];
|
|
13
|
+
const pageIndexCriteria = {
|
|
14
|
+
associations: { seoUrls: {}, media: {} },
|
|
15
|
+
includes: {
|
|
16
|
+
category: ["id", "name", "translated", "updatedAt", "seoUrls", "media"],
|
|
17
|
+
seo_url: ["seoPathInfo", "isCanonical", "routeName"],
|
|
18
|
+
media: ["url"]
|
|
19
|
+
},
|
|
20
|
+
filter: membershipFilter,
|
|
21
|
+
// Page-number cursors need a stable upstream order, or a resumed walk shifts entries between passes.
|
|
22
|
+
sort: [{ field: "id", order: "ASC" }]
|
|
23
|
+
};
|
|
24
|
+
export default defineShopwarePageIndex({
|
|
25
|
+
for: ProductListingPage,
|
|
26
|
+
label: "Shopware Category",
|
|
27
|
+
batchSize: SHOPWARE_MAX_LIMIT,
|
|
28
|
+
cache: { ttl: "1h", search: { ttl: "5m" }, locate: { ttl: "1 day" } },
|
|
29
|
+
/**
|
|
30
|
+
* Point lookup down the same slug→id path as `bySlug.query.ts`, so the index and the rendered page
|
|
31
|
+
* always agree on which category a URL means. The resolver yields only an id, so the page metadata
|
|
32
|
+
* costs a second store-API read — absorbed by `cache.locate`, and non-fatal: the subject still
|
|
33
|
+
* resolves when it fails.
|
|
34
|
+
*
|
|
35
|
+
* `locales` is left unset: the store API scopes SEO reads by the `sw-language-id` header, so
|
|
36
|
+
* cross-locale slugs need one read per language and are not implemented. Reporting only the current
|
|
37
|
+
* locale would claim the category has no page in any other.
|
|
38
|
+
*/
|
|
39
|
+
locate: async ({ context, params }) => {
|
|
40
|
+
const seoEntry = await useSeoResolver(context.storefrontClient).resolve("category", params.slug);
|
|
41
|
+
if (!seoEntry) return void 0;
|
|
42
|
+
return {
|
|
43
|
+
subject: { type: "Category", id: seoEntry.id },
|
|
44
|
+
meta: await readCategoryPageMeta(context.storefrontClient, seoEntry.id)
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
count: async ({ context }) => {
|
|
48
|
+
const response = await context.storefrontClient.invoke("readCategoryList post /category", {
|
|
49
|
+
body: { ...pageIndexCriteria, limit: 1, "total-count-mode": "exact" }
|
|
50
|
+
});
|
|
51
|
+
return response.data.total ?? 0;
|
|
52
|
+
},
|
|
53
|
+
/** Substring match on name and metaTitle, the same form the query-template picker uses for categories. */
|
|
54
|
+
search: ({ context, term, take }) => context.storefrontClient.invoke("readCategoryList post /category", {
|
|
55
|
+
body: {
|
|
56
|
+
...pageIndexCriteria,
|
|
57
|
+
filter: [
|
|
58
|
+
...membershipFilter,
|
|
59
|
+
{
|
|
60
|
+
type: "multi",
|
|
61
|
+
operator: "or",
|
|
62
|
+
queries: [
|
|
63
|
+
{ type: "contains", field: "name", value: term },
|
|
64
|
+
{ type: "contains", field: "metaTitle", value: term }
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
limit: take
|
|
69
|
+
}
|
|
70
|
+
}).then((response) => (response.data.elements ?? []).map(toCategoryPageEntry)),
|
|
71
|
+
list: ({ context, batchSize, startCursor }) => paginate(
|
|
72
|
+
storeApiPageFetcher(
|
|
73
|
+
({ page, limit }) => context.storefrontClient.invoke("readCategoryList post /category", { body: { ...pageIndexCriteria, page, limit } }).then((response) => response.data),
|
|
74
|
+
toCategoryPageEntry,
|
|
75
|
+
batchSize
|
|
76
|
+
),
|
|
77
|
+
startCursor
|
|
78
|
+
)
|
|
79
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CategoryBreadcrumbLink } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { defineShopwareLink } from "../../middleware/defineShopware.js";
|
|
3
|
+
export default defineShopwareLink(CategoryBreadcrumbLink, async ({ entityIds }) => ({
|
|
4
|
+
links: entityIds.map(
|
|
5
|
+
(id) => ({
|
|
6
|
+
sourceId: id,
|
|
7
|
+
targetId: id
|
|
8
|
+
})
|
|
9
|
+
)
|
|
10
|
+
}));
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { MenuItemBase } from "@laioutr-core/canonical-types/entity/menuItem";
|
|
2
|
+
import { categoriesToken } from "../../const/passthroughTokens.js";
|
|
3
|
+
import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
|
|
4
|
+
import { entitySlug } from "../../shopware-helper/mappers/slugMapper.js";
|
|
5
|
+
import { swTranslated } from "../../shopware-helper/swTranslated.js";
|
|
6
|
+
export default defineShopwareComponentResolver({
|
|
7
|
+
entityType: "MenuItem",
|
|
8
|
+
label: "Shopware Menu Connector",
|
|
9
|
+
provides: [MenuItemBase],
|
|
10
|
+
resolve: async ({ passthrough, $entity }) => {
|
|
11
|
+
const categories = passthrough.require(categoriesToken);
|
|
12
|
+
return {
|
|
13
|
+
entities: categories.map(
|
|
14
|
+
(category) => $entity({
|
|
15
|
+
id: category.id,
|
|
16
|
+
base: () => {
|
|
17
|
+
const baseItem = {
|
|
18
|
+
type: "link",
|
|
19
|
+
name: swTranslated(category, "name"),
|
|
20
|
+
childIds: category.children?.map((child) => child.id),
|
|
21
|
+
parentId: category.parentId
|
|
22
|
+
};
|
|
23
|
+
if (category.type === "folder") {
|
|
24
|
+
return {
|
|
25
|
+
...baseItem,
|
|
26
|
+
type: "folder"
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (category.type === "link") {
|
|
30
|
+
return {
|
|
31
|
+
...baseItem,
|
|
32
|
+
link: {
|
|
33
|
+
type: "url",
|
|
34
|
+
href: category.internalLink ?? category.externalLink ?? "/"
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (category.type === "page") {
|
|
39
|
+
return {
|
|
40
|
+
...baseItem,
|
|
41
|
+
link: {
|
|
42
|
+
type: "reference",
|
|
43
|
+
reference: {
|
|
44
|
+
type: category.linkType ?? "Category",
|
|
45
|
+
slug: entitySlug(category),
|
|
46
|
+
id: category.id
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
...baseItem,
|
|
53
|
+
type: "folder"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
)
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
cache: {
|
|
61
|
+
ttl: "10 minutes"
|
|
62
|
+
}
|
|
63
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MenuByAliasQuery } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { categoriesToken } from "../../const/passthroughTokens.js";
|
|
3
|
+
import { defineShopwareQuery } from "../../middleware/defineShopware.js";
|
|
4
|
+
import { flattenCategories } from "../../shopware-helper/categoryFlattener.js";
|
|
5
|
+
export default defineShopwareQuery({
|
|
6
|
+
implements: MenuByAliasQuery,
|
|
7
|
+
run: async ({ input, context, passthrough }) => {
|
|
8
|
+
const { alias } = input;
|
|
9
|
+
const response = await context.storefrontClient.invoke("readNavigation post /navigation/{activeId}/{rootId}", {
|
|
10
|
+
pathParams: {
|
|
11
|
+
activeId: alias,
|
|
12
|
+
rootId: alias
|
|
13
|
+
},
|
|
14
|
+
body: { associations: { seoUrls: {} } }
|
|
15
|
+
});
|
|
16
|
+
const flattenedCategories = flattenCategories(response.data ?? []);
|
|
17
|
+
passthrough.set(categoriesToken, flattenedCategories);
|
|
18
|
+
return {
|
|
19
|
+
ids: flattenedCategories.map((item) => item.id)
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
cache: {
|
|
23
|
+
strategy: "ttl",
|
|
24
|
+
ttl: "10 minutes",
|
|
25
|
+
buildCacheKey({ input }) {
|
|
26
|
+
return input.alias;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MenuByAliasQuery } from "@laioutr-core/canonical-types/ecommerce";
|
|
2
|
+
import { defineShopwareQueryTemplateProvider } from "../../middleware/defineShopware.js";
|
|
3
|
+
export default defineShopwareQueryTemplateProvider({
|
|
4
|
+
for: MenuByAliasQuery,
|
|
5
|
+
run: async ({ input, context }) => [
|
|
6
|
+
{
|
|
7
|
+
input: {
|
|
8
|
+
alias: "main-navigation"
|
|
9
|
+
},
|
|
10
|
+
label: "Main Navigation"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
input: {
|
|
14
|
+
alias: "service-navigation"
|
|
15
|
+
},
|
|
16
|
+
label: "Service Navigation"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
input: {
|
|
20
|
+
alias: "footer-navigation"
|
|
21
|
+
},
|
|
22
|
+
label: "Footer Navigation"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { consola } from "consola";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { SubscribeAction } from "@laioutr-core/canonical-types/newsletter";
|
|
4
|
+
import { defineShopwareAction } from "../../middleware/defineShopware.js";
|
|
5
|
+
const logger = consola.withTag("shopware/newsletter");
|
|
6
|
+
export default defineShopwareAction(SubscribeAction, async ({ context, input }) => {
|
|
7
|
+
const { storefrontUrl } = useRuntimeConfig()["@laioutr/app-shopware"];
|
|
8
|
+
if (!storefrontUrl) {
|
|
9
|
+
logger.error("Cannot subscribe to newsletter: storefrontUrl is not configured");
|
|
10
|
+
return { status: "error" };
|
|
11
|
+
}
|
|
12
|
+
await context.storefrontClient.invoke("subscribeToNewsletter post /newsletter/subscribe", {
|
|
13
|
+
body: {
|
|
14
|
+
option: "subscribe",
|
|
15
|
+
storefrontUrl,
|
|
16
|
+
email: input.email,
|
|
17
|
+
firstName: input.person?.firstName,
|
|
18
|
+
lastName: input.person?.lastName,
|
|
19
|
+
street: input.address?.address1,
|
|
20
|
+
city: input.address?.city,
|
|
21
|
+
zipCode: input.address?.postalCode,
|
|
22
|
+
languageId: context.currentSystemEntities.locale.languageId,
|
|
23
|
+
salutationId: context.systemEntities.salutations.find((salutation) => salutation.key === input.person?.salutation)?.id
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return { status: "success" };
|
|
27
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Money } from "@screeny05/ts-money";
|
|
2
|
+
import {
|
|
3
|
+
ProductBase,
|
|
4
|
+
ProductDefaultVariant,
|
|
5
|
+
ProductDescription,
|
|
6
|
+
ProductFlags,
|
|
7
|
+
ProductInfo,
|
|
8
|
+
ProductMedia,
|
|
9
|
+
ProductPrices,
|
|
10
|
+
ProductRating,
|
|
11
|
+
ProductSeo
|
|
12
|
+
} from "@laioutr-core/canonical-types/entity/product";
|
|
13
|
+
import { FALLBACK_IMAGE } from "../../const/fallbacks.js";
|
|
14
|
+
import { parentIdToDefaultVariantIdToken, productVariantsToken } from "../../const/passthroughTokens.js";
|
|
15
|
+
import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
|
|
16
|
+
import { fetchAllProducts } from "../../shopware-helper/fetchAllProductVariants.js";
|
|
17
|
+
import { entitySlug } from "../../shopware-helper/mappers/slugMapper.js";
|
|
18
|
+
import { mapMedia } from "../../shopware-helper/mediaMapper.js";
|
|
19
|
+
import { swTranslated } from "../../shopware-helper/swTranslated.js";
|
|
20
|
+
export default defineShopwareComponentResolver({
|
|
21
|
+
label: "Shopware Product Connector",
|
|
22
|
+
entityType: "Product",
|
|
23
|
+
provides: [
|
|
24
|
+
ProductBase,
|
|
25
|
+
ProductInfo,
|
|
26
|
+
ProductPrices,
|
|
27
|
+
ProductMedia,
|
|
28
|
+
ProductFlags,
|
|
29
|
+
ProductSeo,
|
|
30
|
+
ProductDescription,
|
|
31
|
+
ProductDefaultVariant,
|
|
32
|
+
ProductRating
|
|
33
|
+
],
|
|
34
|
+
resolve: async ({ entityIds, context, $entity, passthrough }) => {
|
|
35
|
+
const parentIdToDefaultVariantId = passthrough.get(parentIdToDefaultVariantIdToken);
|
|
36
|
+
const variantIds = entityIds.map((id) => parentIdToDefaultVariantId?.[id] ?? id);
|
|
37
|
+
const loadedVariants = passthrough.get(productVariantsToken) ?? [];
|
|
38
|
+
const missingVariantIds = variantIds.filter((id) => !loadedVariants.some((variant) => variant.id === id));
|
|
39
|
+
if (missingVariantIds.length > 0) {
|
|
40
|
+
const response = await fetchAllProducts(context.storefrontClient, { productIds: missingVariantIds, loadVariants: false });
|
|
41
|
+
loadedVariants.push(...response);
|
|
42
|
+
}
|
|
43
|
+
const shopwareProducts = variantIds.map((id) => {
|
|
44
|
+
const rawVariant = loadedVariants.find((variant) => variant.id === id);
|
|
45
|
+
if (!rawVariant) return void 0;
|
|
46
|
+
return {
|
|
47
|
+
rawVariant,
|
|
48
|
+
rawProduct: loadedVariants.find((variant) => variant.id === variant.parentId) ?? rawVariant
|
|
49
|
+
};
|
|
50
|
+
}).filter((product) => !!product);
|
|
51
|
+
const entities = shopwareProducts.map(({ rawProduct, rawVariant }) => {
|
|
52
|
+
const mappedCover = rawProduct.cover?.media ? mapMedia(rawVariant.cover?.media ?? rawProduct.cover.media) : FALLBACK_IMAGE;
|
|
53
|
+
return $entity({
|
|
54
|
+
// The parent-id is the actual product-id, even if the product has variants.
|
|
55
|
+
id: rawProduct.id,
|
|
56
|
+
base: {
|
|
57
|
+
name: swTranslated(rawProduct, "name"),
|
|
58
|
+
slug: entitySlug(rawProduct)
|
|
59
|
+
},
|
|
60
|
+
defaultVariant: {
|
|
61
|
+
id: rawVariant.id
|
|
62
|
+
},
|
|
63
|
+
info: {
|
|
64
|
+
cover: mappedCover,
|
|
65
|
+
shortDescription: swTranslated(rawProduct, "description") || swTranslated(rawVariant, "description"),
|
|
66
|
+
brand: swTranslated(rawProduct.manufacturer, "name") || swTranslated(rawVariant.manufacturer, "name") || ""
|
|
67
|
+
},
|
|
68
|
+
description: {
|
|
69
|
+
html: swTranslated(rawProduct, "description") || swTranslated(rawVariant, "description") || ""
|
|
70
|
+
},
|
|
71
|
+
seo: {
|
|
72
|
+
title: swTranslated(rawProduct, "metaTitle") || swTranslated(rawVariant, "metaTitle"),
|
|
73
|
+
description: swTranslated(rawProduct, "metaDescription") || swTranslated(rawVariant, "metaDescription")
|
|
74
|
+
},
|
|
75
|
+
media: () => {
|
|
76
|
+
const mappedMedia = (rawProduct.media ?? rawVariant.media)?.filter((image) => !!image.media).map((image) => mapMedia(image.media)) ?? [];
|
|
77
|
+
const isFirstMediaCover = mappedCover.sources.at(0)?.src === mappedMedia.at(0)?.sources.at(0)?.src;
|
|
78
|
+
const allMedia = isFirstMediaCover ? mappedMedia : [mappedCover, ...mappedMedia];
|
|
79
|
+
return {
|
|
80
|
+
cover: mappedCover,
|
|
81
|
+
media: allMedia,
|
|
82
|
+
images: allMedia.filter((media) => media.type === "image")
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
prices: () => {
|
|
86
|
+
const rawListPrice = rawProduct.calculatedCheapestPrice?.listPrice?.price ?? rawProduct.calculatedPrice.listPrice?.price ?? rawVariant.calculatedCheapestPrice?.listPrice?.price ?? rawVariant.calculatedPrice.listPrice?.price;
|
|
87
|
+
const listPrice = rawListPrice ? Money.fromDecimal(rawListPrice, context.swCurrency) : void 0;
|
|
88
|
+
const totalPrice = Money.fromDecimal(
|
|
89
|
+
rawProduct.calculatedCheapestPrice?.totalPrice ?? rawProduct.calculatedPrice.totalPrice,
|
|
90
|
+
context.swCurrency
|
|
91
|
+
);
|
|
92
|
+
const hasSavings = typeof listPrice === "object" && listPrice.greaterThan(totalPrice);
|
|
93
|
+
const savingsPercent = hasSavings ? 100 - totalPrice.percentageOf(listPrice) : void 0;
|
|
94
|
+
return {
|
|
95
|
+
price: totalPrice,
|
|
96
|
+
strikethroughPrice: hasSavings ? listPrice : void 0,
|
|
97
|
+
savingsPercent,
|
|
98
|
+
isOnSale: hasSavings,
|
|
99
|
+
isStartingFrom: rawProduct.calculatedCheapestPrice?.hasRange || rawProduct.calculatedPrice.hasRange
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
flags: [],
|
|
103
|
+
rating: {
|
|
104
|
+
average: rawProduct.ratingAverage ?? rawVariant.ratingAverage ?? 0,
|
|
105
|
+
count: rawProduct.productReviews?.length ?? 0
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
return { entities };
|
|
110
|
+
},
|
|
111
|
+
cache: {
|
|
112
|
+
ttl: "1 day",
|
|
113
|
+
components: {
|
|
114
|
+
prices: {
|
|
115
|
+
ttl: "15 minutes"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|