From 5e586d03ffdade156182cd13a3766b6a4fe63350 Mon Sep 17 00:00:00 2001 From: yuriassuncx Date: Wed, 10 Sep 2025 14:37:57 -0300 Subject: [PATCH 1/8] feat: internationalization support for shopify loaders and queries --- shopify/loaders/ProductDetailsPage.ts | 22 +- shopify/loaders/ProductList.ts | 26 +- shopify/loaders/ProductListingPage.ts | 36 ++- shopify/loaders/RelatedProducts.ts | 26 +- shopify/loaders/cart.ts | 34 +- shopify/loaders/proxy.ts | 21 +- shopify/loaders/shop.ts | 25 +- shopify/utils/storefront/queries.ts | 437 ++++++++++++++++---------- shopify/utils/transform.ts | 26 +- shopify/utils/types.ts | 7 +- 10 files changed, 442 insertions(+), 218 deletions(-) diff --git a/shopify/loaders/ProductDetailsPage.ts b/shopify/loaders/ProductDetailsPage.ts index 29c345c81..52028c0a9 100644 --- a/shopify/loaders/ProductDetailsPage.ts +++ b/shopify/loaders/ProductDetailsPage.ts @@ -6,9 +6,11 @@ import { GetProductQuery, GetProductQueryVariables, HasMetafieldsMetafieldsArgs, + LanguageCode, + CountryCode } from "../utils/storefront/storefront.graphql.gen.ts"; import { GetProduct } from "../utils/storefront/queries.ts"; -import { Metafield } from "../utils/types.ts"; +import { LanguageContextArgs, Metafield } from "../utils/types.ts"; export interface Props { slug: RequestURLParam; @@ -17,6 +19,18 @@ export interface Props { * @description search for metafields */ metafields?: Metafield[]; + /** + * @title Language Code + * @description Language code for the storefront API + * @example "EN" for English, "FR" for French, etc. + */ + languageCode?: LanguageCode; + /** + * @title Country Code + * @description Country code for the storefront API + * @example "US" for United States, "FR" for France, etc. + */ + countryCode?: CountryCode; } /** @@ -29,7 +43,7 @@ const loader = async ( ctx: AppContext, ): Promise => { const { storefront } = ctx; - const { slug } = props; + const { slug, languageCode = "PT", countryCode = "BR" } = props; const metafields = props.metafields || []; const splitted = slug?.split("-"); @@ -39,9 +53,9 @@ const loader = async ( const data = await storefront.query< GetProductQuery, - GetProductQueryVariables & HasMetafieldsMetafieldsArgs + GetProductQueryVariables & HasMetafieldsMetafieldsArgs & LanguageContextArgs >({ - variables: { handle, identifiers: metafields }, + variables: { handle, identifiers: metafields, languageCode, countryCode }, ...GetProduct, }); diff --git a/shopify/loaders/ProductList.ts b/shopify/loaders/ProductList.ts index 6fa9f405d..3c0bcd241 100644 --- a/shopify/loaders/ProductList.ts +++ b/shopify/loaders/ProductList.ts @@ -13,6 +13,8 @@ import { QueryRootCollectionArgs, QueryRootSearchArgs, SearchResultItemConnection, + LanguageCode, + CountryCode } from "../utils/storefront/storefront.graphql.gen.ts"; import { toProduct } from "../utils/transform.ts"; import { @@ -21,7 +23,7 @@ import { searchSortShopify, sortShopify, } from "../utils/utils.ts"; -import { Metafield } from "../utils/types.ts"; +import { LanguageContextArgs, Metafield } from "../utils/types.ts"; export interface QueryProps { /** @description search term to use on search */ @@ -70,6 +72,18 @@ export type Props = { * @description search for metafields */ metafields?: Metafield[]; + /** + * @title Language Code + * @description Language code for the storefront API + * @example "EN" for English, "FR" for French, etc. + */ + languageCode?: LanguageCode; + /** + * @title Country Code + * @description Country code for the storefront API + * @example "US" for United States, "FR" for France, etc. + */ + countryCode?: CountryCode; }; // deno-lint-ignore no-explicit-any @@ -92,6 +106,8 @@ const loader = async ( const count = props.count ?? 12; const metafields = expandedProps.metafields || []; + const languageCode = expandedProps?.languageCode ?? "PT"; + const countryCode = expandedProps?.countryCode ?? "BR"; let shopifyProducts: | SearchResultItemConnection @@ -119,15 +135,18 @@ const loader = async ( }); if (isQueryList(props)) { + const data = await storefront.query< QueryRoot, - QueryRootSearchArgs & HasMetafieldsMetafieldsArgs + QueryRootSearchArgs & HasMetafieldsMetafieldsArgs & LanguageContextArgs >({ variables: { first: count, query: props.query, productFilters: filters, identifiers: metafields, + languageCode, + countryCode, ...searchSortShopify[sort], }, ...SearchProducts, @@ -139,12 +158,15 @@ const loader = async ( & QueryRootCollectionArgs & CollectionProductsArgs & HasMetafieldsMetafieldsArgs + & LanguageContextArgs >({ variables: { first: count, handle: props.collection, filters, identifiers: metafields, + languageCode, + countryCode, ...sortShopify[sort], }, ...ProductsByCollection, diff --git a/shopify/loaders/ProductListingPage.ts b/shopify/loaders/ProductListingPage.ts index 4672b8efa..32c66885c 100644 --- a/shopify/loaders/ProductListingPage.ts +++ b/shopify/loaders/ProductListingPage.ts @@ -13,9 +13,11 @@ import { QueryRootCollectionArgs, QueryRootSearchArgs, SearchResultItemConnection, + LanguageCode, + CountryCode } from "../utils/storefront/storefront.graphql.gen.ts"; import { toFilter, toProduct } from "../utils/transform.ts"; -import { Metafield } from "../utils/types.ts"; +import { LanguageContextArgs, Metafield } from "../utils/types.ts"; import { getFiltersByUrl, searchSortOptions, @@ -69,6 +71,18 @@ export interface Props { * @description The URL of the page, used to override URL from request */ pageHref?: string; + /** + * @title Language Code + * @description Language code for the storefront API + * @example "EN" for English, "FR" for French, etc. + */ + languageCode?: LanguageCode; + /** + * @title Country Code + * @description Country code for the storefront API + * @example "US" for United States, "FR" for France, etc. + */ + countryCode?: CountryCode; } /** @@ -94,6 +108,8 @@ const loader = async ( const startCursor = props.startCursor || url.searchParams.get("startCursor") || ""; const metafields = props.metafields || []; + const languageCode = props?.languageCode || "PT"; + const countryCode = props?.countryCode || "BR"; const isSearch = Boolean(query); let hasNextPage = false; @@ -105,6 +121,7 @@ const loader = async ( | undefined = undefined; let shopifyFilters = undefined; let records = undefined; + let collectionId = undefined; let collectionTitle = undefined; let collectionDescription = undefined; @@ -113,7 +130,7 @@ const loader = async ( if (isSearch) { const data = await storefront.query< QueryRoot, - QueryRootSearchArgs & HasMetafieldsMetafieldsArgs + QueryRootSearchArgs & HasMetafieldsMetafieldsArgs & LanguageContextArgs >({ variables: { ...(!endCursor && { first: count }), @@ -123,6 +140,8 @@ const loader = async ( query: query, productFilters: getFiltersByUrl(url), identifiers: metafields, + languageCode, + countryCode, ...searchSortShopify[sort], }, ...SearchProducts, @@ -136,15 +155,16 @@ const loader = async ( data?.search?.pageInfo.hasPreviousPage ?? false, ); } else { - // TODO: understand how accept more than one path - // example: /collections/first-collection/second-collection - const pathname = props.collectionName || url.pathname.split("/")[1]; + // Support for multiple paths, such as /{lang}/collections/first-collection/second-collection + // Always takes the last non-empty segment as pathname + const pathname = props.collectionName || url.pathname.split("/").filter(Boolean).pop(); const data = await storefront.query< QueryRoot, & QueryRootCollectionArgs & CollectionProductsArgs & HasMetafieldsMetafieldsArgs + & LanguageContextArgs >({ variables: { ...(!endCursor && { first: count }), @@ -154,6 +174,8 @@ const loader = async ( identifiers: metafields, handle: pathname, filters: getFiltersByUrl(url), + languageCode, + countryCode, ...sortShopify[sort], }, ...ProductsByCollection, @@ -167,6 +189,7 @@ const loader = async ( hasPreviousPage = Boolean( data?.collection?.products.pageInfo.hasPreviousPage ?? false, ); + collectionId = data.collection?.id; collectionTitle = data.collection?.title; collectionDescription = data.collection?.description; } @@ -201,9 +224,10 @@ const loader = async ( // TODO: Update breadcrumb when accept more than one path breadcrumb: { "@type": "BreadcrumbList", + "@id": collectionId, itemListElement: [{ "@type": "ListItem" as const, - name: isSearch ? query : url.pathname.split("/")[1], + name: isSearch ? query : url.pathname.split("/").filter(Boolean).pop(), item: isSearch ? url.href : url.pathname, position: 2, }], diff --git a/shopify/loaders/RelatedProducts.ts b/shopify/loaders/RelatedProducts.ts index a1d8b0ece..a677f8038 100644 --- a/shopify/loaders/RelatedProducts.ts +++ b/shopify/loaders/RelatedProducts.ts @@ -11,9 +11,11 @@ import { HasMetafieldsMetafieldsArgs, ProductRecommendationsQuery, ProductRecommendationsQueryVariables, + LanguageCode, + CountryCode } from "../utils/storefront/storefront.graphql.gen.ts"; import { toProduct } from "../utils/transform.ts"; -import { Metafield } from "../utils/types.ts"; +import { LanguageContextArgs, Metafield } from "../utils/types.ts"; export interface Props { slug: RequestURLParam; @@ -27,6 +29,18 @@ export interface Props { * @description search for metafields */ metafields?: Metafield[]; + /** + * @title Language Code + * @description Language code for the storefront API + * @example "EN" for English, "FR" for French, etc. + */ + languageCode?: LanguageCode; + /** + * @title Country Code + * @description Country code for the storefront API + * @example "US" for United States, "FR" for France, etc. + */ + countryCode?: CountryCode; } /** @@ -39,7 +53,7 @@ const loader = async ( ctx: AppContext, ): Promise => { const { storefront } = ctx; - const { slug, count } = props; + const { slug, count, languageCode = "PT", countryCode = "BR" } = props; const splitted = slug?.split("-"); const maybeSkuId = Number(splitted[splitted.length - 1]); @@ -48,9 +62,9 @@ const loader = async ( const query = await storefront.query< GetProductQuery, - GetProductQueryVariables & HasMetafieldsMetafieldsArgs + GetProductQueryVariables & HasMetafieldsMetafieldsArgs & LanguageContextArgs >({ - variables: { handle, identifiers: metafields }, + variables: { handle, identifiers: metafields, languageCode, countryCode }, ...GetProduct, }); @@ -60,11 +74,13 @@ const loader = async ( const data = await storefront.query< ProductRecommendationsQuery, - ProductRecommendationsQueryVariables & HasMetafieldsMetafieldsArgs + ProductRecommendationsQueryVariables & HasMetafieldsMetafieldsArgs & LanguageContextArgs >({ variables: { productId: query.product.id, identifiers: metafields, + languageCode, + countryCode }, ...ProductRecommendations, }); diff --git a/shopify/loaders/cart.ts b/shopify/loaders/cart.ts index 960d3f7b1..8f198cb6c 100644 --- a/shopify/loaders/cart.ts +++ b/shopify/loaders/cart.ts @@ -2,31 +2,53 @@ import { AppContext } from "../mod.ts"; import { getCartCookie, setCartCookie } from "../utils/cart.ts"; import { CreateCart, GetCart } from "../utils/storefront/queries.ts"; import { + CountryCode, CreateCartMutation, CreateCartMutationVariables, GetCartQuery, GetCartQueryVariables, + LanguageCode, } from "../utils/storefront/storefront.graphql.gen.ts"; +import { LanguageContextArgs } from "../utils/types.ts"; + +export interface Props { + /** + * @title Language Code + * @description Language code for the storefront API + * @example "EN" for English, "FR" for French, etc. + */ + languageCode?: LanguageCode; + /** + * @title Country Code + * @description Country code for the storefront API + * @example "US" for United States, "FR" for France, etc. + */ + countryCode?: CountryCode; +} const loader = async ( - _props: unknown, + props: Props, req: Request, ctx: AppContext, ): Promise => { + const { languageCode = "PT", countryCode = "BR" } = props; const { storefront } = ctx; const maybeCartId = getCartCookie(req.headers); const cartId = maybeCartId || - await storefront.query( - CreateCart, - ).then((data) => data.payload?.cart?.id); + await storefront.query({ + ...CreateCart, + }).then((data) => data.payload?.cart?.id); if (!cartId) { throw new Error("Missing cart id"); } - const cart = await storefront.query({ - variables: { id: decodeURIComponent(cartId) }, + const cart = await storefront.query< + GetCartQuery, + GetCartQueryVariables & LanguageContextArgs + >({ + variables: { id: decodeURIComponent(cartId), languageCode, countryCode }, ...GetCart, }).then((data) => data.cart); diff --git a/shopify/loaders/proxy.ts b/shopify/loaders/proxy.ts index aee923767..3009ca3a8 100644 --- a/shopify/loaders/proxy.ts +++ b/shopify/loaders/proxy.ts @@ -29,17 +29,13 @@ const buildProxyRoutes = ( { ctx, ctx: { storeName, publicUrl }, - extraPaths, + extraPathsToProxy: extraPaths = [], includeSiteMap, generateDecoSiteMap, excludePathsFromDecoSiteMap, + excludePathsFromShopifySiteMap, replaces, - }: { - extraPaths: string[]; - includeSiteMap?: string[]; - generateDecoSiteMap?: boolean; - excludePathsFromDecoSiteMap: string[]; - replaces: TextReplace[]; + }: Props & { ctx: AppContext; }, ) => { @@ -64,7 +60,7 @@ const buildProxyRoutes = ( pathTemplate, handler: { value: { - __resolveType: "website/handlers/proxy.ts", + __resolveType: pathTemplate.includes("sitemap") ? "shopify/handlers/sitemap.ts" : "website/handlers/proxy.ts", url: urlToProxy, host: hostToUse, customHeaders: withDigestCookie(ctx), @@ -95,6 +91,7 @@ const buildProxyRoutes = ( handler: { value: { include, + exclude: excludePathsFromShopifySiteMap, __resolveType: "shopify/handlers/sitemap.ts", }, }, @@ -130,6 +127,10 @@ export interface Props { * @title Exclude paths from /deco-sitemap.xml */ excludePathsFromDecoSiteMap?: string[]; + /** + * @title Exclude paths from /shopify-sitemap.xml + */ + excludePathsFromShopifySiteMap?: string[]; replaces?: TextReplace[]; } @@ -142,6 +143,7 @@ function loader( includeSiteMap = [], generateDecoSiteMap = true, excludePathsFromDecoSiteMap = [], + excludePathsFromShopifySiteMap = [], replaces = [], }: Props, _req: Request, @@ -150,8 +152,9 @@ function loader( return buildProxyRoutes({ generateDecoSiteMap, excludePathsFromDecoSiteMap, + excludePathsFromShopifySiteMap, includeSiteMap, - extraPaths: extraPathsToProxy, + extraPathsToProxy, replaces, ctx, }); diff --git a/shopify/loaders/shop.ts b/shopify/loaders/shop.ts index 1498e0c85..22db0b638 100644 --- a/shopify/loaders/shop.ts +++ b/shopify/loaders/shop.ts @@ -1,10 +1,12 @@ import { AppContext } from "../mod.ts"; import { GetShopInfo } from "../utils/storefront/queries.ts"; import { + CountryCode, + LanguageCode, Shop, ShopMetafieldsArgs, } from "../utils/storefront/storefront.graphql.gen.ts"; -import { Metafield } from "../utils/types.ts"; +import { LanguageContextArgs, Metafield } from "../utils/types.ts"; export interface Props { /** @@ -12,6 +14,18 @@ export interface Props { * @description search for metafields */ metafields?: Metafield[]; + /** + * @title Language Code + * @description Language code for the storefront API + * @example "EN" for English, "FR" for French, etc. + */ + languageCode?: LanguageCode; + /** + * @title Country Code + * @description Country code for the storefront API + * @example "US" for United States, "FR" for France, etc. + */ + countryCode?: CountryCode; } export const defaultVisibility = "private"; @@ -22,10 +36,13 @@ const loader = async ( ctx: AppContext, ): Promise => { const { storefront } = ctx; - const { metafields = [] } = props; + const { metafields = [], languageCode = "PT", countryCode = "BR" } = props; - const shop = await storefront.query<{ shop: Shop }, ShopMetafieldsArgs>({ - variables: { identifiers: metafields }, + const shop = await storefront.query< + { shop: Shop }, + ShopMetafieldsArgs & LanguageContextArgs + >({ + variables: { identifiers: metafields, languageCode, countryCode }, ...GetShopInfo, }).then((data) => data.shop); diff --git a/shopify/utils/storefront/queries.ts b/shopify/utils/storefront/queries.ts index bd5e2bc3e..4ad018239 100644 --- a/shopify/utils/storefront/queries.ts +++ b/shopify/utils/storefront/queries.ts @@ -174,6 +174,10 @@ fragment Cart on Cart { id checkoutUrl totalQuantity + buyerIdentity { + countryCode + email + } lines(first: 100) { nodes { id @@ -266,112 +270,103 @@ const Customer = gql` `; export const CreateCart = { - query: gql`mutation CreateCart { - payload: cartCreate { - cart { id } + query: gql` + mutation CreateCart($countryCode: CountryCode) { + payload: cartCreate(input: { buyerIdentity: { countryCode: $countryCode } }) { + cart { + id + } + } } - }`, + `, }; export const GetCart = { fragments: [Cart], - query: gql`query GetCart($id: ID!) { cart(id: $id) { ...Cart } }`, + query: gql` + query GetCart( + $id: ID!, + $languageCode: LanguageCode, + $countryCode: CountryCode + ) @inContext(language: $languageCode, country: $countryCode) { + cart(id: $id) { + ...Cart + } + } + `, }; export const GetProduct = { fragments: [Product, ProductVariant, Collection], - query: - gql`query GetProduct($handle: String, $identifiers: [HasMetafieldsIdentifier!]!) { - product(handle: $handle) { ...Product } - }`, + query: gql` + query GetProduct( + $handle: String, + $identifiers: [HasMetafieldsIdentifier!]!, + $languageCode: LanguageCode, + $countryCode: CountryCode + ) @inContext(language: $languageCode, country: $countryCode) { + product(handle: $handle) { + ...Product + } + } + `, }; export const ListProducts = { fragments: [Product, ProductVariant, Collection], - query: - gql`query ListProducts($first: Int, $after: String, $query: String, $identifiers: [HasMetafieldsIdentifier!]!) { - products(first: $first, after: $after, query: $query) { - nodes { - ...Product + query: gql` + query ListProducts( + $first: Int, + $after: String, + $query: String, + $identifiers: [HasMetafieldsIdentifier!]!, + $languageCode: LanguageCode, + $countryCode: CountryCode + ) @inContext(language: $languageCode, country: $countryCode) { + products(first: $first, after: $after, query: $query) { + nodes { + ...Product + } } } - }`, + `, }; export const SearchProducts = { fragments: [Product, ProductVariant, Filter, Collection], - query: gql`query searchWithFilters( - $first: Int, - $last: Int, - $after: String, - $before: String, - $query: String!, - $productFilters: [ProductFilter!] - $sortKey: SearchSortKeys, + query: gql` + query searchWithFilters( + $first: Int, + $last: Int, + $after: String, + $before: String, + $query: String!, + $productFilters: [ProductFilter!], + $sortKey: SearchSortKeys, $reverse: Boolean, - $identifiers: [HasMetafieldsIdentifier!]! - ){ - search( - first: $first, - last: $last, - after: $after, - before: $before, - query: $query, - productFilters: $productFilters, - types: PRODUCT, - sortKey: $sortKey, - reverse: $reverse, - ){ - totalCount - pageInfo { - hasNextPage - hasPreviousPage - endCursor - startCursor - } - productFilters { - ...Filter - } - nodes { - ...Product - } - } - }`, -}; - -export const ProductsByCollection = { - fragments: [Product, ProductVariant, Collection, Filter], - query: gql`query AllProducts( - $first: Int, - $last: Int, - $after: String, - $before: String, - $handle: String, - $sortKey: ProductCollectionSortKeys, - $reverse: Boolean, - $filters: [ProductFilter!], - $identifiers: [HasMetafieldsIdentifier!]! - ){ - collection(handle: $handle) { - handle - description - title - products( - first: $first, - last: $last, - after: $after, - before: $before, - sortKey: $sortKey, - reverse: $reverse, - filters: $filters - ){ + $identifiers: [HasMetafieldsIdentifier!]!, + $languageCode: LanguageCode, + $countryCode: CountryCode + ) @inContext(language: $languageCode, country: $countryCode) { + search( + first: $first, + last: $last, + after: $after, + before: $before, + query: $query, + productFilters: $productFilters, + types: PRODUCT, + sortKey: $sortKey, + reverse: $reverse + ) { + totalCount pageInfo { hasNextPage hasPreviousPage endCursor startCursor } - filters { + productFilters { ...Filter } nodes { @@ -379,63 +374,123 @@ export const ProductsByCollection = { } } } - }`, + `, +}; + +export const ProductsByCollection = { + fragments: [Product, ProductVariant, Collection, Filter], + query: gql` + query AllProducts( + $first: Int, + $last: Int, + $after: String, + $before: String, + $handle: String, + $sortKey: ProductCollectionSortKeys, + $reverse: Boolean, + $filters: [ProductFilter!], + $identifiers: [HasMetafieldsIdentifier!]!, + $languageCode: LanguageCode, + $countryCode: CountryCode + ) @inContext(language: $languageCode, country: $countryCode) { + collection(handle: $handle) { + id + handle + description + title + products( + first: $first, + last: $last, + after: $after, + before: $before, + sortKey: $sortKey, + reverse: $reverse, + filters: $filters + ) { + pageInfo { + hasNextPage + hasPreviousPage + endCursor + startCursor + } + filters { + ...Filter + } + nodes { + ...Product + } + } + } + } + `, }; export const ProductRecommendations = { fragments: [Product, ProductVariant, Collection], - query: - gql`query productRecommendations($productId: ID!, $identifiers: [HasMetafieldsIdentifier!]!) { - productRecommendations(productId: $productId) { - ...Product + query: gql` + query ProductRecommendations( + $productId: ID!, + $identifiers: [HasMetafieldsIdentifier!]!, + $languageCode: LanguageCode, + $countryCode: CountryCode + ) @inContext(language: $languageCode, country: $countryCode) { + productRecommendations(productId: $productId) { + ...Product + } } - }`, + `, }; export const GetShopInfo = { - query: gql`query GetShopInfo($identifiers: [HasMetafieldsIdentifier!]!) { - shop { - name - description - privacyPolicy { - title - body - } - refundPolicy { - title - body - } - shippingPolicy { - title - body - } - subscriptionPolicy { - title - body - } - termsOfService { - title - body - } - metafields(identifiers: $identifiers) { + query: gql` + query GetShopInfo( + $identifiers: [HasMetafieldsIdentifier!]!, + $languageCode: LanguageCode, + $countryCode: CountryCode + ) @inContext(language: $languageCode, country: $countryCode) { + shop { + name description - key - namespace - type - value - reference { - ... on MediaImage { - image { - url + privacyPolicy { + title + body + } + refundPolicy { + title + body + } + shippingPolicy { + title + body + } + subscriptionPolicy { + title + body + } + termsOfService { + title + body + } + metafields(identifiers: $identifiers) { + description + key + namespace + type + value + reference { + ... on MediaImage { + image { + url + } } } - } - references(first: 250) { - edges { - node { - ... on MediaImage { - image { - url + references(first: 250) { + edges { + node { + ... on MediaImage { + image { + url + } } } } @@ -443,88 +498,126 @@ export const GetShopInfo = { } } } - }`, + `, }; export const FetchCustomerInfo = { fragments: [Customer], - query: gql`query FetchCustomerInfo($customerAccessToken: String!) { - customer(customerAccessToken: $customerAccessToken) { - ...Customer + query: gql` + query FetchCustomerInfo($customerAccessToken: String!) { + customer(customerAccessToken: $customerAccessToken) { + ...Customer + } } - }`, + `, }; export const AddItemToCart = { fragments: [Cart], - query: gql`mutation AddItemToCart($cartId: ID!, $lines: [CartLineInput!]!) { - payload: cartLinesAdd(cartId: $cartId, lines: $lines) { - cart { ...Cart } + query: gql` + mutation AddItemToCart($cartId: ID!, $lines: [CartLineInput!]!) { + payload: cartLinesAdd(cartId: $cartId, lines: $lines) { + cart { + ...Cart + } + } } - }`, + `, }; export const RegisterAccount = { - query: gql`mutation RegisterAccount( + query: gql` + mutation RegisterAccount( $email: String!, $password: String!, $firstName: String, $lastName: String, $acceptsMarketing: Boolean = false ) { - customerCreate(input: { - email: $email, - password: $password, - firstName: $firstName, - lastName: $lastName, - acceptsMarketing: $acceptsMarketing, - }) { - customer { - id - } - customerUserErrors { - code - message + customerCreate( + input: { + email: $email, + password: $password, + firstName: $firstName, + lastName: $lastName, + acceptsMarketing: $acceptsMarketing + } + ) { + customer { + id + } + customerUserErrors { + code + message + } } } - }`, + `, }; export const AddCoupon = { fragments: [Cart], - query: gql`mutation AddCoupon($cartId: ID!, $discountCodes: [String!]!) { - payload: cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) { - cart { ...Cart } - userErrors { - field - message + query: gql` + mutation AddCoupon($cartId: ID!, $discountCodes: [String!]!) { + payload: cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) { + cart { + ...Cart + } + userErrors { + field + message + } } } - }`, + `, }; export const UpdateItems = { fragments: [Cart], - query: - gql`mutation UpdateItems($cartId: ID!, $lines: [CartLineUpdateInput!]!) { + query: gql` + mutation UpdateItems($cartId: ID!, $lines: [CartLineUpdateInput!]!) { payload: cartLinesUpdate(cartId: $cartId, lines: $lines) { - cart { ...Cart } + cart { + ...Cart + } } - }`, + } + `, }; -export const SignInWithEmailAndPassword = { - query: - gql`mutation SignInWithEmailAndPassword($email: String!, $password: String!) { - customerAccessTokenCreate(input: { email: $email, password: $password }) { - customerAccessToken { - accessToken - expiresAt +export const CartBuyerIdentityUpdate = { + fragments: [Cart], + query: gql` + mutation CartBuyerIdentityUpdate( + $cartId: ID!, + $buyerIdentity: CartBuyerIdentityInput! + ) { + cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) { + cart { + ...Cart + } + userErrors { + field + message + } } - customerUserErrors { - code - message + } + `, +} + +export const SignInWithEmailAndPassword = { + query: gql` + mutation SignInWithEmailAndPassword($email: String!, $password: String!) { + customerAccessTokenCreate(input: { email: $email, password: $password }) { + customerAccessToken { + accessToken + expiresAt + } + customerUserErrors { + code + message + } } } - }`, + `, }; diff --git a/shopify/utils/transform.ts b/shopify/utils/transform.ts index b5f080c52..680eaa387 100644 --- a/shopify/utils/transform.ts +++ b/shopify/utils/transform.ts @@ -161,17 +161,25 @@ export const toProduct = ( .filter((metafield) => metafield && metafield.key && metafield.value) .map((metafield): PropertyValue => { const { key, value, reference, references } = metafield || {}; - const hasReferenceImage = reference && "image" in reference; - const referenceImageUrl = hasReferenceImage ? reference.image?.url : null; - - const hasEdges = references?.edges && references.edges.length > 0; - const edgeImages = hasEdges - ? references.edges.map((edge) => - edge.node && "image" in edge.node ? edge.node.image?.url : null - ) + + const referenceImageUrl = + (reference as { image?: { url?: string } } | undefined)?.image?.url ?? null; + + const edgeImages = Array.isArray(references?.edges) + ? references.edges.map( + (edge) => + (edge?.node as { image?: { url?: string } } | undefined)?.image + ?.url ?? null + ) : null; - const valueToReturn = referenceImageUrl || edgeImages || value; + const validEdgeImages = edgeImages?.filter((url) => !!url) as string[] | undefined; + + const valueToReturn = + referenceImageUrl ?? + (validEdgeImages && validEdgeImages.length > 0 + ? validEdgeImages.join(",") + : value); return { "@type": "PropertyValue", diff --git a/shopify/utils/types.ts b/shopify/utils/types.ts index cf02cad48..c7e9f7b9e 100644 --- a/shopify/utils/types.ts +++ b/shopify/utils/types.ts @@ -1,10 +1,10 @@ import { - CountryCode, CurrencyCode, OrderCancelReason, OrderFinancialStatus, OrderFulfillmentStatus, } from "./enums.ts"; +import { LanguageCode, CountryCode } from "./storefront/storefront.graphql.gen.ts"; type Attribute = { key: string; @@ -189,3 +189,8 @@ export interface Metafield { namespace: string; key: string; } + +export interface LanguageContextArgs { + languageCode: LanguageCode; + countryCode: CountryCode; +} From 5cc3c6d9624b4634ef1c5c34600020f261af40de Mon Sep 17 00:00:00 2001 From: yuriassuncx Date: Wed, 10 Sep 2025 14:46:28 -0300 Subject: [PATCH 2/8] chore: files generated --- .../storefront/storefront.graphql.gen.ts | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/shopify/utils/storefront/storefront.graphql.gen.ts b/shopify/utils/storefront/storefront.graphql.gen.ts index 552e29680..74fb686c7 100644 --- a/shopify/utils/storefront/storefront.graphql.gen.ts +++ b/shopify/utils/storefront/storefront.graphql.gen.ts @@ -7722,25 +7722,31 @@ export type ProductFragment = { availableForSale: boolean, createdAt: any, descr export type FilterFragment = { id: string, label: string, type: FilterType, values: Array<{ count: number, id: string, input: any, label: string }> }; -export type CartFragment = { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> }; +export type CartFragment = { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> }; export type CustomerFragment = { id: string, email?: string | null, firstName?: string | null, lastName?: string | null }; -export type CreateCartMutationVariables = Exact<{ [key: string]: never; }>; +export type CreateCartMutationVariables = Exact<{ + countryCode?: InputMaybe; +}>; export type CreateCartMutation = { payload?: { cart?: { id: string } | null } | null }; export type GetCartQueryVariables = Exact<{ id: Scalars['ID']['input']; + languageCode?: InputMaybe; + countryCode?: InputMaybe; }>; -export type GetCartQuery = { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null }; +export type GetCartQuery = { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null }; export type GetProductQueryVariables = Exact<{ handle?: InputMaybe; identifiers: Array | HasMetafieldsIdentifier; + languageCode?: InputMaybe; + countryCode?: InputMaybe; }>; @@ -7751,6 +7757,8 @@ export type ListProductsQueryVariables = Exact<{ after?: InputMaybe; query?: InputMaybe; identifiers: Array | HasMetafieldsIdentifier; + languageCode?: InputMaybe; + countryCode?: InputMaybe; }>; @@ -7766,6 +7774,8 @@ export type SearchWithFiltersQueryVariables = Exact<{ sortKey?: InputMaybe; reverse?: InputMaybe; identifiers: Array | HasMetafieldsIdentifier; + languageCode?: InputMaybe; + countryCode?: InputMaybe; }>; @@ -7781,14 +7791,18 @@ export type AllProductsQueryVariables = Exact<{ reverse?: InputMaybe; filters?: InputMaybe | ProductFilter>; identifiers: Array | HasMetafieldsIdentifier; + languageCode?: InputMaybe; + countryCode?: InputMaybe; }>; -export type AllProductsQuery = { collection?: { handle: string, description: string, title: string, products: { pageInfo: { hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null, startCursor?: string | null }, filters: Array<{ id: string, label: string, type: FilterType, values: Array<{ count: number, id: string, input: any, label: string }> }>, nodes: Array<{ availableForSale: boolean, createdAt: any, description: string, descriptionHtml: any, handle: string, id: string, isGiftCard: boolean, onlineStoreUrl?: any | null, productType: string, publishedAt: any, requiresSellingPlan: boolean, tags: Array, title: string, totalInventory?: number | null, updatedAt: any, vendor: string, featuredImage?: { altText?: string | null, url: any } | null, images: { nodes: Array<{ altText?: string | null, url: any }> }, media: { nodes: Array<{ alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, sources: Array<{ url: string }>, previewImage?: { altText?: string | null, url: any } | null }> }, options: Array<{ name: string, values: Array }>, priceRange: { minVariantPrice: { amount: any, currencyCode: CurrencyCode }, maxVariantPrice: { amount: any, currencyCode: CurrencyCode } }, seo: { title?: string | null, description?: string | null }, variants: { nodes: Array<{ availableForSale: boolean, barcode?: string | null, currentlyNotInStock: boolean, id: string, quantityAvailable?: number | null, requiresShipping: boolean, sku?: string | null, title: string, weight?: number | null, weightUnit: WeightUnit, compareAtPrice?: { amount: any, currencyCode: CurrencyCode } | null, image?: { altText?: string | null, url: any } | null, price: { amount: any, currencyCode: CurrencyCode }, selectedOptions: Array<{ name: string, value: string }>, unitPrice?: { amount: any, currencyCode: CurrencyCode } | null, unitPriceMeasurement?: { measuredType?: UnitPriceMeasurementMeasuredType | null, quantityValue: number, referenceUnit?: UnitPriceMeasurementMeasuredUnit | null, quantityUnit?: UnitPriceMeasurementMeasuredUnit | null } | null }> }, collections: { nodes: Array<{ description: string, descriptionHtml: any, handle: string, id: string, title: string, updatedAt: any, image?: { altText?: string | null, url: any } | null }> }, metafields: Array<{ description?: string | null, key: string, namespace: string, type: string, value: string, reference?: { image?: { url: any } | null } | {} | null, references?: { edges: Array<{ node: { image?: { url: any } | null } | {} }> } | null } | null> }> } } | null }; +export type AllProductsQuery = { collection?: { id: string, handle: string, description: string, title: string, products: { pageInfo: { hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null, startCursor?: string | null }, filters: Array<{ id: string, label: string, type: FilterType, values: Array<{ count: number, id: string, input: any, label: string }> }>, nodes: Array<{ availableForSale: boolean, createdAt: any, description: string, descriptionHtml: any, handle: string, id: string, isGiftCard: boolean, onlineStoreUrl?: any | null, productType: string, publishedAt: any, requiresSellingPlan: boolean, tags: Array, title: string, totalInventory?: number | null, updatedAt: any, vendor: string, featuredImage?: { altText?: string | null, url: any } | null, images: { nodes: Array<{ altText?: string | null, url: any }> }, media: { nodes: Array<{ alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, sources: Array<{ url: string }>, previewImage?: { altText?: string | null, url: any } | null }> }, options: Array<{ name: string, values: Array }>, priceRange: { minVariantPrice: { amount: any, currencyCode: CurrencyCode }, maxVariantPrice: { amount: any, currencyCode: CurrencyCode } }, seo: { title?: string | null, description?: string | null }, variants: { nodes: Array<{ availableForSale: boolean, barcode?: string | null, currentlyNotInStock: boolean, id: string, quantityAvailable?: number | null, requiresShipping: boolean, sku?: string | null, title: string, weight?: number | null, weightUnit: WeightUnit, compareAtPrice?: { amount: any, currencyCode: CurrencyCode } | null, image?: { altText?: string | null, url: any } | null, price: { amount: any, currencyCode: CurrencyCode }, selectedOptions: Array<{ name: string, value: string }>, unitPrice?: { amount: any, currencyCode: CurrencyCode } | null, unitPriceMeasurement?: { measuredType?: UnitPriceMeasurementMeasuredType | null, quantityValue: number, referenceUnit?: UnitPriceMeasurementMeasuredUnit | null, quantityUnit?: UnitPriceMeasurementMeasuredUnit | null } | null }> }, collections: { nodes: Array<{ description: string, descriptionHtml: any, handle: string, id: string, title: string, updatedAt: any, image?: { altText?: string | null, url: any } | null }> }, metafields: Array<{ description?: string | null, key: string, namespace: string, type: string, value: string, reference?: { image?: { url: any } | null } | {} | null, references?: { edges: Array<{ node: { image?: { url: any } | null } | {} }> } | null } | null> }> } } | null }; export type ProductRecommendationsQueryVariables = Exact<{ productId: Scalars['ID']['input']; identifiers: Array | HasMetafieldsIdentifier; + languageCode?: InputMaybe; + countryCode?: InputMaybe; }>; @@ -7796,6 +7810,8 @@ export type ProductRecommendationsQuery = { productRecommendations?: Array<{ ava export type GetShopInfoQueryVariables = Exact<{ identifiers: Array | HasMetafieldsIdentifier; + languageCode?: InputMaybe; + countryCode?: InputMaybe; }>; @@ -7814,7 +7830,7 @@ export type AddItemToCartMutationVariables = Exact<{ }>; -export type AddItemToCartMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null } | null }; +export type AddItemToCartMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null } | null }; export type RegisterAccountMutationVariables = Exact<{ email: Scalars['String']['input']; @@ -7833,7 +7849,7 @@ export type AddCouponMutationVariables = Exact<{ }>; -export type AddCouponMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null, userErrors: Array<{ field?: Array | null, message: string }> } | null }; +export type AddCouponMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null, userErrors: Array<{ field?: Array | null, message: string }> } | null }; export type UpdateItemsMutationVariables = Exact<{ cartId: Scalars['ID']['input']; @@ -7841,7 +7857,15 @@ export type UpdateItemsMutationVariables = Exact<{ }>; -export type UpdateItemsMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null } | null }; +export type UpdateItemsMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null } | null }; + +export type CartBuyerIdentityUpdateMutationVariables = Exact<{ + cartId: Scalars['ID']['input']; + buyerIdentity: CartBuyerIdentityInput; +}>; + + +export type CartBuyerIdentityUpdateMutation = { cartBuyerIdentityUpdate?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null, userErrors: Array<{ field?: Array | null, message: string }> } | null }; export type SignInWithEmailAndPasswordMutationVariables = Exact<{ email: Scalars['String']['input']; From ce35c85c4773319f433b45ec78579f0d85a27b29 Mon Sep 17 00:00:00 2001 From: yuriassuncx Date: Wed, 10 Sep 2025 16:33:50 -0300 Subject: [PATCH 3/8] feat: exclude prop added to sitemap --- shopify/handlers/sitemap.ts | 104 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/shopify/handlers/sitemap.ts b/shopify/handlers/sitemap.ts index 5cb37be8b..1c1194285 100644 --- a/shopify/handlers/sitemap.ts +++ b/shopify/handlers/sitemap.ts @@ -3,74 +3,72 @@ import { AppContext } from "../mod.ts"; import { withDigestCookie } from "../utils/password.ts"; type ConnInfo = Deno.ServeHandlerInfo; -const xmlHeader = - ''; -const includeSiteMaps = ( - currentXML: string, - origin: string, - includes?: string[], -) => { - const siteMapIncludeTags = []; +const XML_HEADER = ''; +const TODAY = new Date().toISOString().substring(0, 10); - for (const include of (includes ?? [])) { - siteMapIncludeTags.push(` - - ${include.startsWith("/") ? `${origin}${include}` : include} - ${new Date().toISOString().substring(0, 10)} - `); - } - return siteMapIncludeTags.length > 0 - ? currentXML.replace( - xmlHeader, - `${xmlHeader}\n${siteMapIncludeTags.join("\n")}`, - ) - : currentXML; -}; +function buildIncludeSitemaps(origin: string, includes?: string[]) { + if (!includes?.length) return ""; + + return includes + .map((include) => { + const loc = include.startsWith("/") ? `${origin}${include}` : include; + return ` \n ${loc}\n ${TODAY}\n `; + }) + .join("\n"); +} + +function excludeSitemaps(xml: string, origin: string, excludes?: string[]) { + if (!excludes?.length) return xml; + + return xml.replace( + /\s*(.*?)<\/loc>[\s\S]*?<\/sitemap>/g, + (match, loc) => { + const locPath = loc.startsWith(origin) + ? loc.slice(origin.length) + : new URL(loc).pathname; + + return excludes.some((ex) => locPath.startsWith(ex)) ? "" : match; + }, + ); +} export interface Props { include?: string[]; + exclude?: string[]; } + /** * @title Sitemap Proxy */ export default function Sitemap( - { include }: Props, + { include, exclude }: Props, appCtx: AppContext, ) { - const url = `https://${appCtx.storeName}.myshopify.com`; - return async ( - req: Request, - ctx: ConnInfo, - ) => { - if (!url) { - throw new Error("Missing publicUrl"); - } - - const publicUrl = - new URL(url?.startsWith("http") ? url : `https://${url}`).href; + const shopifyUrl = `https://${appCtx.storeName}.myshopify.com`; - const response = await Proxy({ - url: publicUrl, + return async (req: Request, conn: ConnInfo) => { + const reqOrigin = new URL(req.url).origin; + const proxyResponse = await Proxy({ + url: shopifyUrl, customHeaders: withDigestCookie(appCtx), - })(req, ctx); + })(req, conn); + + if (!proxyResponse.ok) return proxyResponse; + + const originalXml = await proxyResponse.text(); + const originWithSlash = reqOrigin.endsWith("/") ? reqOrigin.slice(0, -1) : reqOrigin; + const originReplacedXml = originalXml.replaceAll(shopifyUrl, originWithSlash); + const excludedXml = excludeSitemaps(originReplacedXml, reqOrigin, exclude); - if (!response.ok) { - return response; - } + const includeBlock = buildIncludeSitemaps(reqOrigin, include); + const finalXml = includeBlock + ? excludedXml.replace(XML_HEADER, `${XML_HEADER}\n${includeBlock}`) + : excludedXml; - const reqUrl = new URL(req.url); - const text = await response.text(); - return new Response( - includeSiteMaps( - text.replaceAll(publicUrl, `${reqUrl.origin}/`), - reqUrl.origin, - include, - ), - { - headers: response.headers, - status: response.status, - }, - ); + return new Response(finalXml, { + headers: proxyResponse.headers, + status: proxyResponse.status, + }); }; } From f0178ddb55a6c400e8bee161002f5e7286fbb9cd Mon Sep 17 00:00:00 2001 From: "@yuri_assuncx" Date: Tue, 30 Sep 2025 12:42:04 -0300 Subject: [PATCH 4/8] fix: new improvements suggested by coderrabit Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- shopify/handlers/sitemap.ts | 39 +++++++++++++++++++++++++++++++------ shopify/loaders/cart.ts | 2 ++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/shopify/handlers/sitemap.ts b/shopify/handlers/sitemap.ts index 1c1194285..a72125b6e 100644 --- a/shopify/handlers/sitemap.ts +++ b/shopify/handlers/sitemap.ts @@ -10,10 +10,20 @@ const TODAY = new Date().toISOString().substring(0, 10); function buildIncludeSitemaps(origin: string, includes?: string[]) { if (!includes?.length) return ""; + const today = new Date().toISOString().slice(0, 10); + const esc = (s: string) => + s + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); + return includes .map((include) => { const loc = include.startsWith("/") ? `${origin}${include}` : include; - return ` \n ${loc}\n ${TODAY}\n `; + const safeLoc = esc(loc); + return ` \n ${safeLoc}\n ${today}\n `; }) .join("\n"); } @@ -21,14 +31,23 @@ function buildIncludeSitemaps(origin: string, includes?: string[]) { function excludeSitemaps(xml: string, origin: string, excludes?: string[]) { if (!excludes?.length) return xml; + // Ensure all exclude prefixes start with a slash + const normalized = excludes.map((ex) => (ex.startsWith("/") ? ex : `/${ex}`)); + return xml.replace( /\s*(.*?)<\/loc>[\s\S]*?<\/sitemap>/g, (match, loc) => { - const locPath = loc.startsWith(origin) - ? loc.slice(origin.length) - : new URL(loc).pathname; + let locPath: string; + try { + // Use origin as base to support both absolute and relative URLs + const u = new URL(loc, origin); + locPath = u.pathname; + } catch { + // If URL parsing fails, leave the sitemap entry untouched + return match; + } - return excludes.some((ex) => locPath.startsWith(ex)) ? "" : match; + return normalized.some((ex) => locPath.startsWith(ex)) ? "" : match; }, ); } @@ -66,9 +85,17 @@ export default function Sitemap( ? excludedXml.replace(XML_HEADER, `${XML_HEADER}\n${includeBlock}`) : excludedXml; + const headers = new Headers(proxyResponse.headers); + headers.delete("content-length"); + headers.delete("content-encoding"); + headers.delete("etag"); + headers.delete("accept-ranges"); + if (!headers.get("content-type")?.includes("xml")) { + headers.set("content-type", "application/xml; charset=utf-8"); + } return new Response(finalXml, { - headers: proxyResponse.headers, status: proxyResponse.status, + headers, }); }; } diff --git a/shopify/loaders/cart.ts b/shopify/loaders/cart.ts index 8f198cb6c..33d978c79 100644 --- a/shopify/loaders/cart.ts +++ b/shopify/loaders/cart.ts @@ -35,9 +35,11 @@ const loader = async ( const { storefront } = ctx; const maybeCartId = getCartCookie(req.headers); + const cartId = maybeCartId || const cartId = maybeCartId || await storefront.query({ ...CreateCart, + variables: { countryCode }, }).then((data) => data.payload?.cart?.id); if (!cartId) { From 0ae284a1efad0ea3f711d6c6d3a497fcbd54c3af Mon Sep 17 00:00:00 2001 From: yuriassuncx Date: Tue, 30 Sep 2025 12:51:05 -0300 Subject: [PATCH 5/8] refactor: improve code readability and maintainability across multiple files --- shopify/handlers/sitemap.ts | 18 +++++++++++++----- shopify/loaders/ProductDetailsPage.ts | 2 +- shopify/loaders/ProductList.ts | 5 ++--- shopify/loaders/ProductListingPage.ts | 11 ++++++----- shopify/loaders/RelatedProducts.ts | 10 ++++++---- shopify/loaders/cart.ts | 1 - shopify/loaders/proxy.ts | 25 ++++++++++++++++++++++--- shopify/utils/storefront/queries.ts | 2 +- shopify/utils/transform.ts | 18 ++++++++++-------- shopify/utils/types.ts | 5 ++++- 10 files changed, 65 insertions(+), 32 deletions(-) diff --git a/shopify/handlers/sitemap.ts b/shopify/handlers/sitemap.ts index a72125b6e..968238e3d 100644 --- a/shopify/handlers/sitemap.ts +++ b/shopify/handlers/sitemap.ts @@ -4,13 +4,16 @@ import { withDigestCookie } from "../utils/password.ts"; type ConnInfo = Deno.ServeHandlerInfo; -const XML_HEADER = ''; -const TODAY = new Date().toISOString().substring(0, 10); +const XML_HEADER = + ''; + +// Helper function to get current date in YYYY-MM-DD format +const getToday = (): string => new Date().toISOString().substring(0, 10); function buildIncludeSitemaps(origin: string, includes?: string[]) { if (!includes?.length) return ""; - const today = new Date().toISOString().slice(0, 10); + const today = getToday(); const esc = (s: string) => s .replaceAll("&", "&") @@ -76,8 +79,13 @@ export default function Sitemap( if (!proxyResponse.ok) return proxyResponse; const originalXml = await proxyResponse.text(); - const originWithSlash = reqOrigin.endsWith("/") ? reqOrigin.slice(0, -1) : reqOrigin; - const originReplacedXml = originalXml.replaceAll(shopifyUrl, originWithSlash); + const originWithSlash = reqOrigin.endsWith("/") + ? reqOrigin.slice(0, -1) + : reqOrigin; + const originReplacedXml = originalXml.replaceAll( + shopifyUrl, + originWithSlash, + ); const excludedXml = excludeSitemaps(originReplacedXml, reqOrigin, exclude); const includeBlock = buildIncludeSitemaps(reqOrigin, include); diff --git a/shopify/loaders/ProductDetailsPage.ts b/shopify/loaders/ProductDetailsPage.ts index 52028c0a9..5117c5282 100644 --- a/shopify/loaders/ProductDetailsPage.ts +++ b/shopify/loaders/ProductDetailsPage.ts @@ -3,11 +3,11 @@ import { AppContext } from "../../shopify/mod.ts"; import { toProductPage } from "../../shopify/utils/transform.ts"; import type { RequestURLParam } from "../../website/functions/requestToParam.ts"; import { + CountryCode, GetProductQuery, GetProductQueryVariables, HasMetafieldsMetafieldsArgs, LanguageCode, - CountryCode } from "../utils/storefront/storefront.graphql.gen.ts"; import { GetProduct } from "../utils/storefront/queries.ts"; import { LanguageContextArgs, Metafield } from "../utils/types.ts"; diff --git a/shopify/loaders/ProductList.ts b/shopify/loaders/ProductList.ts index 3c0bcd241..e0be30c47 100644 --- a/shopify/loaders/ProductList.ts +++ b/shopify/loaders/ProductList.ts @@ -6,15 +6,15 @@ import { } from "../utils/storefront/queries.ts"; import { CollectionProductsArgs, + CountryCode, HasMetafieldsMetafieldsArgs, + LanguageCode, Product as ProductShopify, ProductConnection, QueryRoot, QueryRootCollectionArgs, QueryRootSearchArgs, SearchResultItemConnection, - LanguageCode, - CountryCode } from "../utils/storefront/storefront.graphql.gen.ts"; import { toProduct } from "../utils/transform.ts"; import { @@ -135,7 +135,6 @@ const loader = async ( }); if (isQueryList(props)) { - const data = await storefront.query< QueryRoot, QueryRootSearchArgs & HasMetafieldsMetafieldsArgs & LanguageContextArgs diff --git a/shopify/loaders/ProductListingPage.ts b/shopify/loaders/ProductListingPage.ts index 32c66885c..a60872f1e 100644 --- a/shopify/loaders/ProductListingPage.ts +++ b/shopify/loaders/ProductListingPage.ts @@ -6,15 +6,15 @@ import { } from "../utils/storefront/queries.ts"; import { CollectionProductsArgs, + CountryCode, HasMetafieldsMetafieldsArgs, + LanguageCode, Product, ProductConnection, QueryRoot, QueryRootCollectionArgs, QueryRootSearchArgs, SearchResultItemConnection, - LanguageCode, - CountryCode } from "../utils/storefront/storefront.graphql.gen.ts"; import { toFilter, toProduct } from "../utils/transform.ts"; import { LanguageContextArgs, Metafield } from "../utils/types.ts"; @@ -142,7 +142,7 @@ const loader = async ( identifiers: metafields, languageCode, countryCode, - ...searchSortShopify[sort], + ...(searchSortShopify[sort] || {}), }, ...SearchProducts, }); @@ -157,7 +157,8 @@ const loader = async ( } else { // Support for multiple paths, such as /{lang}/collections/first-collection/second-collection // Always takes the last non-empty segment as pathname - const pathname = props.collectionName || url.pathname.split("/").filter(Boolean).pop(); + const pathname = props.collectionName || + url.pathname.split("/").filter(Boolean).pop(); const data = await storefront.query< QueryRoot, @@ -176,7 +177,7 @@ const loader = async ( filters: getFiltersByUrl(url), languageCode, countryCode, - ...sortShopify[sort], + ...(sortShopify[sort] || {}), }, ...ProductsByCollection, }); diff --git a/shopify/loaders/RelatedProducts.ts b/shopify/loaders/RelatedProducts.ts index a677f8038..51f44515a 100644 --- a/shopify/loaders/RelatedProducts.ts +++ b/shopify/loaders/RelatedProducts.ts @@ -6,13 +6,13 @@ import { ProductRecommendations, } from "../utils/storefront/queries.ts"; import { + CountryCode, GetProductQuery, GetProductQueryVariables, HasMetafieldsMetafieldsArgs, + LanguageCode, ProductRecommendationsQuery, ProductRecommendationsQueryVariables, - LanguageCode, - CountryCode } from "../utils/storefront/storefront.graphql.gen.ts"; import { toProduct } from "../utils/transform.ts"; import { LanguageContextArgs, Metafield } from "../utils/types.ts"; @@ -74,13 +74,15 @@ const loader = async ( const data = await storefront.query< ProductRecommendationsQuery, - ProductRecommendationsQueryVariables & HasMetafieldsMetafieldsArgs & LanguageContextArgs + & ProductRecommendationsQueryVariables + & HasMetafieldsMetafieldsArgs + & LanguageContextArgs >({ variables: { productId: query.product.id, identifiers: metafields, languageCode, - countryCode + countryCode, }, ...ProductRecommendations, }); diff --git a/shopify/loaders/cart.ts b/shopify/loaders/cart.ts index 33d978c79..8a526705f 100644 --- a/shopify/loaders/cart.ts +++ b/shopify/loaders/cart.ts @@ -35,7 +35,6 @@ const loader = async ( const { storefront } = ctx; const maybeCartId = getCartCookie(req.headers); - const cartId = maybeCartId || const cartId = maybeCartId || await storefront.query({ ...CreateCart, diff --git a/shopify/loaders/proxy.ts b/shopify/loaders/proxy.ts index 3009ca3a8..605412dc8 100644 --- a/shopify/loaders/proxy.ts +++ b/shopify/loaders/proxy.ts @@ -29,7 +29,8 @@ const buildProxyRoutes = ( { ctx, ctx: { storeName, publicUrl }, - extraPathsToProxy: extraPaths = [], + extraPathsToProxy = [], + extraPaths = [], includeSiteMap, generateDecoSiteMap, excludePathsFromDecoSiteMap, @@ -37,8 +38,18 @@ const buildProxyRoutes = ( replaces, }: Props & { ctx: AppContext; + extraPaths?: string[]; }, ) => { + // Handle backward compatibility for extraPaths prop + const finalExtraPathsToProxy = extraPathsToProxy.length > 0 + ? extraPathsToProxy + : extraPaths; + if (extraPaths.length > 0 && extraPathsToProxy.length === 0) { + console.warn( + 'DEPRECATION WARNING: "extraPaths" prop is deprecated. Use "extraPathsToProxy" instead.', + ); + } const urlToUse = publicUrl ? new URL(publicUrl.startsWith("http") ? publicUrl : `https://${publicUrl}`) : new URL(`https://${storeName}.myshopify.com`); @@ -60,7 +71,9 @@ const buildProxyRoutes = ( pathTemplate, handler: { value: { - __resolveType: pathTemplate.includes("sitemap") ? "shopify/handlers/sitemap.ts" : "website/handlers/proxy.ts", + __resolveType: pathTemplate.includes("sitemap") + ? "shopify/handlers/sitemap.ts" + : "website/handlers/proxy.ts", url: urlToProxy, host: hostToUse, customHeaders: withDigestCookie(ctx), @@ -68,7 +81,7 @@ const buildProxyRoutes = ( }, }, }); - const routesFromPaths = [...PATHS_TO_PROXY, ...extraPaths].map( + const routesFromPaths = [...PATHS_TO_PROXY, ...finalExtraPathsToProxy].map( routeFromPath, ); @@ -115,6 +128,10 @@ const buildProxyRoutes = ( export interface Props { extraPathsToProxy?: string[]; + /** + * @deprecated Use extraPathsToProxy instead + */ + extraPaths?: string[]; /** * @title Other site maps to include */ @@ -140,6 +157,7 @@ export interface Props { function loader( { extraPathsToProxy = [], + extraPaths = [], includeSiteMap = [], generateDecoSiteMap = true, excludePathsFromDecoSiteMap = [], @@ -155,6 +173,7 @@ function loader( excludePathsFromShopifySiteMap, includeSiteMap, extraPathsToProxy, + extraPaths, replaces, ctx, }); diff --git a/shopify/utils/storefront/queries.ts b/shopify/utils/storefront/queries.ts index 4ad018239..68b68170d 100644 --- a/shopify/utils/storefront/queries.ts +++ b/shopify/utils/storefront/queries.ts @@ -603,7 +603,7 @@ export const CartBuyerIdentityUpdate = { } } `, -} +}; export const SignInWithEmailAndPassword = { query: gql` diff --git a/shopify/utils/transform.ts b/shopify/utils/transform.ts index 680eaa387..ee945fec6 100644 --- a/shopify/utils/transform.ts +++ b/shopify/utils/transform.ts @@ -163,20 +163,22 @@ export const toProduct = ( const { key, value, reference, references } = metafield || {}; const referenceImageUrl = - (reference as { image?: { url?: string } } | undefined)?.image?.url ?? null; + (reference as { image?: { url?: string } } | undefined)?.image?.url ?? + null; const edgeImages = Array.isArray(references?.edges) ? references.edges.map( - (edge) => - (edge?.node as { image?: { url?: string } } | undefined)?.image - ?.url ?? null - ) + (edge) => + (edge?.node as { image?: { url?: string } } | undefined)?.image + ?.url ?? null, + ) : null; - const validEdgeImages = edgeImages?.filter((url) => !!url) as string[] | undefined; + const validEdgeImages = edgeImages?.filter((url) => !!url) as + | string[] + | undefined; - const valueToReturn = - referenceImageUrl ?? + const valueToReturn = referenceImageUrl ?? (validEdgeImages && validEdgeImages.length > 0 ? validEdgeImages.join(",") : value); diff --git a/shopify/utils/types.ts b/shopify/utils/types.ts index c7e9f7b9e..55d8d2424 100644 --- a/shopify/utils/types.ts +++ b/shopify/utils/types.ts @@ -4,7 +4,10 @@ import { OrderFinancialStatus, OrderFulfillmentStatus, } from "./enums.ts"; -import { LanguageCode, CountryCode } from "./storefront/storefront.graphql.gen.ts"; +import { + CountryCode, + LanguageCode, +} from "./storefront/storefront.graphql.gen.ts"; type Attribute = { key: string; From 0220c31eb7e27aedb24d7ce3e19dcef7cac80eba Mon Sep 17 00:00:00 2001 From: yuriassuncx Date: Thu, 30 Oct 2025 14:26:20 -0300 Subject: [PATCH 6/8] fix: remove unnecessary countryCode variable from cart loader --- shopify/loaders/cart.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/shopify/loaders/cart.ts b/shopify/loaders/cart.ts index 8a526705f..8f198cb6c 100644 --- a/shopify/loaders/cart.ts +++ b/shopify/loaders/cart.ts @@ -38,7 +38,6 @@ const loader = async ( const cartId = maybeCartId || await storefront.query({ ...CreateCart, - variables: { countryCode }, }).then((data) => data.payload?.cart?.id); if (!cartId) { From 5f8bb0bd7fd206a883bd1dce50f741ebf9fdbd2f Mon Sep 17 00:00:00 2001 From: yuriassuncx Date: Wed, 19 Nov 2025 15:24:04 -0300 Subject: [PATCH 7/8] chore: remove unused Product import from loaders --- shopify/loaders/ProductList.ts | 1 - shopify/loaders/ProductListingPage.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/shopify/loaders/ProductList.ts b/shopify/loaders/ProductList.ts index ec405c975..aee5bf601 100644 --- a/shopify/loaders/ProductList.ts +++ b/shopify/loaders/ProductList.ts @@ -9,7 +9,6 @@ import { CountryCode, HasMetafieldsMetafieldsArgs, LanguageCode, - Product as ProductShopify, ProductConnection, ProductFragment, QueryRoot, diff --git a/shopify/loaders/ProductListingPage.ts b/shopify/loaders/ProductListingPage.ts index 012e10046..3a31abdaf 100644 --- a/shopify/loaders/ProductListingPage.ts +++ b/shopify/loaders/ProductListingPage.ts @@ -9,7 +9,6 @@ import { CountryCode, HasMetafieldsMetafieldsArgs, LanguageCode, - Product, ProductConnection, ProductFragment, QueryRoot, From fff74a80e9eac5696bc45656e1e608226b6fb13c Mon Sep 17 00:00:00 2001 From: yuriassuncx Date: Wed, 19 Nov 2025 15:25:21 -0300 Subject: [PATCH 8/8] feat: update Cart and GetCart types to include buyerIdentity field --- .../storefront/storefront.graphql.gen.ts | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/shopify/utils/storefront/storefront.graphql.gen.ts b/shopify/utils/storefront/storefront.graphql.gen.ts index a4ad7ac15..03602f998 100644 --- a/shopify/utils/storefront/storefront.graphql.gen.ts +++ b/shopify/utils/storefront/storefront.graphql.gen.ts @@ -7733,8 +7733,7 @@ export type ProductFragment = { availableForSale: boolean, createdAt: any, descr export type FilterFragment = { id: string, label: string, type: FilterType, values: Array<{ count: number, id: string, input: any, label: string }> }; -export type CartFragment = { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> }; -export type CartFragment = { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array< +export type CartFragment = { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array< | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array< | { code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | Record @@ -7765,7 +7764,20 @@ export type GetCartQueryVariables = Exact<{ }>; -export type GetCartQuery = { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array<{ code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | {}>, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null }; +export type GetCartQuery = { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array< + | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array< + | { code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } + | Record + >, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } + | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array< + | { code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } + | Record + >, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } + > }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array< + | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } + | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } + | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } + > } | null }; export type GetProductQueryVariables = Exact<{ handle?: InputMaybe; @@ -7857,7 +7869,7 @@ export type AllProductsQueryVariables = Exact<{ }>; -export type AllProductsQuery = { collection?: { handle: string, description: string, title: string, products: { pageInfo: { hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null, startCursor?: string | null }, filters: Array<{ id: string, label: string, type: FilterType, values: Array<{ count: number, id: string, input: any, label: string }> }>, nodes: Array<{ availableForSale: boolean, createdAt: any, description: string, descriptionHtml: any, handle: string, id: string, isGiftCard: boolean, onlineStoreUrl?: any | null, productType: string, publishedAt: any, requiresSellingPlan: boolean, tags: Array, title: string, totalInventory?: number | null, updatedAt: any, vendor: string, featuredImage?: { altText?: string | null, url: any } | null, images: { nodes: Array<{ altText?: string | null, url: any }> }, media: { nodes: Array< +export type AllProductsQuery = { collection?: { id: string, handle: string, description: string, title: string, products: { pageInfo: { hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null, startCursor?: string | null }, filters: Array<{ id: string, label: string, type: FilterType, values: Array<{ count: number, id: string, input: any, label: string }> }>, nodes: Array<{ availableForSale: boolean, createdAt: any, description: string, descriptionHtml: any, handle: string, id: string, isGiftCard: boolean, onlineStoreUrl?: any | null, productType: string, publishedAt: any, requiresSellingPlan: boolean, tags: Array, title: string, totalInventory?: number | null, updatedAt: any, vendor: string, featuredImage?: { altText?: string | null, url: any } | null, images: { nodes: Array<{ altText?: string | null, url: any }> }, media: { nodes: Array< | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } @@ -7919,7 +7931,7 @@ export type AddItemToCartMutationVariables = Exact<{ }>; -export type AddItemToCartMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array< +export type AddItemToCartMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array< | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array< | { code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | Record @@ -7951,7 +7963,7 @@ export type AddCouponMutationVariables = Exact<{ }>; -export type AddCouponMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array< +export type AddCouponMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array< | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array< | { code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | Record @@ -7972,7 +7984,7 @@ export type UpdateItemsMutationVariables = Exact<{ }>; -export type UpdateItemsMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array< +export type UpdateItemsMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array< | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array< | { code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } | Record @@ -7987,6 +7999,27 @@ export type UpdateItemsMutation = { payload?: { cart?: { id: string, checkoutUrl | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } > } | null } | null }; +export type CartBuyerIdentityUpdateMutationVariables = Exact<{ + cartId: Scalars['ID']['input']; + buyerIdentity: CartBuyerIdentityInput; +}>; + + +export type CartBuyerIdentityUpdateMutation = { cartBuyerIdentityUpdate?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, buyerIdentity: { countryCode?: CountryCode | null, email?: string | null }, lines: { nodes: Array< + | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array< + | { code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } + | Record + >, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } + | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string, onlineStoreUrl?: any | null, handle: string }, price: { amount: any, currencyCode: CurrencyCode } }, discountAllocations: Array< + | { code: string, discountedAmount: { amount: any, currencyCode: CurrencyCode } } + | Record + >, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } + > }, cost: { totalTaxAmount?: { amount: any, currencyCode: CurrencyCode } | null, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array< + | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } + | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } + | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } + > } | null, userErrors: Array<{ field?: Array | null, message: string }> } | null }; + export type SignInWithEmailAndPasswordMutationVariables = Exact<{ email: Scalars['String']['input']; password: Scalars['String']['input'];