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 = {
url: string
@ -31,12 +31,17 @@ export type ProductVariant = {
id: string | number
options: ProductOption[]
availableForSale?: boolean
// Product variant price
price?: ProductPrice
// Product variant image
image?: ProductImage
}
export type ProductMetafield = {
key: string
value: string
description?: string
type?: string
}
export type Product = {
id: string
name: string
@ -51,6 +56,7 @@ export type Product = {
options: ProductOption[]
vendor?: string
seo?: SEO
metafields?: ProductMetafield[]
}
export type SearchProductsBody = {
@ -102,5 +108,5 @@ export type GetAllProductsOperation<T extends ProductTypes = ProductTypes> = {
export type GetProductOperation<T extends ProductTypes = ProductTypes> = {
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.
* 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`,
* 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']>
/** The URL where the customer needs to be redirected so they can complete the 3D Secure payment flow. */
nextActionUrl?: Maybe<Scalars['URL']>
/** Whether or not the payment is still processing asynchronously. */
/** Whether the payment is still processing asynchronously. */
ready: Scalars['Boolean']
/** A flag to indicate if the payment is to be done in test mode for gateways that support it. */
test: Scalars['Boolean']
@ -6458,7 +6458,7 @@ export type ShopPolicyWithDefault = {
*/
export type 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']
/** The location where this product variant is stocked at. */
location: Location
@ -6759,24 +6759,195 @@ export enum WeightUnit {
Pounds = 'POUNDS',
}
export type AssociateCustomerWithCheckoutMutationVariables = Exact<{
checkoutId: Scalars['ID']
customerAccessToken: Scalars['String']
}>
export type GetSiteInfoQueryVariables = Exact<{ [key: string]: never }>
export type AssociateCustomerWithCheckoutMutation = {
__typename?: 'Mutation'
checkoutCustomerAssociateV2?: {
__typename?: 'CheckoutCustomerAssociateV2Payload'
checkout?: { __typename?: 'Checkout'; id: string } | null
checkoutUserErrors: Array<{
__typename?: 'CheckoutUserError'
code?: CheckoutErrorCode | null
field?: Array<string> | null
message: string
export type GetSiteInfoQuery = {
__typename?: 'QueryRoot'
shop: { __typename?: 'Shop'; name: string }
}
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'
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
message: string
}
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
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<{
@ -7170,6 +7341,54 @@ export type CartLinesUpdateMutation = {
} | 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<{
input: CustomerAccessTokenCreateInput
}>
@ -7210,54 +7429,6 @@ export type CustomerAccessTokenDeleteMutation = {
} | 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<{
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<{
first?: InputMaybe<Scalars['Int']>
query?: InputMaybe<Scalars['String']>
@ -7421,33 +7547,41 @@ export type GetAllProductsQuery = {
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: {
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
}>
}
}
@ -7455,87 +7589,6 @@ export type GetAllProductsQuery = {
}
}
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'
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 GetCartQueryVariables = Exact<{
cartId: Scalars['ID']
}>
@ -7624,181 +7677,6 @@ export type GetCartQuery = {
} | 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<{
categoryId: Scalars['ID']
first?: InputMaybe<Scalars['Int']>
@ -7825,39 +7703,49 @@ export type GetProductsFromCollectionQuery = {
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
startCursor?: string | null
endCursor?: string | null
}
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: {
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
}>
}
}
@ -7961,6 +7849,7 @@ export type GetPageQuery = {
export type GetProductBySlugQueryVariables = Exact<{
slug: Scalars['String']
withMetafields?: InputMaybe<Scalars['Boolean']>
}>
export type GetProductBySlugQuery = {
@ -7981,77 +7870,65 @@ export type GetProductBySlugQuery = {
name: string
values: Array<string>
}>
priceRange: {
__typename?: 'ProductPriceRange'
maxVariantPrice: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
minVariantPrice: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
seo: {
__typename?: 'SEO'
description?: string | null
title?: string | null
}
variants: {
__typename?: 'ProductVariantConnection'
pageInfo: {
__typename?: 'PageInfo'
hasNextPage: boolean
hasPreviousPage: boolean
}
edges: Array<{
__typename?: 'ProductVariantEdge'
node: {
__typename?: 'ProductVariant'
id: string
title: string
sku?: string | null
availableForSale: boolean
requiresShipping: boolean
selectedOptions: Array<{
__typename?: 'SelectedOption'
name: string
value: string
}>
priceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
compareAtPriceV2?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
nodes: Array<{
__typename?: 'ProductVariant'
id: string
title: string
sku?: string | null
availableForSale: boolean
requiresShipping: boolean
image?: {
__typename?: 'Image'
id?: string | null
altText?: string | null
url: any
width?: number | null
height?: number | null
} | null
selectedOptions: Array<{
__typename?: 'SelectedOption'
name: string
value: string
}>
priceV2: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
}
compareAtPriceV2?: {
__typename?: 'MoneyV2'
amount: any
currencyCode: CurrencyCode
} | null
}>
}
metafields?: {
__typename?: 'MetafieldConnection'
nodes: Array<{
__typename?: 'Metafield'
key: string
value: string
namespace: string
description?: string | null
type: string
}>
}
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
}
nodes: Array<{
__typename?: 'Image'
url: any
altText?: string | null
width?: number | null
height?: number | 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!
}
"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 {
"""
Ascension Island.
@ -9638,7 +9638,7 @@ type Payment implements Node {
nextActionUrl: URL
"""
Whether or not the payment is still processing asynchronously.
Whether the payment is still processing asynchronously.
"""
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"
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!

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
"provider": "shopify",
"features": {
"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'
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 */ `
fragment userErrors on UserError {
fragment userErrors on CartUserError {
code
field
message

View File

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

View File

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

View File

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

View File

@ -1,8 +1,9 @@
import { FetcherError } from '@vercel/commerce/utils/errors'
export function getError(err: any[] | string | null, status: number) {
console.log(JSON.stringify(err, null, 2))
if (process.env.NODE_ENV !== 'production') {
console.log(JSON.stringify(err, null, 2))
}
const errors = Array.isArray(err)
? err
: [{ 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 { handleLogin, handleAutomaticLogin } from './handle-login'
export { handleAccountActivation } from './handle-account-activation'
export { getCategories } from './get-categories'
export { getBrands } from './get-brands'
export * from './helpers'
export * from './queries'

View File

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

View File

@ -14,6 +14,7 @@ import {
PageEdge,
Collection,
CartDetailsFragment,
MetafieldConnection,
} from '../../schema'
import { colorMap } from './colors'
import { CommerceError } from '@vercel/commerce/utils/errors'
@ -114,7 +115,6 @@ export function normalizeProduct({
...rest
}: ShopifyProduct): Product {
const variant = variants?.nodes?.[0]
return {
id,
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
.map((o) => normalizeProductOption(o))
: [],
metafields: metafields?.nodes?.map((m) => m) || [],
...(description && { description }),
...(descriptionHtml && { descriptionHtml }),
...rest,
}
}
function normalizeLineItem({
node: { id, merchandise: variant, quantity },
}: {

View File

@ -1,5 +1,5 @@
export const getProductQuery = /* GraphQL */ `
query getProductBySlug($slug: String!) {
query getProductBySlug($slug: String!, $withMetafields: Boolean = false) {
productByHandle(handle: $slug) {
id
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 {
url
altText
@ -55,15 +64,5 @@ export const getProductQuery = /* GraphQL */ `
}
}
}
shop {
shippingPolicy {
body
handle
}
refundPolicy {
body
handle
}
}
}
`

View File

@ -4,6 +4,7 @@
"search": true,
"wishlist": 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"
/>
<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>
)
}

View File

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

View File

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

View File

@ -4,8 +4,7 @@ import { FC, useState } from 'react'
import { ProductOptions } from '@components/product'
import { Button, Text, Rating, Collapse, useUI } from '@components/ui'
import { useProduct } from '../product-context'
import { useProduct } from '../context'
interface ProductSidebarProps {
className?: string
}
@ -60,6 +59,23 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ className }) => {
)}
</div>
<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">
This is a limited edition production run. Printing starts when the
drop ends.

View File

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

View File

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

View File

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

View File

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

1571
yarn.lock

File diff suppressed because it is too large Load Diff