diff --git a/.changeset/cool-pans-lie.md b/.changeset/cool-pans-lie.md new file mode 100644 index 0000000000..c5b298fd85 --- /dev/null +++ b/.changeset/cool-pans-lie.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/magento-store': minor +--- + +Added support for multiple display currencies in the frontend. Multiple currencies were already supported, but this introduces Display Currencies for viewing the cart in different currencies. diff --git a/.changeset/lemon-ads-rescue.md b/.changeset/lemon-ads-rescue.md new file mode 100644 index 0000000000..97b8fd8936 --- /dev/null +++ b/.changeset/lemon-ads-rescue.md @@ -0,0 +1,5 @@ +--- +'@graphcommerce/magento-store': minor +--- + +Refactored the Store Selector to be more of a form and have multiple nested toggles to switch groups, then stores and then currencies. It automatically hides features that aren't used: If only a single group is used with multiple stores only the store selector is shown. If multiple groups are used with each a single store is used, only the group selector is shown. If only a single currency is used, there is no currency selector. If multiple currencies are used, the currency selector is shown. This makes the selector more user-friendly and less cluttered. diff --git a/docs/roadmap.md b/docs/roadmap.md index 3075ccdff3..5bdfc4e8ff 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -17,6 +17,7 @@ roadmap. - [x] Adobe Commerce: Store credit functionality - [x] Adobe Commerce: Gift card functionality - [ ] Adobe Commerce: Content staging functionality in preview mode. +- [ ] Custom options pricing on product page - [ ] Storelocator UI. Generic Store Locator UI to allow for any datasource to be used. Integrate with Magento's pickupLocations functionality. - [ ] Store inventory on product pages UI and Magento's pickupLocations @@ -30,6 +31,11 @@ roadmap. GraphCommerce integration. - [ ] GraphCommerce Pro: Cache Notifier module + GraphCommerce Integration to revalidate the frontend on backend changes. +- [ ] GraphCommerce: Video support +- [ ] GraphCommerce: Product List Gallery +- [ ] GraphCommerce: Product List Configurable Image Toggle +- [x] Magento: Implement currency query and switch frontend to different + currency with usePrivateQuery ## Researching / Considering @@ -69,6 +75,13 @@ roadmap. integration) - [ ] GraphCommerce Pro: Shop in shop functionality - [ ] GraphCommerce POS: Create a light POS integration for GraphCommerce +- [ ] Magento customerDownloadableProducts +- [ ] Magento: customerPaymentTokens + deletePaymentToken +- [ ] Magento: customerDownloadableProducts +- [ ] Magento 2.4.7: recaptchaV3Config +- [ ] Magento: downloadable products +- [ ] Magento: Migrate to createCustomerV2 +- [ ] Magento 2.4.7: createGuestCart migration ## Released diff --git a/examples/magento-graphcms/pages/switch-stores.tsx b/examples/magento-graphcms/pages/switch-stores.tsx index 8d735e50be..1b02dec290 100644 --- a/examples/magento-graphcms/pages/switch-stores.tsx +++ b/examples/magento-graphcms/pages/switch-stores.tsx @@ -1,53 +1,92 @@ -import { PageOptions } from '@graphcommerce/framer-next-pages' +import { PageOptions, usePrevPageRouter } from '@graphcommerce/framer-next-pages' import { PageMeta, StoreConfigDocument, - StoreSwitcherList, + StoreSwitcherApplyButton, + StoreSwitcherCurrencySelector, + StoreSwitcherFormProvider, + StoreSwitcherGroupSelector, + StoreSwitcherLinkOrButton, StoreSwitcherListDocument, StoreSwitcherListQuery, + StoreSwitcherStoreSelector, + storeToLocale, } from '@graphcommerce/magento-store' import { + FormActions, GetStaticProps, iconLanguage, LayoutOverlayHeader, LayoutTitle, + OverlayStickyBottom, + SectionHeader, } from '@graphcommerce/next-ui' import { i18n } from '@lingui/core' import { Trans } from '@lingui/react' import { Container } from '@mui/material' -import { useRouter } from 'next/router' -import { LayoutOverlay, LayoutOverlayProps } from '../components' +import { LayoutDocument, LayoutOverlay, LayoutOverlayProps } from '../components' import { graphqlSsrClient, graphqlSharedClient } from '../lib/graphql/graphqlSsrClient' +import { useRouter } from 'next/router' type RouteProps = { country?: string[] } type Props = StoreSwitcherListQuery type GetPageStaticProps = GetStaticProps function StoresIndexPage({ availableStores }: Props) { - const { locale } = useRouter() + const prev = usePrevPageRouter() + const router = useRouter() return ( - <> + { + await router.push(prev?.asPath ?? '/', undefined, { + locale: storeToLocale(data.storeCode), + scroll: false, + }) + }} + > - + + + + } + > - + - + ({ mb: theme.spacings.lg })}> - + - + } + showStores={1} + showCurrencies={1} + /> + } + showCurrencies={1} + /> + } /> + + + + + + - + ) } const pageOptions: PageOptions = { overlayGroup: 'left', Layout: LayoutOverlay, - layoutProps: { variantMd: 'left' }, + layoutProps: { variantMd: 'right', sizeMd: 'floating', justifyMd: 'start' }, } StoresIndexPage.pageOptions = pageOptions @@ -56,13 +95,14 @@ export default StoresIndexPage export const getStaticProps: GetPageStaticProps = async (context) => { const client = graphqlSharedClient(context) const staticClient = graphqlSsrClient(context) - const conf = client.query({ query: StoreConfigDocument }) + const layout = staticClient.query({ query: LayoutDocument }) const stores = staticClient.query({ query: StoreSwitcherListDocument }) return { props: { ...(await stores).data, + ...(await layout).data, apolloState: await conf.then(() => client.cache.extract()), }, } diff --git a/examples/magento-open-source/pages/switch-stores.tsx b/examples/magento-open-source/pages/switch-stores.tsx index 56c416f237..f7515dcd41 100644 --- a/examples/magento-open-source/pages/switch-stores.tsx +++ b/examples/magento-open-source/pages/switch-stores.tsx @@ -1,51 +1,94 @@ -import type { PageOptions } from '@graphcommerce/framer-next-pages' +import { usePrevPageRouter, type PageOptions } from '@graphcommerce/framer-next-pages' import type { StoreSwitcherListQuery } from '@graphcommerce/magento-store' import { PageMeta, StoreConfigDocument, - StoreSwitcherList, + StoreSwitcherApplyButton, + StoreSwitcherCurrencySelector, + StoreSwitcherFormProvider, + StoreSwitcherGroupSelector, + StoreSwitcherLinkOrButton, StoreSwitcherListDocument, + StoreSwitcherStoreSelector, + storeToLocale, } from '@graphcommerce/magento-store' import type { GetStaticProps } from '@graphcommerce/next-ui' -import { iconLanguage, LayoutOverlayHeader, LayoutTitle } from '@graphcommerce/next-ui' +import { + FormActions, + iconLanguage, + LayoutOverlayHeader, + LayoutTitle, + OverlayStickyBottom, + SectionHeader, +} from '@graphcommerce/next-ui' import { i18n } from '@lingui/core' import { Trans } from '@lingui/react' import { Container } from '@mui/material' import { useRouter } from 'next/router' import type { LayoutOverlayProps } from '../components' -import { LayoutOverlay } from '../components' +import { LayoutDocument, LayoutOverlay } from '../components' import { graphqlSharedClient, graphqlSsrClient } from '../lib/graphql/graphqlSsrClient' -type RouteProps = { country?: string[] } +type RouteProps = Record type Props = StoreSwitcherListQuery type GetPageStaticProps = GetStaticProps function StoresIndexPage({ availableStores }: Props) { - const { locale } = useRouter() + const prev = usePrevPageRouter() + const router = useRouter() return ( - <> + { + await router.push(prev?.asPath ?? '/', undefined, { + locale: storeToLocale(data.storeCode), + scroll: false, + }) + }} + > - + + + + } + > - + - + ({ mb: theme.spacings.lg })}> - + - + } + showStores={1} + showCurrencies={1} + /> + } + showCurrencies={1} + /> + } /> + + + + + - + ) } const pageOptions: PageOptions = { overlayGroup: 'left', Layout: LayoutOverlay, - layoutProps: { variantMd: 'left' }, + layoutProps: { variantMd: 'right', sizeMd: 'floating', justifyMd: 'start' }, } + StoresIndexPage.pageOptions = pageOptions export default StoresIndexPage @@ -53,13 +96,14 @@ export default StoresIndexPage export const getStaticProps: GetPageStaticProps = async (context) => { const client = graphqlSharedClient(context) const staticClient = graphqlSsrClient(context) - const conf = client.query({ query: StoreConfigDocument }) + const layout = staticClient.query({ query: LayoutDocument }) const stores = staticClient.query({ query: StoreSwitcherListDocument }) return { props: { ...(await stores).data, + ...(await layout).data, apolloState: await conf.then(() => client.cache.extract()), }, } diff --git a/packages/graphql-mesh/index.ts b/packages/graphql-mesh/index.ts index 2b36f9aa37..7b3fe6116a 100644 --- a/packages/graphql-mesh/index.ts +++ b/packages/graphql-mesh/index.ts @@ -5,3 +5,4 @@ export * from './.mesh' // @ts-expect-error getBuiltMesh and createBuiltMeshHTTPHandler are re-exported here and override the export from .mesh export * from './api/globalThisMesh' export * from './utils/traverseSelectionSet' +export * from './utils/storefrontFromContext' diff --git a/packages/graphql-mesh/utils/storefrontFromContext.ts b/packages/graphql-mesh/utils/storefrontFromContext.ts new file mode 100644 index 0000000000..a1c8ed57c1 --- /dev/null +++ b/packages/graphql-mesh/utils/storefrontFromContext.ts @@ -0,0 +1,10 @@ +import type { GraphCommerceStorefrontConfig } from '@graphcommerce/next-config' +import type { MeshContext } from '@graphql-mesh/runtime' + +export function storefrontFromContext( + context: MeshContext & { headers?: Record }, +): GraphCommerceStorefrontConfig | undefined { + const storefrontAll = import.meta.graphCommerce.storefront + const store = context.headers?.store + return storefrontAll.find((s) => s.magentoStoreCode === store) +} diff --git a/packages/graphql/components/GraphQLProvider/persistenceMapper.ts b/packages/graphql/components/GraphQLProvider/persistenceMapper.ts index a02ccd4d98..d3cea10683 100644 --- a/packages/graphql/components/GraphQLProvider/persistenceMapper.ts +++ b/packages/graphql/components/GraphQLProvider/persistenceMapper.ts @@ -33,7 +33,9 @@ export const persistenceMapper = (data: string): Promise => { 'ROOT_QUERY.countries', 'ROOT_QUERY.checkoutAgreements', 'ROOT_QUERY.storeConfig', + 'ROOT_QUERY.currency', 'ROOT_QUERY.guestOrder', + 'ROOT_QUERY.cmsBlocks', 'ROOT_QUERY.__type*', '*Product:{"uid":"*"}.crosssell_products', 'ROOT_QUERY.recaptchaV3Config', diff --git a/packages/magento-store/README.md b/packages/magento-store/README.md new file mode 100644 index 0000000000..b2dc970051 --- /dev/null +++ b/packages/magento-store/README.md @@ -0,0 +1,8 @@ +# @graphcommerce/magento-store + +## Store Switcher + +![](./docs/store-switcher-single-group-multiple-stores-single-currency.png) +![](./docs/store-switcher-single-group-multiple-stores-multiple-currencies.png) +![](./docs/store-switcher-multiple-groups-one-store-single-currency.png) +![](./docs/store-switcher-multiple-groups-one-store-multiple-currency.png) diff --git a/packages/magento-store/StoreConfig.graphql b/packages/magento-store/StoreConfig.graphql deleted file mode 100644 index 5157adf8ae..0000000000 --- a/packages/magento-store/StoreConfig.graphql +++ /dev/null @@ -1,5 +0,0 @@ -query StoreConfig { - storeConfig { - ...StoreConfigFragment - } -} diff --git a/packages/magento-store/components/CurrencySymbol/CurrencySymbol.tsx b/packages/magento-store/components/CurrencySymbol/CurrencySymbol.tsx index 009581be1e..b7aa33dac0 100644 --- a/packages/magento-store/components/CurrencySymbol/CurrencySymbol.tsx +++ b/packages/magento-store/components/CurrencySymbol/CurrencySymbol.tsx @@ -3,10 +3,11 @@ import { CurrencySymbol as CurrencySymbolBase, type CurrencySymbolProps, } from '@graphcommerce/next-ui' -import { StoreConfigDocument } from '../../StoreConfig.gql' +import { StoreConfigDocument } from '../../queries/StoreConfig.gql' export function CurrencySymbol(props: CurrencySymbolProps) { + const { currency } = props const baseCurrencyCode = useQuery(StoreConfigDocument).data?.storeConfig?.base_currency_code ?? '' - return + return } diff --git a/packages/magento-store/components/GlobalHead/GlobalHead.tsx b/packages/magento-store/components/GlobalHead/GlobalHead.tsx index de5763483f..26245115cb 100644 --- a/packages/magento-store/components/GlobalHead/GlobalHead.tsx +++ b/packages/magento-store/components/GlobalHead/GlobalHead.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@graphcommerce/graphql' import type { GlobalHeadProps as GlobalHeadPropsBase } from '@graphcommerce/next-ui' import { GlobalHead as GlobalHeadBase } from '@graphcommerce/next-ui' -import { StoreConfigDocument } from '../../StoreConfig.gql' +import { StoreConfigDocument } from '../../queries/StoreConfig.gql' export type GlobalHeadProps = Omit diff --git a/packages/magento-store/Money.graphql b/packages/magento-store/components/Money/Money.graphql similarity index 100% rename from packages/magento-store/Money.graphql rename to packages/magento-store/components/Money/Money.graphql diff --git a/packages/magento-store/Money.tsx b/packages/magento-store/components/Money/Money.tsx similarity index 94% rename from packages/magento-store/Money.tsx rename to packages/magento-store/components/Money/Money.tsx index 5f040a6f89..e2644edc7a 100644 --- a/packages/magento-store/Money.tsx +++ b/packages/magento-store/components/Money/Money.tsx @@ -2,8 +2,8 @@ import { useQuery } from '@graphcommerce/graphql' import type { CurrencyFormatProps } from '@graphcommerce/next-ui' import { CurrencyFormat } from '@graphcommerce/next-ui' import type { SxProps, Theme } from '@mui/material' +import { StoreConfigDocument } from '../../queries/StoreConfig.gql' import type { MoneyFragment } from './Money.gql' -import { StoreConfigDocument } from './StoreConfig.gql' type OverridableProps = { round?: boolean diff --git a/packages/magento-store/PageMeta.tsx b/packages/magento-store/components/PageMeta/PageMeta.tsx similarity index 93% rename from packages/magento-store/PageMeta.tsx rename to packages/magento-store/components/PageMeta/PageMeta.tsx index 4edf72fd87..16491272b2 100644 --- a/packages/magento-store/PageMeta.tsx +++ b/packages/magento-store/components/PageMeta/PageMeta.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@graphcommerce/graphql' import type { PageMetaProps as NextPageMetaProps } from '@graphcommerce/next-ui' import { PageMeta as NextPageMeta } from '@graphcommerce/next-ui' -import { StoreConfigDocument } from './StoreConfig.gql' +import { StoreConfigDocument } from '../../queries/StoreConfig.gql' export type PageMetaProps = Omit & { canonical?: string diff --git a/packages/magento-store/components/StoreSwitcher/StoreSwitcherApply.tsx b/packages/magento-store/components/StoreSwitcher/StoreSwitcherApply.tsx new file mode 100644 index 0000000000..736d7ed3de --- /dev/null +++ b/packages/magento-store/components/StoreSwitcher/StoreSwitcherApply.tsx @@ -0,0 +1,16 @@ +import { useFormState } from '@graphcommerce/ecommerce-ui' +import type { ButtonProps, LinkOrButtonProps } from '@graphcommerce/next-ui' +import { Button, LinkOrButton } from '@graphcommerce/next-ui' +import { useStoreSwitcherForm } from './useStoreSwitcher' + +export function StoreSwitcherApplyButton(props: ButtonProps<'button'>) { + const { control } = useStoreSwitcherForm() + const formState = useFormState({ control }) + return ) } diff --git a/packages/magento-store/components/StoreSwitcherList/StoreSwitcherList.graphql b/packages/magento-store/components/StoreSwitcherList/StoreSwitcherList.graphql deleted file mode 100644 index 6c5ef39d2d..0000000000 --- a/packages/magento-store/components/StoreSwitcherList/StoreSwitcherList.graphql +++ /dev/null @@ -1,10 +0,0 @@ -query StoreSwitcherList { - availableStores { - store_name - store_code - locale - base_currency_code - store_group_name - store_group_code - } -} diff --git a/packages/magento-store/docs/store-switcher-multiple-groups-one-store-multiple-currency.png b/packages/magento-store/docs/store-switcher-multiple-groups-one-store-multiple-currency.png new file mode 100644 index 0000000000..d5d250aee2 Binary files /dev/null and b/packages/magento-store/docs/store-switcher-multiple-groups-one-store-multiple-currency.png differ diff --git a/packages/magento-store/docs/store-switcher-multiple-groups-one-store-single-currency.png b/packages/magento-store/docs/store-switcher-multiple-groups-one-store-single-currency.png new file mode 100644 index 0000000000..16e58328c0 Binary files /dev/null and b/packages/magento-store/docs/store-switcher-multiple-groups-one-store-single-currency.png differ diff --git a/packages/magento-store/docs/store-switcher-single-group-multiple-stores-multiple-currencies.png b/packages/magento-store/docs/store-switcher-single-group-multiple-stores-multiple-currencies.png new file mode 100644 index 0000000000..cc83ad4101 Binary files /dev/null and b/packages/magento-store/docs/store-switcher-single-group-multiple-stores-multiple-currencies.png differ diff --git a/packages/magento-store/docs/store-switcher-single-group-multiple-stores-single-currency.png b/packages/magento-store/docs/store-switcher-single-group-multiple-stores-single-currency.png new file mode 100644 index 0000000000..0b64c66c1a Binary files /dev/null and b/packages/magento-store/docs/store-switcher-single-group-multiple-stores-single-currency.png differ diff --git a/packages/magento-store/index.ts b/packages/magento-store/index.ts index aaa4c20d3f..256dfe17e2 100644 --- a/packages/magento-store/index.ts +++ b/packages/magento-store/index.ts @@ -1,14 +1,13 @@ +export * from './components/CurrencySymbol/CurrencySymbol' export * from './components/GlobalHead/GlobalHead' +export * from './components/Money/Money' +export * from './components/Money/Money.gql' +export * from './components/PageMeta/PageMeta' +export * from './components/StoreSwitcherButton/StoreSwitcherButton' export * from './hooks/useFindCountry' export * from './hooks/useFindRegion' -export * from './localeToStore' -export * from './Money' -export * from './Money.gql' -export * from './PageMeta' export * from './queries/CountryRegions.gql' -export * from './StoreConfig.gql' -export * from './components/StoreSwitcherButton/StoreSwitcherButton' -export * from './components/StoreSwitcherList/StoreSwitcherList' -export * from './components/StoreSwitcherList/StoreSwitcherList.gql' +export * from './queries/StoreConfig.gql' +export * from './utils/localeToStore' export * from './utils/redirectOrNotFound' -export * from './components/CurrencySymbol/CurrencySymbol' +export * from './components/StoreSwitcher' diff --git a/packages/magento-store/mesh/resolvers.ts b/packages/magento-store/mesh/resolvers.ts new file mode 100644 index 0000000000..425e4e8871 --- /dev/null +++ b/packages/magento-store/mesh/resolvers.ts @@ -0,0 +1,14 @@ +import type { Resolvers } from '@graphcommerce/graphql-mesh' + +export const resolvers: Resolvers = { + StoreConfig: { + currency: { + selectionSet: '{ store_code }', + resolve: (parent, args, ctx, info) => { + if (!parent.store_code) return null + const context = { ...ctx, headers: { store: parent.store_code } } + return context.m2.Query.currency({ context, info }) ?? null + }, + }, + }, +} diff --git a/packages/magento-store/package.json b/packages/magento-store/package.json index 818d5647cc..eb2503a75b 100644 --- a/packages/magento-store/package.json +++ b/packages/magento-store/package.json @@ -12,7 +12,9 @@ } }, "peerDependencies": { + "@graphcommerce/ecommerce-ui": "^9.1.0-canary.16", "@graphcommerce/eslint-config-pwa": "^9.1.0-canary.16", + "@graphcommerce/framer-utils": "^9.1.0-canary.16", "@graphcommerce/graphql": "^9.1.0-canary.16", "@graphcommerce/graphql-mesh": "^9.1.0-canary.16", "@graphcommerce/image": "^9.1.0-canary.16", diff --git a/packages/magento-store/plugins/magentoCurrencyCode.ts b/packages/magento-store/plugins/magentoCurrencyCode.ts new file mode 100644 index 0000000000..266d2763a5 --- /dev/null +++ b/packages/magento-store/plugins/magentoCurrencyCode.ts @@ -0,0 +1,35 @@ +import { + type getPrivateQueryContext as getPrivateQueryContextType, + type usePrivateQueryContext as usePrivateQueryContextType, +} from '@graphcommerce/graphql' +import type { PrivateContext } from '@graphcommerce/graphql-mesh' +import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config' +import { cookie } from '@graphcommerce/next-ui' + +export const config: PluginConfig = { + type: 'function', + module: '@graphcommerce/graphql', +} + +export const getPrivateQueryContext: FunctionPlugin = ( + prev, + client, + ...args +) => { + const currencyCode = cookie('Magento-Content-Currency') + + const res = prev(client, ...args) + if (!currencyCode) return res + return { ...res, currencyCode } satisfies PrivateContext +} + +export const usePrivateQueryContext: FunctionPlugin = ( + prev, + ...args +) => { + const currencyCode = cookie('Magento-Content-Currency') + + const res = prev(...args) + if (!currencyCode) return res + return { ...res, currencyCode } +} diff --git a/packages/magento-store/plugins/magentoStoreGraphqlConfig.ts b/packages/magento-store/plugins/magentoStoreGraphqlConfig.ts index 271b7cc2bf..cf5258b272 100644 --- a/packages/magento-store/plugins/magentoStoreGraphqlConfig.ts +++ b/packages/magento-store/plugins/magentoStoreGraphqlConfig.ts @@ -1,5 +1,6 @@ import { setContext, type graphqlConfig as graphqlConfigType } from '@graphcommerce/graphql' import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config' +import { cookie } from '@graphcommerce/next-ui' export const config: PluginConfig = { type: 'function', @@ -15,7 +16,13 @@ export const graphqlConfig: FunctionPlugin = (prev, co ...results.links, setContext((_, context) => { if (!context.headers) context.headers = {} - context.headers.store = conf.storefront.magentoStoreCode + if (!context.headers.store) { + context.headers.store = conf.storefront.magentoStoreCode + } + + const contentCurrency = cookie('Magento-Content-Currency') + if (contentCurrency && typeof context.headers['content-currency'] === 'undefined') + context.headers['content-currency'] = contentCurrency if (conf.preview) { // To disable caching from the backend, we provide a bogus cache ID. diff --git a/packages/magento-store/plugins/meshMagentoStore.ts b/packages/magento-store/plugins/meshMagentoStore.ts new file mode 100644 index 0000000000..cc2e463552 --- /dev/null +++ b/packages/magento-store/plugins/meshMagentoStore.ts @@ -0,0 +1,23 @@ +import type { meshConfig as meshConfigBase } from '@graphcommerce/graphql-mesh/meshConfig' +import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config' + +export const config: PluginConfig = { + module: '@graphcommerce/graphql-mesh/meshConfig', + type: 'function', +} + +export const meshConfig: FunctionPlugin = ( + prev, + baseConfig, + graphCommerceConfig, +) => + prev( + { + ...baseConfig, + additionalResolvers: [ + ...(baseConfig.additionalResolvers ?? []), + '@graphcommerce/magento-store/mesh/resolvers.ts', + ], + }, + graphCommerceConfig, + ) diff --git a/packages/magento-store/queries/StoreConfig.graphql b/packages/magento-store/queries/StoreConfig.graphql new file mode 100644 index 0000000000..c5a4b83ec4 --- /dev/null +++ b/packages/magento-store/queries/StoreConfig.graphql @@ -0,0 +1,3 @@ +query StoreConfig { + ...StoreConfigQueryFragment +} diff --git a/packages/magento-store/StoreConfigFragment.graphql b/packages/magento-store/queries/StoreConfigFragment.graphql similarity index 100% rename from packages/magento-store/StoreConfigFragment.graphql rename to packages/magento-store/queries/StoreConfigFragment.graphql diff --git a/packages/magento-store/queries/StoreConfigQueryFragment.graphql b/packages/magento-store/queries/StoreConfigQueryFragment.graphql new file mode 100644 index 0000000000..a956a6c395 --- /dev/null +++ b/packages/magento-store/queries/StoreConfigQueryFragment.graphql @@ -0,0 +1,6 @@ +fragment StoreConfigQueryFragment on Query { + __typename + storeConfig { + ...StoreConfigFragment + } +} diff --git a/packages/magento-store/schema/PrivateContext.graphqls b/packages/magento-store/schema/PrivateContext.graphqls new file mode 100644 index 0000000000..d9155e5357 --- /dev/null +++ b/packages/magento-store/schema/PrivateContext.graphqls @@ -0,0 +1,4 @@ +input PrivateContext { + storeCode: String + currencyCode: String +} diff --git a/packages/magento-store/schema/StoreConfig-currency.graphqls b/packages/magento-store/schema/StoreConfig-currency.graphqls new file mode 100644 index 0000000000..53cd37b3e6 --- /dev/null +++ b/packages/magento-store/schema/StoreConfig-currency.graphqls @@ -0,0 +1,3 @@ +extend type StoreConfig { + currency: Currency +} diff --git a/packages/magento-store/test/apolloClientStore.fixture.ts b/packages/magento-store/test/apolloClientStore.fixture.ts index 5b3a8a798a..3d045e6a1c 100644 --- a/packages/magento-store/test/apolloClientStore.fixture.ts +++ b/packages/magento-store/test/apolloClientStore.fixture.ts @@ -4,7 +4,7 @@ import type { NormalizedCacheObject } from '@graphcommerce/graphql' import { ApolloClient, InMemoryCache } from '@graphcommerce/graphql' import { test as base } from '@playwright/test' -import { localeToStore } from '../localeToStore' +import { localeToStore } from '../utils/localeToStore' type ApolloClientStoreTest = { apolloClient: ApolloClient diff --git a/packages/magento-store/localeToStore.ts b/packages/magento-store/utils/localeToStore.ts similarity index 100% rename from packages/magento-store/localeToStore.ts rename to packages/magento-store/utils/localeToStore.ts diff --git a/packages/magento-store/utils/redirectOrNotFound.ts b/packages/magento-store/utils/redirectOrNotFound.ts index 9863c0dfde..93b8ca8ff0 100644 --- a/packages/magento-store/utils/redirectOrNotFound.ts +++ b/packages/magento-store/utils/redirectOrNotFound.ts @@ -3,10 +3,10 @@ import type { ApolloClient, ApolloQueryResult, NormalizedCacheObject } from '@gr import { flushMeasurePerf } from '@graphcommerce/graphql' import { isTypename, nonNullable, storefrontConfig } from '@graphcommerce/next-ui' import type { Redirect } from 'next' -import { defaultLocale } from '../localeToStore' -import type { StoreConfigQuery } from '../StoreConfig.gql' +import type { StoreConfigQuery } from '../queries/StoreConfig.gql' import type { HandleRedirectQuery } from './HandleRedirect.gql' import { HandleRedirectDocument } from './HandleRedirect.gql' +import { defaultLocale } from './localeToStore' export type RedirectOr404Return = Promise< | { redirect: Redirect; revalidate?: number | boolean } diff --git a/packages/next-ui/ActionCard/ActionCard.tsx b/packages/next-ui/ActionCard/ActionCard.tsx index 4372bf4211..369fa8755e 100644 --- a/packages/next-ui/ActionCard/ActionCard.tsx +++ b/packages/next-ui/ActionCard/ActionCard.tsx @@ -186,9 +186,9 @@ export function ActionCard(props: Acti '&.variantOutlined': { backgroundColor: theme.palette.background.paper, boxShadow: `inset 0 0 0 1px ${theme.palette.divider}`, - '&:not(:last-of-type)': { - marginBottom: '-2px', - }, + // '&:not(:last-of-type)': { + // marginBottom: '-2px', + // }, '&.layoutList': { borderRadius: 0, '&:first-of-type': { @@ -296,6 +296,7 @@ export function ActionCard(props: Acti display: 'flex', flexDirection: 'column', alignItems: 'flex-start', + justifyContent: 'center', }} > {title && ( @@ -348,7 +349,7 @@ export function ActionCard(props: Acti )} {...slotProps.end} > - {action && ( + {(action || reset) && ( +export type FlagAvatarProps = { country: string; size: string } & Omit, 'src'> export function FlagAvatar(props: FlagAvatarProps) { - const { country, ...avatarProps } = props + const { country, size, sx, ...avatarProps } = props + + const displayName = useIntlDisplayNames({ type: 'region' }) return ( - - {country.toLocaleUpperCase()} - + src={`https://flagcdn.com/${country}.svg`} + alt={displayName.of(country)} + sx={sxx( + { + width: size, + borderRadius: '2px', + height: size, + }, + sx, + )} + /> ) } diff --git a/packages/next-ui/Intl/CurrencySymbol/CurrencySymbol.tsx b/packages/next-ui/Intl/CurrencySymbol/CurrencySymbol.tsx index 07e94b2017..e8e22220c1 100644 --- a/packages/next-ui/Intl/CurrencySymbol/CurrencySymbol.tsx +++ b/packages/next-ui/Intl/CurrencySymbol/CurrencySymbol.tsx @@ -1,5 +1,6 @@ import { Box, type SxProps, type Theme } from '@mui/material' import { forwardRef } from 'react' +import { DisplayNames } from '../DisplayNames' import { useIntlNumberFormat, type UseIntlNumberFormatOptions, @@ -7,16 +8,23 @@ import { export type CurrencySymbolProps = Omit & { sx?: SxProps + variant?: 'symbol' | 'full' } /** @public */ export const CurrencySymbol = forwardRef((props, ref) => { - const { sx, ...options } = props + const { sx, variant = 'symbol', ...options } = props const formatter = useIntlNumberFormat({ ...options, numberStyle: 'currency' }) return ( {formatter.formatToParts(1).find((part) => part.type === 'currency')?.value} + {options.currency && variant === 'full' && ( + <> + {' – '} + {options.currency} – + + )} ) }) diff --git a/packages/next-ui/Intl/ListFormat/useIntlListFormat.ts b/packages/next-ui/Intl/ListFormat/useIntlListFormat.ts index 5c8fb9398c..b9457c3378 100644 --- a/packages/next-ui/Intl/ListFormat/useIntlListFormat.ts +++ b/packages/next-ui/Intl/ListFormat/useIntlListFormat.ts @@ -9,7 +9,7 @@ export type UseIntlListFormatOptions = { UseIntlLocalesArgumentOptions export function useIntlListFormat(props: UseIntlListFormatOptions) { - const [locales, options] = useIntlLocalesArgument(props) - const memoOptions = useMemoObject({ ...options }) + const [locales, { listStyle: style, ...options }] = useIntlLocalesArgument(props) + const memoOptions = useMemoObject({ style, ...options }) return useMemo(() => new Intl.ListFormat(locales, memoOptions), [locales, memoOptions]) } diff --git a/packages/next-ui/hooks/useStorefrontConfig.ts b/packages/next-ui/hooks/useStorefrontConfig.ts index 4c1c1cc29a..3716096e3b 100644 --- a/packages/next-ui/hooks/useStorefrontConfig.ts +++ b/packages/next-ui/hooks/useStorefrontConfig.ts @@ -5,6 +5,6 @@ import { storefrontConfig } from '../utils/storefrontConfig' export function useStorefrontConfig(locale?: string | undefined) { const routerLocale = useRouter().locale const config = storefrontConfig(locale ?? routerLocale) - if (!config) throw Error(`No storefront config found for locale ${locale}`) + if (!config) throw Error(`No storefront config found for locale '${locale}'`) return config }