diff --git a/.changeset/add-fixed-product-taxes-package.md b/.changeset/add-fixed-product-taxes-package.md new file mode 100644 index 00000000000..14ab3624957 --- /dev/null +++ b/.changeset/add-fixed-product-taxes-package.md @@ -0,0 +1,11 @@ +--- +'@graphcommerce/magento-fixed-product-taxes': minor +--- + +Add `@graphcommerce/magento-fixed-product-taxes` — a new package that renders +Magento's Fixed Product Taxes (FPT / WEEE / GreenTax) on product pages and +inside cart items. The store-config display setting drives the behaviour: +nothing is rendered when FPT is disabled or set to "without details"; per-tax +labels and amounts are shown for the "with details" modes; an extra "Final +price" line is appended when the price excludes FPT. Activate via +`PRIVATE_ADDITIONAL_DEPENDENCIES`. diff --git a/packages/magento-fixed-product-taxes/README.md b/packages/magento-fixed-product-taxes/README.md new file mode 100644 index 00000000000..711a5ca092a --- /dev/null +++ b/packages/magento-fixed-product-taxes/README.md @@ -0,0 +1,24 @@ +# @graphcommerce/magento-fixed-product-taxes + +Renders Magento's **Fixed Product Taxes** (FPT, also known as WEEE / GreenTax) +on product pages and inside cart items. + +## Activate + +Add to `PRIVATE_ADDITIONAL_DEPENDENCIES` in `.env`: + +```bash +PRIVATE_ADDITIONAL_DEPENDENCIES="@graphcommerce/magento-fixed-product-taxes" +``` + +## Behaviour + +Display is driven by Magento's `StoreConfig.product_fixed_product_tax_display_setting`: + +| Setting | Effect | +| -------------------------------------- | ---------------------------------------------------------------------------- | +| `FPT_DISABLED` | No FPT rendering. | +| `INCLUDE_FPT_WITHOUT_DETAILS` | Tax is already in the price; nothing extra is rendered. | +| `INCLUDE_FPT_WITH_DETAILS` | Show each FPT label + amount next to the price. | +| `EXCLUDE_FPT_WITHOUT_DETAILS` | Price excludes FPT; nothing extra is rendered. | +| `EXCLUDE_FPT_AND_INCLUDE_WITH_DETAILS` | Price excludes FPT and the FPT lines are rendered. A final price is shown. | diff --git a/packages/magento-fixed-product-taxes/graphql/fragments/FixedProductTax.graphql b/packages/magento-fixed-product-taxes/graphql/fragments/FixedProductTax.graphql new file mode 100644 index 00000000000..97099cf9725 --- /dev/null +++ b/packages/magento-fixed-product-taxes/graphql/fragments/FixedProductTax.graphql @@ -0,0 +1,6 @@ +fragment FixedProductTax on FixedProductTax { + amount { + ...Money + } + label +} diff --git a/packages/magento-fixed-product-taxes/graphql/inject/CartItem_FixedProductTaxes.graphql b/packages/magento-fixed-product-taxes/graphql/inject/CartItem_FixedProductTaxes.graphql new file mode 100644 index 00000000000..3b618f3da9a --- /dev/null +++ b/packages/magento-fixed-product-taxes/graphql/inject/CartItem_FixedProductTaxes.graphql @@ -0,0 +1,10 @@ +fragment CartItem_FixedProductTaxes on CartItemInterface @inject(into: ["CartItem"]) { + prices { + fixed_product_taxes { + amount { + ...Money + } + label + } + } +} diff --git a/packages/magento-fixed-product-taxes/graphql/inject/ProductPrice_FixedProductTaxes.graphql b/packages/magento-fixed-product-taxes/graphql/inject/ProductPrice_FixedProductTaxes.graphql new file mode 100644 index 00000000000..3466b797742 --- /dev/null +++ b/packages/magento-fixed-product-taxes/graphql/inject/ProductPrice_FixedProductTaxes.graphql @@ -0,0 +1,8 @@ +fragment ProductPrice_FixedProductTaxes on ProductPrice @inject(into: ["ProductPrice"]) { + fixed_product_taxes { + amount { + ...Money + } + label + } +} diff --git a/packages/magento-fixed-product-taxes/graphql/inject/StoreConfigFragment_FixedProductTaxes.graphql b/packages/magento-fixed-product-taxes/graphql/inject/StoreConfigFragment_FixedProductTaxes.graphql new file mode 100644 index 00000000000..5f08e1baec0 --- /dev/null +++ b/packages/magento-fixed-product-taxes/graphql/inject/StoreConfigFragment_FixedProductTaxes.graphql @@ -0,0 +1,5 @@ +fragment StoreConfigFragment_FixedProductTaxes on StoreConfig @inject(into: ["StoreConfigFragment"]) { + product_fixed_product_tax_display_setting + category_fixed_product_tax_display_setting + sales_fixed_product_tax_display_setting +} diff --git a/packages/magento-fixed-product-taxes/hooks/useFixedProductTaxes.tsx b/packages/magento-fixed-product-taxes/hooks/useFixedProductTaxes.tsx new file mode 100644 index 00000000000..3a40461b289 --- /dev/null +++ b/packages/magento-fixed-product-taxes/hooks/useFixedProductTaxes.tsx @@ -0,0 +1,29 @@ +import { useQuery } from '@graphcommerce/graphql' +import { StoreConfigDocument } from '@graphcommerce/magento-store' + +export type FptDisplaySetting = + | 'FPT_DISABLED' + | 'INCLUDE_FPT_WITHOUT_DETAILS' + | 'INCLUDE_FPT_WITH_DETAILS' + | 'EXCLUDE_FPT_WITHOUT_DETAILS' + | 'EXCLUDE_FPT_AND_INCLUDE_WITH_DETAILS' + +export function useFixedProductTaxes() { + const displaySetting = (useQuery(StoreConfigDocument).data?.storeConfig + ?.product_fixed_product_tax_display_setting ?? 'FPT_DISABLED') as FptDisplaySetting + + const subtractValue = + displaySetting === 'EXCLUDE_FPT_AND_INCLUDE_WITH_DETAILS' || + displaySetting === 'EXCLUDE_FPT_WITHOUT_DETAILS' + const showDetails = + displaySetting === 'INCLUDE_FPT_WITH_DETAILS' || + displaySetting === 'EXCLUDE_FPT_AND_INCLUDE_WITH_DETAILS' + const showFinalPrice = displaySetting === 'EXCLUDE_FPT_AND_INCLUDE_WITH_DETAILS' + + return { + displaySetting, + subtractValue, + showDetails, + showFinalPrice, + } +} diff --git a/packages/magento-fixed-product-taxes/index.ts b/packages/magento-fixed-product-taxes/index.ts new file mode 100644 index 00000000000..0cc734d7ac8 --- /dev/null +++ b/packages/magento-fixed-product-taxes/index.ts @@ -0,0 +1 @@ +export * from './hooks/useFixedProductTaxes' diff --git a/packages/magento-fixed-product-taxes/next-env.d.ts b/packages/magento-fixed-product-taxes/next-env.d.ts new file mode 100644 index 00000000000..799156c1ad0 --- /dev/null +++ b/packages/magento-fixed-product-taxes/next-env.d.ts @@ -0,0 +1,4 @@ +/// +/// +/// +/// diff --git a/packages/magento-fixed-product-taxes/package.json b/packages/magento-fixed-product-taxes/package.json new file mode 100644 index 00000000000..3c5e3bdc7da --- /dev/null +++ b/packages/magento-fixed-product-taxes/package.json @@ -0,0 +1,38 @@ +{ + "name": "@graphcommerce/magento-fixed-product-taxes", + "homepage": "https://www.graphcommerce.org/", + "repository": "github:graphcommerce-org/graphcommerce", + "version": "10.1.0-canary.16", + "sideEffects": false, + "prettier": "@graphcommerce/prettier-config-pwa", + "eslintConfig": { + "extends": "@graphcommerce/eslint-config-pwa", + "parserOptions": { + "project": "./tsconfig.json" + } + }, + "peerDependencies": { + "@graphcommerce/eslint-config-pwa": "^10.1.0-canary.16", + "@graphcommerce/graphql": "^10.1.0-canary.16", + "@graphcommerce/magento-cart": "^10.1.0-canary.16", + "@graphcommerce/magento-cart-items": "^10.1.0-canary.16", + "@graphcommerce/magento-product": "^10.1.0-canary.16", + "@graphcommerce/magento-store": "^10.1.0-canary.16", + "@graphcommerce/next-config": "^10.1.0-canary.16", + "@graphcommerce/next-ui": "^10.1.0-canary.16", + "@graphcommerce/prettier-config-pwa": "^10.1.0-canary.16", + "@graphcommerce/typescript-config-pwa": "^10.1.0-canary.16", + "@lingui/core": "^5", + "@lingui/macro": "^5", + "@lingui/react": "^5", + "@mui/material": "^7.0.0", + "next": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "exports": { + ".": "./index.ts", + "./plugins/FixedProductTaxesProductPagePrice": "./plugins/FixedProductTaxesProductPagePrice.tsx", + "./plugins/FixedProductTaxesCartItem": "./plugins/FixedProductTaxesCartItem.tsx" + } +} diff --git a/packages/magento-fixed-product-taxes/plugins/FixedProductTaxesCartItem.tsx b/packages/magento-fixed-product-taxes/plugins/FixedProductTaxesCartItem.tsx new file mode 100644 index 00000000000..0ac22eadaee --- /dev/null +++ b/packages/magento-fixed-product-taxes/plugins/FixedProductTaxesCartItem.tsx @@ -0,0 +1,44 @@ +import type { CartItemActionCardProps } from '@graphcommerce/magento-cart-items' +import { Money } from '@graphcommerce/magento-store' +import type { PluginConfig, PluginProps } from '@graphcommerce/next-config' +import { filterNonNullableKeys } from '@graphcommerce/next-ui' +import { Box } from '@mui/material' +import { useFixedProductTaxes } from '../hooks/useFixedProductTaxes' + +export const config: PluginConfig = { + type: 'component', + module: '@graphcommerce/magento-cart-items', +} + +export function CartItemActionCard(props: PluginProps) { + const { Prev, cartItem, ...rest } = props + const { showDetails } = useFixedProductTaxes() + + const taxes = filterNonNullableKeys(cartItem.prices?.fixed_product_taxes, ['amount']).filter( + (tax) => tax.amount.value && tax.amount.value > 0, + ) + + if (!showDetails || taxes.length === 0) { + return + } + + return ( + + {rest.details} + + {taxes.map((tax) => ( + + {tax.label} + + + ))} + + + } + /> + ) +} diff --git a/packages/magento-fixed-product-taxes/plugins/FixedProductTaxesProductPagePrice.tsx b/packages/magento-fixed-product-taxes/plugins/FixedProductTaxesProductPagePrice.tsx new file mode 100644 index 00000000000..c32ad0ead8b --- /dev/null +++ b/packages/magento-fixed-product-taxes/plugins/FixedProductTaxesProductPagePrice.tsx @@ -0,0 +1,52 @@ +import type { ProductPagePriceProps } from '@graphcommerce/magento-product' +import { Money } from '@graphcommerce/magento-store' +import type { PluginConfig, PluginProps } from '@graphcommerce/next-config' +import { filterNonNullableKeys, ListFormat } from '@graphcommerce/next-ui' +import { Box } from '@mui/material' +import { Trans } from '@lingui/react/macro' +import React from 'react' +import { useFixedProductTaxes } from '../hooks/useFixedProductTaxes' + +export const config: PluginConfig = { + type: 'component', + module: '@graphcommerce/magento-product', +} + +export function ProductPagePrice(props: PluginProps) { + const { Prev, product, ...rest } = props + const { displaySetting, showDetails, showFinalPrice } = useFixedProductTaxes() + + const taxes = filterNonNullableKeys( + product.price_range.minimum_price.fixed_product_taxes, + ['amount'], + ).filter((tax) => tax.amount.value && tax.amount.value > 0) + + if (taxes.length === 0 || displaySetting === 'FPT_DISABLED' || !showDetails) { + return + } + + const totalFpt = taxes.reduce((sum, tax) => sum + (tax.amount.value ?? 0), 0) + const basePrice = product.price_range.minimum_price.final_price + const finalPriceValue = (basePrice.value ?? 0) + (showFinalPrice ? totalFpt : 0) + + return ( + <> + + + + {taxes.map((tax) => ( + + {tax.label} + + ))} + + + {showFinalPrice && ( + + Final price{' '} + + + )} + + ) +} diff --git a/packages/magento-fixed-product-taxes/tsconfig.json b/packages/magento-fixed-product-taxes/tsconfig.json new file mode 100644 index 00000000000..7398153dd62 --- /dev/null +++ b/packages/magento-fixed-product-taxes/tsconfig.json @@ -0,0 +1,5 @@ +{ + "exclude": ["**/node_modules", "**/.*/"], + "include": ["**/*.ts", "**/*.tsx"], + "extends": "@graphcommerce/typescript-config-pwa/nextjs.json" +}