Change variant image/price & metafields progress

This commit is contained in:
cond0r 2022-07-18 12:09:15 +03:00
parent d4858910e4
commit a56c84375b
27 changed files with 1090 additions and 1607 deletions

View File

@ -1,4 +1,4 @@
import { SEO } from './common' import type { SEO } from './common'
export type ProductImage = { export type ProductImage = {
url: string url: string
@ -31,12 +31,17 @@ export type ProductVariant = {
id: string | number id: string | number
options: ProductOption[] options: ProductOption[]
availableForSale?: boolean availableForSale?: boolean
// Product variant price
price?: ProductPrice price?: ProductPrice
// Product variant image
image?: ProductImage image?: ProductImage
} }
export type ProductMetafield = {
key: string
value: string
description?: string
type?: string
}
export type Product = { export type Product = {
id: string id: string
name: string name: string
@ -51,6 +56,7 @@ export type Product = {
options: ProductOption[] options: ProductOption[]
vendor?: string vendor?: string
seo?: SEO seo?: SEO
metafields?: ProductMetafield[]
} }
export type SearchProductsBody = { export type SearchProductsBody = {
@ -102,5 +108,5 @@ export type GetAllProductsOperation<T extends ProductTypes = ProductTypes> = {
export type GetProductOperation<T extends ProductTypes = ProductTypes> = { export type GetProductOperation<T extends ProductTypes = ProductTypes> = {
data: { product?: T['product'] } data: { product?: T['product'] }
variables: { path: string; slug?: never } | { path?: never; slug: string } variables: { slug?: string; path?: string; withMetafields?: boolean }
} }

View File

@ -1737,7 +1737,7 @@ export type Country = {
/** /**
* The code designating a country/region, which generally follows ISO 3166-1 alpha-2 guidelines. * The code designating a country/region, which generally follows ISO 3166-1 alpha-2 guidelines.
* If a territory doesn't have a country code value in the `CountryCode` enum, it might be considered a subdivision * If a territory doesn't have a country code value in the `CountryCode` enum, then it might be considered a subdivision
* of another country. For example, the territories associated with Spain are represented by the country code `ES`, * of another country. For example, the territories associated with Spain are represented by the country code `ES`,
* and the territories associated with the United States of America are represented by the country code `US`. * and the territories associated with the United States of America are represented by the country code `US`.
* *
@ -5317,7 +5317,7 @@ export type Payment = Node & {
idempotencyKey?: Maybe<Scalars['String']> idempotencyKey?: Maybe<Scalars['String']>
/** The URL where the customer needs to be redirected so they can complete the 3D Secure payment flow. */ /** The URL where the customer needs to be redirected so they can complete the 3D Secure payment flow. */
nextActionUrl?: Maybe<Scalars['URL']> nextActionUrl?: Maybe<Scalars['URL']>
/** Whether or not the payment is still processing asynchronously. */ /** Whether the payment is still processing asynchronously. */
ready: Scalars['Boolean'] ready: Scalars['Boolean']
/** A flag to indicate if the payment is to be done in test mode for gateways that support it. */ /** A flag to indicate if the payment is to be done in test mode for gateways that support it. */
test: Scalars['Boolean'] test: Scalars['Boolean']
@ -6458,7 +6458,7 @@ export type ShopPolicyWithDefault = {
*/ */
export type StoreAvailability = { export type StoreAvailability = {
__typename?: 'StoreAvailability' __typename?: 'StoreAvailability'
/** Whether or not this product variant is in-stock at this location. */ /** Whether the product variant is in-stock at this location. */
available: Scalars['Boolean'] available: Scalars['Boolean']
/** The location where this product variant is stocked at. */ /** The location where this product variant is stocked at. */
location: Location location: Location
@ -6759,24 +6759,195 @@ export enum WeightUnit {
Pounds = 'POUNDS', Pounds = 'POUNDS',
} }
export type AssociateCustomerWithCheckoutMutationVariables = Exact<{ export type GetSiteInfoQueryVariables = Exact<{ [key: string]: never }>
checkoutId: Scalars['ID']
customerAccessToken: Scalars['String']
}>
export type AssociateCustomerWithCheckoutMutation = { export type GetSiteInfoQuery = {
__typename?: 'Mutation' __typename?: 'QueryRoot'
checkoutCustomerAssociateV2?: { shop: { __typename?: 'Shop'; name: string }
__typename?: 'CheckoutCustomerAssociateV2Payload' }
checkout?: { __typename?: 'Checkout'; id: string } | null
checkoutUserErrors: Array<{ export type CartDetailsFragment = {
__typename?: 'CheckoutUserError' __typename?: 'Cart'
code?: CheckoutErrorCode | null id: string
checkoutUrl: any
createdAt: any
updatedAt: any
lines: {
__typename?: 'CartLineConnection'
edges: Array<{
__typename?: 'CartLineEdge'
node: {
__typename?: 'CartLine'
id: string
quantity: number
merchandise: {
__typename?: 'ProductVariant'
id: string
sku?: string | null
title: string
selectedOptions: Array<{
__typename?: 'SelectedOption'
name: string
value: string
}>
image?: {
__typename?: 'Image'
url: any
altText?: string | null
width?: number | null
height?: number | null
} | null
priceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
compareAtPriceV2?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
product: { __typename?: 'Product'; title: string; handle: string }
}
}
}>
}
attributes: Array<{
__typename?: 'Attribute'
key: string
value?: string | null
}>
buyerIdentity: {
__typename?: 'CartBuyerIdentity'
email?: string | null
customer?: { __typename?: 'Customer'; id: string } | null
}
estimatedCost: {
__typename?: 'CartEstimatedCost'
totalAmount: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
subtotalAmount: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
totalTaxAmount?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
totalDutyAmount?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
}
}
export type UserErrorsFragment = {
__typename?: 'CartUserError'
code?: CartErrorCode | null
field?: Array<string> | null field?: Array<string> | null
message: string message: string
}> }
customer?: { __typename?: 'Customer'; id: string } | null
export type CustomerUserErrorsFragment = {
__typename?: 'CustomerUserError'
code?: CustomerErrorCode | null
field?: Array<string> | null
message: string
}
export type CustomerAccessTokenFragment = {
__typename?: 'CustomerAccessToken'
accessToken: string
expiresAt: any
}
type Media_ExternalVideo_Fragment = {
__typename?: 'ExternalVideo'
mediaContentType: MediaContentType
alt?: string | null
previewImage?: { __typename?: 'Image'; url: any } | null
}
type Media_MediaImage_Fragment = {
__typename?: 'MediaImage'
id: string
mediaContentType: MediaContentType
alt?: string | null
image?: {
__typename?: 'Image'
url: any
width?: number | null
height?: number | null
} | null } | null
previewImage?: { __typename?: 'Image'; url: any } | null
}
type Media_Model3d_Fragment = {
__typename?: 'Model3d'
mediaContentType: MediaContentType
alt?: string | null
previewImage?: { __typename?: 'Image'; url: any } | null
}
type Media_Video_Fragment = {
__typename?: 'Video'
mediaContentType: MediaContentType
alt?: string | null
previewImage?: { __typename?: 'Image'; url: any } | null
}
export type MediaFragment =
| Media_ExternalVideo_Fragment
| Media_MediaImage_Fragment
| Media_Model3d_Fragment
| Media_Video_Fragment
export type ProductCardFragment = {
__typename?: 'Product'
id: string
handle: string
availableForSale: boolean
title: string
productType: string
vendor: string
variants: {
__typename?: 'ProductVariantConnection'
nodes: Array<{
__typename?: 'ProductVariant'
id: string
title: string
requiresShipping: boolean
availableForSale: boolean
selectedOptions: Array<{
__typename?: 'SelectedOption'
name: string
value: string
}>
image?: {
__typename?: 'Image'
url: any
altText?: string | null
width?: number | null
height?: number | null
} | null
priceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
compareAtPriceV2?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
}>
}
} }
export type CartCreateMutationVariables = Exact<{ export type CartCreateMutationVariables = Exact<{
@ -7170,6 +7341,54 @@ export type CartLinesUpdateMutation = {
} | null } | null
} }
export type CustomerActivateMutationVariables = Exact<{
id: Scalars['ID']
input: CustomerActivateInput
}>
export type CustomerActivateMutation = {
__typename?: 'Mutation'
customerActivate?: {
__typename?: 'CustomerActivatePayload'
customer?: { __typename?: 'Customer'; id: string } | null
customerAccessToken?: {
__typename?: 'CustomerAccessToken'
accessToken: string
expiresAt: any
} | null
customerUserErrors: Array<{
__typename?: 'CustomerUserError'
code?: CustomerErrorCode | null
field?: Array<string> | null
message: string
}>
} | null
}
export type CustomerActivateByUrlMutationVariables = Exact<{
activationUrl: Scalars['URL']
password: Scalars['String']
}>
export type CustomerActivateByUrlMutation = {
__typename?: 'Mutation'
customerActivateByUrl?: {
__typename?: 'CustomerActivateByUrlPayload'
customer?: { __typename?: 'Customer'; id: string } | null
customerAccessToken?: {
__typename?: 'CustomerAccessToken'
accessToken: string
expiresAt: any
} | null
customerUserErrors: Array<{
__typename?: 'CustomerUserError'
code?: CustomerErrorCode | null
field?: Array<string> | null
message: string
}>
} | null
}
export type CustomerAccessTokenCreateMutationVariables = Exact<{ export type CustomerAccessTokenCreateMutationVariables = Exact<{
input: CustomerAccessTokenCreateInput input: CustomerAccessTokenCreateInput
}> }>
@ -7210,54 +7429,6 @@ export type CustomerAccessTokenDeleteMutation = {
} | null } | null
} }
export type CustomerActivateByUrlMutationVariables = Exact<{
activationUrl: Scalars['URL']
password: Scalars['String']
}>
export type CustomerActivateByUrlMutation = {
__typename?: 'Mutation'
customerActivateByUrl?: {
__typename?: 'CustomerActivateByUrlPayload'
customer?: { __typename?: 'Customer'; id: string } | null
customerAccessToken?: {
__typename?: 'CustomerAccessToken'
accessToken: string
expiresAt: any
} | null
customerUserErrors: Array<{
__typename?: 'CustomerUserError'
code?: CustomerErrorCode | null
field?: Array<string> | null
message: string
}>
} | null
}
export type CustomerActivateMutationVariables = Exact<{
id: Scalars['ID']
input: CustomerActivateInput
}>
export type CustomerActivateMutation = {
__typename?: 'Mutation'
customerActivate?: {
__typename?: 'CustomerActivatePayload'
customer?: { __typename?: 'Customer'; id: string } | null
customerAccessToken?: {
__typename?: 'CustomerAccessToken'
accessToken: string
expiresAt: any
} | null
customerUserErrors: Array<{
__typename?: 'CustomerUserError'
code?: CustomerErrorCode | null
field?: Array<string> | null
message: string
}>
} | null
}
export type CustomerCreateMutationVariables = Exact<{ export type CustomerCreateMutationVariables = Exact<{
input: CustomerCreateInput input: CustomerCreateInput
}> }>
@ -7355,51 +7526,6 @@ export type GetAllProductPathsQuery = {
} }
} }
export type ProductConnectionFragment = {
__typename?: 'ProductConnection'
pageInfo: {
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
}
edges: Array<{
__typename?: 'ProductEdge'
node: {
__typename?: 'Product'
id: string
title: string
vendor: string
handle: string
priceRange: {
__typename?: 'ProductPriceRange'
minVariantPrice: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
}
images: {
__typename?: 'ImageConnection'
pageInfo: {
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
}
edges: Array<{
__typename?: 'ImageEdge'
node: {
__typename?: 'Image'
url: any
altText?: string | null
width?: number | null
height?: number | null
}
}>
}
}
}>
}
export type GetAllProductsQueryVariables = Exact<{ export type GetAllProductsQueryVariables = Exact<{
first?: InputMaybe<Scalars['Int']> first?: InputMaybe<Scalars['Int']>
query?: InputMaybe<Scalars['String']> query?: InputMaybe<Scalars['String']>
@ -7421,59 +7547,19 @@ export type GetAllProductsQuery = {
node: { node: {
__typename?: 'Product' __typename?: 'Product'
id: string id: string
title: string
vendor: string
handle: string handle: string
priceRange: { availableForSale: boolean
__typename?: 'ProductPriceRange' title: string
minVariantPrice: { productType: string
__typename?: 'MoneyV2' vendor: string
amount: any variants: {
currencyCode: CurrencyCode __typename?: 'ProductVariantConnection'
} nodes: Array<{
}
images: {
__typename?: 'ImageConnection'
pageInfo: {
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
}
edges: Array<{
__typename?: 'ImageEdge'
node: {
__typename?: 'Image'
url: any
altText?: string | null
width?: number | null
height?: number | null
}
}>
}
}
}>
}
}
export type CartDetailsFragment = {
__typename?: 'Cart'
id: string
checkoutUrl: any
createdAt: any
updatedAt: any
lines: {
__typename?: 'CartLineConnection'
edges: Array<{
__typename?: 'CartLineEdge'
node: {
__typename?: 'CartLine'
id: string
quantity: number
merchandise: {
__typename?: 'ProductVariant' __typename?: 'ProductVariant'
id: string id: string
sku?: string | null
title: string title: string
requiresShipping: boolean
availableForSale: boolean
selectedOptions: Array<{ selectedOptions: Array<{
__typename?: 'SelectedOption' __typename?: 'SelectedOption'
name: string name: string
@ -7496,44 +7582,11 @@ export type CartDetailsFragment = {
amount: any amount: any
currencyCode: CurrencyCode currencyCode: CurrencyCode
} | null } | null
product: { __typename?: 'Product'; title: string; handle: string } }>
} }
} }
}> }>
} }
attributes: Array<{
__typename?: 'Attribute'
key: string
value?: string | null
}>
buyerIdentity: {
__typename?: 'CartBuyerIdentity'
email?: string | null
customer?: { __typename?: 'Customer'; id: string } | null
}
estimatedCost: {
__typename?: 'CartEstimatedCost'
totalAmount: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
subtotalAmount: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
totalTaxAmount?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
totalDutyAmount?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
}
} }
export type GetCartQueryVariables = Exact<{ export type GetCartQueryVariables = Exact<{
@ -7624,181 +7677,6 @@ export type GetCartQuery = {
} | null } | null
} }
export type CheckoutDetailsFragment = {
__typename?: 'Checkout'
id: string
webUrl: any
completedAt?: any | null
createdAt: any
taxesIncluded: boolean
subtotalPriceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
totalTaxV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
totalPriceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
lineItems: {
__typename?: 'CheckoutLineItemConnection'
pageInfo: {
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
}
edges: Array<{
__typename?: 'CheckoutLineItemEdge'
node: {
__typename?: 'CheckoutLineItem'
id: string
title: string
quantity: number
variant?: {
__typename?: 'ProductVariant'
id: string
sku?: string | null
title: string
selectedOptions: Array<{
__typename?: 'SelectedOption'
name: string
value: string
}>
image?: {
__typename?: 'Image'
url: any
altText?: string | null
width?: number | null
height?: number | null
} | null
priceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
compareAtPriceV2?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
product: { __typename?: 'Product'; handle: string }
} | null
}
}>
}
}
export type GetCheckoutQueryVariables = Exact<{
checkoutId: Scalars['ID']
}>
export type GetCheckoutQuery = {
__typename?: 'QueryRoot'
node?:
| { __typename?: 'AppliedGiftCard' }
| { __typename?: 'Article' }
| { __typename?: 'Blog' }
| { __typename?: 'Cart' }
| { __typename?: 'CartLine' }
| {
__typename?: 'Checkout'
id: string
webUrl: any
completedAt?: any | null
createdAt: any
taxesIncluded: boolean
subtotalPriceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
totalTaxV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
totalPriceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
lineItems: {
__typename?: 'CheckoutLineItemConnection'
pageInfo: {
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
}
edges: Array<{
__typename?: 'CheckoutLineItemEdge'
node: {
__typename?: 'CheckoutLineItem'
id: string
title: string
quantity: number
variant?: {
__typename?: 'ProductVariant'
id: string
sku?: string | null
title: string
selectedOptions: Array<{
__typename?: 'SelectedOption'
name: string
value: string
}>
image?: {
__typename?: 'Image'
url: any
altText?: string | null
width?: number | null
height?: number | null
} | null
priceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
compareAtPriceV2?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
product: { __typename?: 'Product'; handle: string }
} | null
}
}>
}
}
| { __typename?: 'CheckoutLineItem' }
| { __typename?: 'Collection' }
| { __typename?: 'Comment' }
| { __typename?: 'ExternalVideo' }
| { __typename?: 'GenericFile' }
| { __typename?: 'Location' }
| { __typename?: 'MailingAddress' }
| { __typename?: 'MediaImage' }
| { __typename?: 'Menu' }
| { __typename?: 'MenuItem' }
| { __typename?: 'Metafield' }
| { __typename?: 'Model3d' }
| { __typename?: 'Order' }
| { __typename?: 'Page' }
| { __typename?: 'Payment' }
| { __typename?: 'Product' }
| { __typename?: 'ProductOption' }
| { __typename?: 'ProductVariant' }
| { __typename?: 'Shop' }
| { __typename?: 'ShopPolicy' }
| { __typename?: 'Video' }
| null
}
export type GetProductsFromCollectionQueryVariables = Exact<{ export type GetProductsFromCollectionQueryVariables = Exact<{
categoryId: Scalars['ID'] categoryId: Scalars['ID']
first?: InputMaybe<Scalars['Int']> first?: InputMaybe<Scalars['Int']>
@ -7825,39 +7703,49 @@ export type GetProductsFromCollectionQuery = {
__typename?: 'PageInfo' __typename?: 'PageInfo'
hasNextPage: boolean hasNextPage: boolean
hasPreviousPage: boolean hasPreviousPage: boolean
startCursor?: string | null
endCursor?: string | null
} }
edges: Array<{ edges: Array<{
__typename?: 'ProductEdge' __typename?: 'ProductEdge'
node: { node: {
__typename?: 'Product' __typename?: 'Product'
id: string id: string
title: string
vendor: string
handle: string handle: string
priceRange: { availableForSale: boolean
__typename?: 'ProductPriceRange' title: string
minVariantPrice: { productType: string
__typename?: 'MoneyV2' vendor: string
amount: any variants: {
currencyCode: CurrencyCode __typename?: 'ProductVariantConnection'
} nodes: Array<{
} __typename?: 'ProductVariant'
images: { id: string
__typename?: 'ImageConnection' title: string
pageInfo: { requiresShipping: boolean
__typename?: 'PageInfo' availableForSale: boolean
hasNextPage: boolean selectedOptions: Array<{
hasPreviousPage: boolean __typename?: 'SelectedOption'
} name: string
edges: Array<{ value: string
__typename?: 'ImageEdge' }>
node: { image?: {
__typename?: 'Image' __typename?: 'Image'
url: any url: any
altText?: string | null altText?: string | null
width?: number | null width?: number | null
height?: number | null height?: number | null
} | null
priceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} }
compareAtPriceV2?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
}> }>
} }
} }
@ -7961,6 +7849,7 @@ export type GetPageQuery = {
export type GetProductBySlugQueryVariables = Exact<{ export type GetProductBySlugQueryVariables = Exact<{
slug: Scalars['String'] slug: Scalars['String']
withMetafields?: InputMaybe<Scalars['Boolean']>
}> }>
export type GetProductBySlugQuery = { export type GetProductBySlugQuery = {
@ -7981,35 +7870,28 @@ export type GetProductBySlugQuery = {
name: string name: string
values: Array<string> values: Array<string>
}> }>
priceRange: { seo: {
__typename?: 'ProductPriceRange' __typename?: 'SEO'
maxVariantPrice: { description?: string | null
__typename?: 'MoneyV2' title?: string | null
amount: any
currencyCode: CurrencyCode
}
minVariantPrice: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
} }
variants: { variants: {
__typename?: 'ProductVariantConnection' __typename?: 'ProductVariantConnection'
pageInfo: { nodes: Array<{
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
}
edges: Array<{
__typename?: 'ProductVariantEdge'
node: {
__typename?: 'ProductVariant' __typename?: 'ProductVariant'
id: string id: string
title: string title: string
sku?: string | null sku?: string | null
availableForSale: boolean availableForSale: boolean
requiresShipping: boolean requiresShipping: boolean
image?: {
__typename?: 'Image'
id?: string | null
altText?: string | null
url: any
width?: number | null
height?: number | null
} | null
selectedOptions: Array<{ selectedOptions: Array<{
__typename?: 'SelectedOption' __typename?: 'SelectedOption'
name: string name: string
@ -8025,33 +7907,28 @@ export type GetProductBySlugQuery = {
amount: any amount: any
currencyCode: CurrencyCode currencyCode: CurrencyCode
} | null } | null
}>
} }
metafields?: {
__typename?: 'MetafieldConnection'
nodes: Array<{
__typename?: 'Metafield'
key: string
value: string
namespace: string
description?: string | null
type: string
}> }>
} }
images: { images: {
__typename?: 'ImageConnection' __typename?: 'ImageConnection'
pageInfo: { nodes: Array<{
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
}
edges: Array<{
__typename?: 'ImageEdge'
node: {
__typename?: 'Image' __typename?: 'Image'
url: any url: any
altText?: string | null altText?: string | null
width?: number | null width?: number | null
height?: number | null height?: number | null
}
}> }>
} }
} | null } | null
} }
export type GetSiteInfoQueryVariables = Exact<{ [key: string]: never }>
export type GetSiteInfoQuery = {
__typename?: 'QueryRoot'
shop: { __typename?: 'Shop'; name: string }
}

View File

@ -2948,7 +2948,7 @@ type Country {
unitSystem: UnitSystem! unitSystem: UnitSystem!
} }
"The code designating a country/region, which generally follows ISO 3166-1 alpha-2 guidelines.\nIf a territory doesn't have a country code value in the `CountryCode` enum, it might be considered a subdivision\nof another country. For example, the territories associated with Spain are represented by the country code `ES`,\nand the territories associated with the United States of America are represented by the country code `US`.\n" "The code designating a country/region, which generally follows ISO 3166-1 alpha-2 guidelines.\nIf a territory doesn't have a country code value in the `CountryCode` enum, then it might be considered a subdivision\nof another country. For example, the territories associated with Spain are represented by the country code `ES`,\nand the territories associated with the United States of America are represented by the country code `US`.\n"
enum CountryCode { enum CountryCode {
""" """
Ascension Island. Ascension Island.
@ -9638,7 +9638,7 @@ type Payment implements Node {
nextActionUrl: URL nextActionUrl: URL
""" """
Whether or not the payment is still processing asynchronously. Whether the payment is still processing asynchronously.
""" """
ready: Boolean! ready: Boolean!
@ -11598,7 +11598,7 @@ type ShopPolicyWithDefault {
"The availability of a product variant at a particular location.\nLocal pick-up must be enabled in the store's shipping settings, otherwise this will return an empty result.\n" "The availability of a product variant at a particular location.\nLocal pick-up must be enabled in the store's shipping settings, otherwise this will return an empty result.\n"
type StoreAvailability { type StoreAvailability {
""" """
Whether or not this product variant is in-stock at this location. Whether the product variant is in-stock at this location.
""" """
available: Boolean! available: Boolean!

View File

@ -5,8 +5,10 @@ import type {
import { GetProductOperation } from '../../types/product' import { GetProductOperation } from '../../types/product'
import { normalizeProduct, getProductQuery } from '../../utils' import { normalizeProduct, getProductQuery } from '../../utils'
import type { ShopifyConfig, Provider } from '..' import type { ShopifyConfig, Provider } from '..'
import { import {
GetProductBySlugQuery, GetProductBySlugQuery,
GetProductBySlugQueryVariables,
Product as ShopifyProduct, Product as ShopifyProduct,
} from '../../../schema' } from '../../../schema'
@ -38,10 +40,12 @@ export default function getProductOperation({
preview?: boolean preview?: boolean
}): Promise<T['data']> { }): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(cfg) const { fetch, locale } = commerce.getConfig(cfg)
const { const {
data: { productByHandle }, data: { productByHandle },
} = await fetch<GetProductBySlugQuery>( } = await fetch<
GetProductBySlugQuery,
Partial<GetProductBySlugQueryVariables>
>(
query, query,
{ {
variables, variables,

View File

@ -5,9 +5,7 @@ import type {
import { GetSiteInfoQueryVariables } from '../../../schema' import { GetSiteInfoQueryVariables } from '../../../schema'
import type { ShopifyConfig, Provider } from '..' import type { ShopifyConfig, Provider } from '..'
import { GetSiteInfoOperation } from '../../types/site' import { GetSiteInfoOperation } from '../../types/site'
import { getBrands, getCategories } from '../../utils'
import { getBrands } from '../utils/get-brands'
import { getCategories } from '../utils/get-categories'
export const getSiteInfoQuery = /* GraphQL */ ` export const getSiteInfoQuery = /* GraphQL */ `
query getSiteInfo { query getSiteInfo {

View File

@ -6,7 +6,7 @@ import getProduct from './get-product'
import getSiteInfo from './get-site-info' import getSiteInfo from './get-site-info'
import login from './login' import login from './login'
export default { const operations = {
getAllPages, getAllPages,
getPage, getPage,
getAllProducts, getAllProducts,
@ -15,3 +15,5 @@ export default {
getSiteInfo, getSiteInfo,
login, login,
} }
export default operations

View File

@ -2,6 +2,7 @@
"provider": "shopify", "provider": "shopify",
"features": { "features": {
"wishlist": false, "wishlist": false,
"customerAuth": true "customerAuth": true,
"metafields": true
} }
} }

View File

@ -1 +1,51 @@
import * as Core from '@vercel/commerce/types/product'
export * from '@vercel/commerce/types/product' export * from '@vercel/commerce/types/product'
type LiteralUnion<T extends string> = T | Omit<T, T>
export type MetaieldTypes =
| 'integer'
| 'boolean'
| 'color'
| 'json'
| 'date'
| 'file_reference'
| 'date_time'
| 'dimension'
| 'multi_line_text_field'
| 'number_decimal'
| 'number_integer'
| 'page_reference'
| 'product_reference'
| 'rating'
| 'single_line_text_field'
| 'url'
| 'variant_reference'
| 'volume'
| 'weight'
| 'list.color'
| 'list.date'
| 'list.date_time'
| 'list.dimension'
| 'list.file_reference'
| 'list.number_integer'
| 'list.number_decimal'
| 'list.page_reference'
| 'list.product_reference'
| 'list.rating'
| 'list.single_line_text_field'
| 'list.url'
| 'list.variant_reference'
| 'list.volume'
| 'list.weight'
export type MetafieldType = LiteralUnion<MetaieldTypes>
export type ProductMetafield = Core.ProductMetafield & {
type?: MetafieldType
}
export type Product = Core.Product & {
metafields?: ProductMetafield[]
}

View File

@ -74,7 +74,7 @@ export const cartDetailsFragment = /* GraphQL */ `
` `
export const userErrorsFragment = /* GraphQL */ ` export const userErrorsFragment = /* GraphQL */ `
fragment userErrors on UserError { fragment userErrors on CartUserError {
code code
field field
message message

View File

@ -1,5 +1,5 @@
export const customerUserErrorsFragment = /* GraphQL */ ` export const customerUserErrorsFragment = /* GraphQL */ `
fragment customerUserErrors on CustomerUserErrors { fragment customerUserErrors on CustomerUserError {
code code
field field
message message
@ -8,8 +8,7 @@ export const customerUserErrorsFragment = /* GraphQL */ `
export const customerAccessTokenFragment = /* GraphQL */ ` export const customerAccessTokenFragment = /* GraphQL */ `
fragment customerAccessToken on CustomerAccessToken { fragment customerAccessToken on CustomerAccessToken {
code accessToken
field expiresAt
message
} }
` `

View File

@ -1,11 +1,11 @@
import { ShopifyConfig } from '..' import { ShopifyConfig } from '../api'
import { import {
GetAllProductVendorsQuery, GetAllProductVendorsQuery,
GetAllProductVendorsQueryVariables, GetAllProductVendorsQueryVariables,
} from '../../../schema' } from '../../schema'
import { getAllProductVendors } from '../../utils/queries' import { getAllProductVendors } from './queries'
export type Brand = { export type Brand = {
entityId: string entityId: string

View File

@ -1,7 +1,8 @@
import { ShopifyConfig } from '..' import { ShopifyConfig } from '../api'
import type { Category } from '../../types/site' import type { Category } from '../types/site'
import { CollectionEdge } from '../../../schema' import { CollectionEdge } from '../../schema'
import { normalizeCategory, getSiteCollectionsQuery } from '../../utils' import { normalizeCategory } from '../utils/normalize'
import { getSiteCollectionsQuery } from './queries'
export const getCategories = async ({ export const getCategories = async ({
fetch, fetch,

View File

@ -1,8 +1,9 @@
import { FetcherError } from '@vercel/commerce/utils/errors' import { FetcherError } from '@vercel/commerce/utils/errors'
export function getError(err: any[] | string | null, status: number) { export function getError(err: any[] | string | null, status: number) {
if (process.env.NODE_ENV !== 'production') {
console.log(JSON.stringify(err, null, 2)) console.log(JSON.stringify(err, null, 2))
}
const errors = Array.isArray(err) const errors = Array.isArray(err)
? err ? err
: [{ message: err || 'Failed to fetch Shopify API' }] : [{ message: err || 'Failed to fetch Shopify API' }]

View File

@ -3,6 +3,8 @@ export { handleFetchResponse } from './handle-fetch-response'
export { cartCreate } from './cart-create' export { cartCreate } from './cart-create'
export { handleLogin, handleAutomaticLogin } from './handle-login' export { handleLogin, handleAutomaticLogin } from './handle-login'
export { handleAccountActivation } from './handle-account-activation' export { handleAccountActivation } from './handle-account-activation'
export { getCategories } from './get-categories'
export { getBrands } from './get-brands'
export * from './helpers' export * from './helpers'
export * from './queries' export * from './queries'

View File

@ -9,10 +9,14 @@ export const customerActivateMutation = /* GraphQL */ `
customer { customer {
id id
} }
customerAccessToken {
...customerAccessToken ...customerAccessToken
}
customerUserErrors {
...customerUserErrors ...customerUserErrors
} }
} }
}
${customerUserErrorsFragment} ${customerUserErrorsFragment}
${customerAccessTokenFragment} ${customerAccessTokenFragment}
` `
@ -22,10 +26,14 @@ export const customerActivateByUrlMutation = /* GraphQL */ `
customer { customer {
id id
} }
customerAccessToken {
...customerAccessToken ...customerAccessToken
}
customerUserErrors {
...customerUserErrors ...customerUserErrors
} }
} }
}
${customerUserErrorsFragment} ${customerUserErrorsFragment}
${customerAccessTokenFragment} ${customerAccessTokenFragment}
` `
@ -33,10 +41,14 @@ export const customerActivateByUrlMutation = /* GraphQL */ `
export const customerAccessTokenCreateMutation = /* GraphQL */ ` export const customerAccessTokenCreateMutation = /* GraphQL */ `
mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) { mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) {
customerAccessTokenCreate(input: $input) { customerAccessTokenCreate(input: $input) {
customerAccessToken {
...customerAccessToken ...customerAccessToken
}
customerUserErrors {
...customerUserErrors ...customerUserErrors
} }
} }
}
${customerUserErrorsFragment} ${customerUserErrorsFragment}
${customerAccessTokenFragment} ${customerAccessTokenFragment}
` `
@ -46,16 +58,20 @@ export const customerAccessTokenDeleteMutation = /* GraphQL */ `
customerAccessTokenDelete(customerAccessToken: $customerAccessToken) { customerAccessTokenDelete(customerAccessToken: $customerAccessToken) {
deletedAccessToken deletedAccessToken
deletedCustomerAccessTokenId deletedCustomerAccessTokenId
...customerUserErrors userErrors {
field
message
}
} }
} }
${customerUserErrorsFragment}
` `
export const customerCreateMutation = /* GraphQL */ ` export const customerCreateMutation = /* GraphQL */ `
mutation customerCreate($input: CustomerCreateInput!) { mutation customerCreate($input: CustomerCreateInput!) {
customerCreate(input: $input) { customerCreate(input: $input) {
customerUserErrors {
...customerUserErrors ...customerUserErrors
}
customer { customer {
id id
} }

View File

@ -14,6 +14,7 @@ import {
PageEdge, PageEdge,
Collection, Collection,
CartDetailsFragment, CartDetailsFragment,
MetafieldConnection,
} from '../../schema' } from '../../schema'
import { colorMap } from './colors' import { colorMap } from './colors'
import { CommerceError } from '@vercel/commerce/utils/errors' import { CommerceError } from '@vercel/commerce/utils/errors'
@ -114,7 +115,6 @@ export function normalizeProduct({
...rest ...rest
}: ShopifyProduct): Product { }: ShopifyProduct): Product {
const variant = variants?.nodes?.[0] const variant = variants?.nodes?.[0]
return { return {
id, id,
name, name,
@ -132,11 +132,13 @@ export function normalizeProduct({
.filter((o) => o.name !== 'Title') // By default Shopify adds a 'Title' name when there's only one option. We don't need it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095 .filter((o) => o.name !== 'Title') // By default Shopify adds a 'Title' name when there's only one option. We don't need it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095
.map((o) => normalizeProductOption(o)) .map((o) => normalizeProductOption(o))
: [], : [],
metafields: metafields?.nodes?.map((m) => m) || [],
...(description && { description }), ...(description && { description }),
...(descriptionHtml && { descriptionHtml }), ...(descriptionHtml && { descriptionHtml }),
...rest, ...rest,
} }
} }
function normalizeLineItem({ function normalizeLineItem({
node: { id, merchandise: variant, quantity }, node: { id, merchandise: variant, quantity },
}: { }: {

View File

@ -1,5 +1,5 @@
export const getProductQuery = /* GraphQL */ ` export const getProductQuery = /* GraphQL */ `
query getProductBySlug($slug: String!) { query getProductBySlug($slug: String!, $withMetafields: Boolean = false) {
productByHandle(handle: $slug) { productByHandle(handle: $slug) {
id id
handle handle
@ -46,7 +46,16 @@ export const getProductQuery = /* GraphQL */ `
} }
} }
} }
images(first: 15) { metafields(first: 25) @include(if: $withMetafields) {
nodes {
key
value
namespace
description
type
}
}
images(first: 25) {
nodes { nodes {
url url
altText altText
@ -55,15 +64,5 @@ export const getProductQuery = /* GraphQL */ `
} }
} }
} }
shop {
shippingPolicy {
body
handle
}
refundPolicy {
body
handle
}
}
} }
` `

View File

@ -4,6 +4,7 @@
"search": true, "search": true,
"wishlist": false, "wishlist": false,
"customerAuth": false, "customerAuth": false,
"customCheckout": false "customCheckout": false,
"metafields": false
} }
} }

View File

@ -10,6 +10,15 @@ const Head: VFC = () => {
content="width=device-width, initial-scale=1" content="width=device-width, initial-scale=1"
/> />
<link rel="manifest" href="/site.webmanifest" key="site-manifest" /> <link rel="manifest" href="/site.webmanifest" key="site-manifest" />
{process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN && (
<link
rel="preconnect"
href={`https://${process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN.replace(
/(^\w+:|^)\/\//,
''
)}`}
/>
)}
</SEO> </SEO>
) )
} }

View File

@ -4,7 +4,7 @@ import { WishlistButton } from '@components/wishlist'
import ProductSidebar from '../ProductSidebar' import ProductSidebar from '../ProductSidebar'
import ProductTag from '../ProductTag' import ProductTag from '../ProductTag'
import { useProduct } from '../product-context' import { useProduct } from '../context'
import s from './ProductDetails.module.css' import s from './ProductDetails.module.css'
import ProductSlider from '../ProductSlider' import ProductSlider from '../ProductSlider'

View File

@ -1,6 +1,6 @@
import { memo } from 'react' import { memo } from 'react'
import { Swatch } from '@components/product' import { Swatch } from '@components/product'
import { useProduct } from '../product-context' import { useProduct } from '../context'
const ProductOptions: React.FC = () => { const ProductOptions: React.FC = () => {
const { product, selectedOptions, setSelectedOptions } = useProduct() const { product, selectedOptions, setSelectedOptions } = useProduct()

View File

@ -4,8 +4,7 @@ import { FC, useState } from 'react'
import { ProductOptions } from '@components/product' import { ProductOptions } from '@components/product'
import { Button, Text, Rating, Collapse, useUI } from '@components/ui' import { Button, Text, Rating, Collapse, useUI } from '@components/ui'
import { useProduct } from '../product-context' import { useProduct } from '../context'
interface ProductSidebarProps { interface ProductSidebarProps {
className?: string className?: string
} }
@ -60,6 +59,23 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ className }) => {
)} )}
</div> </div>
<div className="mt-6"> <div className="mt-6">
{process.env.COMMERCE_METAFIELDS_ENABLED &&
product.metafields &&
product.metafields?.length > 0 && (
<Collapse title="Metafields">
<ul className="flex flex-col space-y-2 divide-y divide-dashed">
{product.metafields.map((m) => (
<li
className="flex space-x-2 justify-start items-center text-sm pt-2"
key={m.key}
>
<span className="font-bold capitalize">{m.key}</span>:
<span>{m.value}</span>
</li>
))}
</ul>
</Collapse>
)}
<Collapse title="Care"> <Collapse title="Care">
This is a limited edition production run. Printing starts when the This is a limited edition production run. Printing starts when the
drop ends. drop ends.

View File

@ -10,7 +10,7 @@ import cn from 'clsx'
import { a } from '@react-spring/web' import { a } from '@react-spring/web'
import s from './ProductSlider.module.css' import s from './ProductSlider.module.css'
import ProductSliderControl from '../ProductSliderControl' import ProductSliderControl from '../ProductSliderControl'
import { useProduct } from '../product-context' import { useProduct } from '../context'
interface ProductSliderProps { interface ProductSliderProps {
children: React.ReactNode[] children: React.ReactNode[]
@ -21,7 +21,7 @@ const ProductSlider: React.FC<ProductSliderProps> = ({
children, children,
className = '', className = '',
}) => { }) => {
const { imageIndex, resetImageIndex } = useProduct() const { imageIndex, setImageIndex } = useProduct()
const [currentSlide, setCurrentSlide] = useState(imageIndex ?? 0) const [currentSlide, setCurrentSlide] = useState(imageIndex ?? 0)
const [isMounted, setIsMounted] = useState(false) const [isMounted, setIsMounted] = useState(false)
const sliderContainerRef = useRef<HTMLDivElement>(null) const sliderContainerRef = useRef<HTMLDivElement>(null)
@ -30,10 +30,10 @@ const ProductSlider: React.FC<ProductSliderProps> = ({
const [ref, slider] = useKeenSlider<HTMLDivElement>({ const [ref, slider] = useKeenSlider<HTMLDivElement>({
loop: true, loop: true,
slides: { perView: 1 }, slides: { perView: 1 },
created: () => setIsMounted(true),
dragStarted: () => { dragStarted: () => {
resetImageIndex() setImageIndex(null)
}, },
created: () => setIsMounted(true),
slideChanged(s) { slideChanged(s) {
const slideNumber = s.track.details.rel const slideNumber = s.track.details.rel
setCurrentSlide(slideNumber) setCurrentSlide(slideNumber)
@ -79,22 +79,22 @@ const ProductSlider: React.FC<ProductSliderProps> = ({
}, []) }, [])
useEffect(() => { useEffect(() => {
if (imageIndex && imageIndex !== currentSlide) { if (imageIndex !== null) {
slider.current?.moveToIdx(imageIndex, undefined, { slider.current?.moveToIdx(imageIndex, false, {
duration: 0, duration: 0,
}) })
} }
}, [imageIndex, currentSlide, slider]) }, [imageIndex, slider])
const onPrev = React.useCallback(() => { const onPrev = React.useCallback(() => {
resetImageIndex() setImageIndex(null)
slider.current?.prev() slider.current?.prev()
}, [resetImageIndex, slider]) }, [setImageIndex, slider])
const onNext = React.useCallback(() => { const onNext = React.useCallback(() => {
resetImageIndex() setImageIndex(null)
slider.current?.next() slider.current?.next()
}, [resetImageIndex, slider]) }, [setImageIndex, slider])
return ( return (
<div className={cn(s.root, className)} ref={sliderContainerRef}> <div className={cn(s.root, className)} ref={sliderContainerRef}>
@ -133,7 +133,7 @@ const ProductSlider: React.FC<ProductSliderProps> = ({
}), }),
id: `thumb-${idx}`, id: `thumb-${idx}`,
onClick: () => { onClick: () => {
resetImageIndex() setImageIndex(null)
slider.current?.moveToIdx(idx) slider.current?.moveToIdx(idx)
}, },
}, },

View File

@ -4,7 +4,7 @@ import type { Product } from '@commerce/types/product'
import { ProductCard } from '@components/product' import { ProductCard } from '@components/product'
import { Container, Text } from '@components/ui' import { Container, Text } from '@components/ui'
import { ProductProvider } from '../product-context' import { ProductProvider } from '../context'
import ProductDetails from '../ProductDetails/ProductDetails' import ProductDetails from '../ProductDetails/ProductDetails'
import { SEO } from '@components/common' import { SEO } from '@components/common'

View File

@ -6,24 +6,18 @@ import {
SelectedOptions, SelectedOptions,
} from './helpers' } from './helpers'
import React, { import React, { FC, useMemo, useState, ReactNode, useEffect } from 'react'
FC,
ReactNode,
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import usePrice from '@framework/product/use-price' import usePrice from '@framework/product/use-price'
export interface ProductContextValue { export interface ProductContextValue {
product: Product product: Product
imageIndex: number | null imageIndex: number | null
setImageIndex: React.Dispatch<React.SetStateAction<number | null>>
price: string price: string
variant: ProductVariant variant: ProductVariant
selectedOptions: SelectedOptions selectedOptions: SelectedOptions
setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>> setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>>
resetImageIndex: () => void
} }
export const ProductContext = React.createContext<ProductContextValue | null>( export const ProductContext = React.createContext<ProductContextValue | null>(
@ -43,12 +37,16 @@ export const ProductProvider: FC<ProductProviderProps> = ({
}) => { }) => {
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({}) const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({})
const [imageIndex, setImageIndex] = useState<number | null>(null) const [imageIndex, setImageIndex] = useState<number | null>(null)
const resetImageIndex = useCallback(() => setImageIndex(null), [])
const variant = useMemo(() => { useEffect(
const v = getProductVariant(product, selectedOptions) () => selectDefaultOptionFromProduct(product, setSelectedOptions),
return v || product.variants[0] [product]
}, [product, selectedOptions]) )
const variant = useMemo(
() => getProductVariant(product, selectedOptions) || product.variants[0],
[product, selectedOptions]
)
const { price } = usePrice({ const { price } = usePrice({
amount: variant?.price?.value || product.price.value, amount: variant?.price?.value || product.price.value,
@ -57,32 +55,29 @@ export const ProductProvider: FC<ProductProviderProps> = ({
}) })
useEffect(() => { useEffect(() => {
selectDefaultOptionFromProduct(product, setSelectedOptions) const idx = product.images.findIndex((image: ProductImage) => {
}, [product]) return image.url === variant?.image?.url
})
useEffect(() => { if (idx !== -1) {
const idx = product.images.findIndex(
(image: ProductImage) => image.url === variant?.image?.url
)
if (idx) {
setImageIndex(idx) setImageIndex(idx)
} }
}, [variant, product]) }, [variant, product])
return ( const value = useMemo(
<ProductContext.Provider () => ({
value={{
price, price,
product, product,
variant, variant,
imageIndex, imageIndex,
resetImageIndex, setImageIndex,
selectedOptions, selectedOptions,
setSelectedOptions, setSelectedOptions,
}} }),
> [price, product, variant, imageIndex, selectedOptions]
{children} )
</ProductContext.Provider>
return (
<ProductContext.Provider value={value}>{children}</ProductContext.Provider>
) )
} }

View File

@ -18,7 +18,10 @@ export async function getStaticProps({
const pagesPromise = commerce.getAllPages({ config, preview }) const pagesPromise = commerce.getAllPages({ config, preview })
const siteInfoPromise = commerce.getSiteInfo({ config, preview }) const siteInfoPromise = commerce.getSiteInfo({ config, preview })
const productPromise = commerce.getProduct({ const productPromise = commerce.getProduct({
variables: { slug: params!.slug }, variables: {
slug: params!.slug,
withMetafields: Boolean(process.env.COMMERCE_METAFIELDS_ENABLED),
},
config, config,
preview, preview,
}) })

1571
yarn.lock

File diff suppressed because it is too large Load Diff