mirror of
https://github.com/vercel/commerce.git
synced 2025-07-26 19:51:23 +00:00
Implement Shopify Provider
This commit is contained in:
38
framework/shopify/utils/fetch-graphql-api.ts
Normal file
38
framework/shopify/utils/fetch-graphql-api.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { GraphQLFetcher } from '@commerce/api'
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
import { getConfig } from '../api/index'
|
||||
import fetch from './fetch'
|
||||
|
||||
const fetchGraphqlApi: GraphQLFetcher = async (
|
||||
query: string,
|
||||
{ variables } = {},
|
||||
fetchOptions
|
||||
) => {
|
||||
const { commerceUrl, apiToken } = getConfig()
|
||||
|
||||
const res = await fetch(`https://${commerceUrl}/api/2021-01/graphql.json`, {
|
||||
...fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Shopify-Storefront-Access-Token': apiToken,
|
||||
...fetchOptions?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
})
|
||||
|
||||
const json = await res.json()
|
||||
|
||||
if (json.errors) {
|
||||
throw new FetcherError({
|
||||
errors: json.errors ?? [{ message: 'Failed to fetch Shopify API' }],
|
||||
status: res.status,
|
||||
})
|
||||
}
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
export default fetchGraphqlApi
|
3
framework/shopify/utils/fetch.ts
Normal file
3
framework/shopify/utils/fetch.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import zeitFetch from '@vercel/fetch'
|
||||
|
||||
export default zeitFetch()
|
8
framework/shopify/utils/get-checkout-id.ts
Normal file
8
framework/shopify/utils/get-checkout-id.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { SHOPIFY_CHECKOUT_COOKIE } from '..'
|
||||
|
||||
const getCheckoutId = (id?: string) => {
|
||||
return id ?? Cookies.get(SHOPIFY_CHECKOUT_COOKIE)
|
||||
}
|
||||
|
||||
export default getCheckoutId
|
15
framework/shopify/utils/get-search-variables.ts
Normal file
15
framework/shopify/utils/get-search-variables.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const searchByProductType = (search?: string) => {
|
||||
return search
|
||||
? {
|
||||
query: `product_type:${search}`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
|
||||
export const searchByTag = (categoryPath?: string) => {
|
||||
return categoryPath
|
||||
? {
|
||||
query: `tag:${categoryPath}`,
|
||||
}
|
||||
: {}
|
||||
}
|
32
framework/shopify/utils/get-sort-variables.ts
Normal file
32
framework/shopify/utils/get-sort-variables.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
const getSortVariables = (sort?: string) => {
|
||||
let output = {}
|
||||
switch (sort) {
|
||||
case 'price-asc':
|
||||
output = {
|
||||
sortKey: 'PRICE',
|
||||
reverse: false,
|
||||
}
|
||||
break
|
||||
case 'price-desc':
|
||||
output = {
|
||||
sortKey: 'PRICE',
|
||||
reverse: true,
|
||||
}
|
||||
break
|
||||
case 'trending-desc':
|
||||
output = {
|
||||
sortKey: 'BEST_SELLING',
|
||||
reverse: false,
|
||||
}
|
||||
break
|
||||
case 'latest-desc':
|
||||
output = {
|
||||
sortKey: 'CREATED_AT',
|
||||
reverse: true,
|
||||
}
|
||||
break
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
export default getSortVariables
|
15
framework/shopify/utils/mutations/checkout-create.ts
Normal file
15
framework/shopify/utils/mutations/checkout-create.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
const checkoutCreateMutation = /* GraphQL */ `
|
||||
mutation {
|
||||
checkoutCreate(input: {}) {
|
||||
userErrors {
|
||||
message
|
||||
field
|
||||
}
|
||||
checkout {
|
||||
id
|
||||
webUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default checkoutCreateMutation
|
16
framework/shopify/utils/mutations/checkout-line-item-add.ts
Normal file
16
framework/shopify/utils/mutations/checkout-line-item-add.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { checkoutDetailsFragment } from '../queries/get-checkout-query'
|
||||
|
||||
const checkoutLineItemAddMutation = /* GraphQL */ `
|
||||
mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemInput!]!) {
|
||||
checkoutLineItemsAdd(checkoutId: $checkoutId, lineItems: $lineItems) {
|
||||
userErrors {
|
||||
message
|
||||
field
|
||||
}
|
||||
checkout {
|
||||
${checkoutDetailsFragment}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default checkoutLineItemAddMutation
|
@@ -0,0 +1,19 @@
|
||||
import { checkoutDetailsFragment } from '../queries/get-checkout-query'
|
||||
|
||||
const checkoutLineItemRemoveMutation = /* GraphQL */ `
|
||||
mutation($checkoutId: ID!, $lineItemIds: [ID!]!) {
|
||||
checkoutLineItemsRemove(
|
||||
checkoutId: $checkoutId
|
||||
lineItemIds: $lineItemIds
|
||||
) {
|
||||
userErrors {
|
||||
message
|
||||
field
|
||||
}
|
||||
checkout {
|
||||
${checkoutDetailsFragment}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default checkoutLineItemRemoveMutation
|
@@ -0,0 +1,16 @@
|
||||
import { checkoutDetailsFragment } from '../queries/get-checkout-query'
|
||||
|
||||
const checkoutLineItemUpdateMutation = /* GraphQL */ `
|
||||
mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemUpdateInput!]!) {
|
||||
checkoutLineItemsUpdate(checkoutId: $checkoutId, lineItems: $lineItems) {
|
||||
userErrors {
|
||||
message
|
||||
field
|
||||
}
|
||||
checkout {
|
||||
${checkoutDetailsFragment}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default checkoutLineItemUpdateMutation
|
4
framework/shopify/utils/mutations/index.ts
Normal file
4
framework/shopify/utils/mutations/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as checkoutCreateMutation } from './checkout-create'
|
||||
export { default as checkoutLineItemAddMutation } from './checkout-line-item-add'
|
||||
export { default as checkoutLineItemUpdateMutation } from './checkout-create'
|
||||
export { default as checkoutLineItemRemoveMutation } from './checkout-line-item-remove'
|
14
framework/shopify/utils/queries/get-all-collections-query.ts
Normal file
14
framework/shopify/utils/queries/get-all-collections-query.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const getSiteCollectionsQuery = /* GraphQL */ `
|
||||
query getSiteCollections($first: Int!) {
|
||||
collections(first: $first) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
handle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default getSiteCollectionsQuery
|
17
framework/shopify/utils/queries/get-all-pages-query.ts
Normal file
17
framework/shopify/utils/queries/get-all-pages-query.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const getAllPagesQuery = /* GraphQL */ `
|
||||
query($first: Int!) {
|
||||
pages(first: $first) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
handle
|
||||
body
|
||||
bodySummary
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default getAllPagesQuery
|
@@ -0,0 +1,16 @@
|
||||
const getAllProductsPathsQuery = /* GraphQL */ `
|
||||
query getAllProductPaths($first: Int!) {
|
||||
products(first: $first) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
handle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default getAllProductsPathsQuery
|
47
framework/shopify/utils/queries/get-all-products-query.ts
Normal file
47
framework/shopify/utils/queries/get-all-products-query.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
const getAllProductsQuery = /* GraphQL */ `
|
||||
query getAllProducts(
|
||||
$first: Int = 250
|
||||
$query: String = ""
|
||||
$sortKey: ProductSortKeys = RELEVANCE
|
||||
$reverse: Boolean = false
|
||||
) {
|
||||
products(
|
||||
first: $first
|
||||
sortKey: $sortKey
|
||||
reverse: $reverse
|
||||
query: $query
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
vendor
|
||||
handle
|
||||
description
|
||||
priceRange {
|
||||
minVariantPrice {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
}
|
||||
images(first: 1) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
src
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default getAllProductsQuery
|
40
framework/shopify/utils/queries/get-checkout-query.ts
Normal file
40
framework/shopify/utils/queries/get-checkout-query.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export const checkoutDetailsFragment = /* GraphQL */ `
|
||||
id
|
||||
webUrl
|
||||
subtotalPrice
|
||||
totalTax
|
||||
totalPrice
|
||||
currencyCode
|
||||
lineItems(first: 250) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
variant {
|
||||
id
|
||||
title
|
||||
image {
|
||||
src
|
||||
}
|
||||
price
|
||||
}
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const getCheckoutQuery = /* GraphQL */ `
|
||||
query($checkoutId: ID!) {
|
||||
node(id: $checkoutId) {
|
||||
... on Checkout {
|
||||
${checkoutDetailsFragment}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export default getCheckoutQuery
|
59
framework/shopify/utils/queries/get-product-query.ts
Normal file
59
framework/shopify/utils/queries/get-product-query.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
const getProductQuery = /* GraphQL */ `
|
||||
query getProductBySlug($slug: String!) {
|
||||
productByHandle(handle: $slug) {
|
||||
id
|
||||
handle
|
||||
title
|
||||
productType
|
||||
vendor
|
||||
description
|
||||
descriptionHtml
|
||||
options {
|
||||
id
|
||||
name
|
||||
values
|
||||
}
|
||||
priceRange {
|
||||
maxVariantPrice {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
minVariantPrice {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
}
|
||||
variants(first: 250) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
selectedOptions {
|
||||
name
|
||||
value
|
||||
}
|
||||
price
|
||||
compareAtPrice
|
||||
}
|
||||
}
|
||||
}
|
||||
images(first: 250) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
src
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default getProductQuery
|
6
framework/shopify/utils/queries/index.ts
Normal file
6
framework/shopify/utils/queries/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { default as getSiteCollectionsQuery } from './get-all-collections-query'
|
||||
export { default as getProductQuery } from './get-all-products-paths-query'
|
||||
export { default as getAllProductsQuery } from './get-all-products-query'
|
||||
export { default as getAllProductsPathtsQuery } from './get-all-products-paths-query'
|
||||
export { default as getCheckoutQuery } from './get-checkout-query'
|
||||
export { default as getAllPagesQuery } from './get-all-pages-query'
|
96
framework/shopify/utils/to-commerce-products.ts
Normal file
96
framework/shopify/utils/to-commerce-products.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
Product as ShopifyProduct,
|
||||
ImageEdge,
|
||||
SelectedOption,
|
||||
ProductEdge,
|
||||
ProductVariantEdge,
|
||||
MoneyV2,
|
||||
ProductOption,
|
||||
} from '../schema'
|
||||
|
||||
const money = ({ amount, currencyCode }: MoneyV2) => {
|
||||
return {
|
||||
value: +amount,
|
||||
currencyCode,
|
||||
}
|
||||
}
|
||||
|
||||
const tranformProductOption = ({
|
||||
id,
|
||||
name: displayName,
|
||||
values,
|
||||
}: ProductOption) => ({
|
||||
__typename: 'MultipleChoiceOption',
|
||||
displayName,
|
||||
values: values.map((value) => ({
|
||||
label: value,
|
||||
})),
|
||||
})
|
||||
|
||||
const transformImages = (images: ImageEdge[]) =>
|
||||
images.map(({ node: { src: url } }) => ({
|
||||
url,
|
||||
}))
|
||||
|
||||
export const toCommerceProduct = (product: ShopifyProduct) => {
|
||||
const {
|
||||
id,
|
||||
title: name,
|
||||
vendor,
|
||||
images: { edges: images },
|
||||
variants: { edges: variants },
|
||||
description,
|
||||
handle: slug,
|
||||
priceRange,
|
||||
options,
|
||||
} = product
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
slug,
|
||||
vendor,
|
||||
description,
|
||||
path: `/${slug}`,
|
||||
price: money(priceRange.minVariantPrice),
|
||||
images: transformImages(images),
|
||||
variants: variants.map(
|
||||
({ node: { id, selectedOptions } }: ProductVariantEdge) => {
|
||||
return {
|
||||
id,
|
||||
options: selectedOptions.map(({ name, value }: SelectedOption) =>
|
||||
tranformProductOption({
|
||||
id,
|
||||
name,
|
||||
values: [value],
|
||||
} as ProductOption)
|
||||
),
|
||||
}
|
||||
}
|
||||
),
|
||||
options: options.map((option: ProductOption) =>
|
||||
tranformProductOption(option)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export default function toCommerceProducts(products: ProductEdge[]) {
|
||||
return products.map(
|
||||
({
|
||||
node: {
|
||||
id,
|
||||
title: name,
|
||||
images: { edges: images },
|
||||
handle: slug,
|
||||
priceRange,
|
||||
},
|
||||
}: ProductEdge) => ({
|
||||
id,
|
||||
name,
|
||||
images: transformImages(images),
|
||||
price: money(priceRange.minVariantPrice),
|
||||
slug,
|
||||
path: `/${slug}`,
|
||||
})
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user