Implement Shopify Provider

This commit is contained in:
cond0r
2021-02-04 13:23:44 +02:00
parent c06d9dae3a
commit 14c3f961b3
61 changed files with 16405 additions and 20 deletions

View 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

View File

@@ -0,0 +1,3 @@
import zeitFetch from '@vercel/fetch'
export default zeitFetch()

View 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

View 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}`,
}
: {}
}

View 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

View File

@@ -0,0 +1,15 @@
const checkoutCreateMutation = /* GraphQL */ `
mutation {
checkoutCreate(input: {}) {
userErrors {
message
field
}
checkout {
id
webUrl
}
}
}
`
export default checkoutCreateMutation

View 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

View File

@@ -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

View File

@@ -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

View 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'

View File

@@ -0,0 +1,14 @@
const getSiteCollectionsQuery = /* GraphQL */ `
query getSiteCollections($first: Int!) {
collections(first: $first) {
edges {
node {
id
title
handle
}
}
}
}
`
export default getSiteCollectionsQuery

View 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

View File

@@ -0,0 +1,16 @@
const getAllProductsPathsQuery = /* GraphQL */ `
query getAllProductPaths($first: Int!) {
products(first: $first) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
node {
handle
}
}
}
}
`
export default getAllProductsPathsQuery

View 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

View 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

View 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

View 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'

View 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}`,
})
)
}