[WIP] Node.js provider for the API (#252)

* Adding multiple initial files

* Updated the default cart endpoint

* Fixes

* Updated CommerceAPI class for better usage

* Adding more migration changes

* Taking multiple steps into better API types

* Adding more experimental types

* Removed many testing types

* Adding types, fixes and other updates

* Updated commerce types

* Updated types for hooks now using the API

* Updated mutation types

* Simplified cart types for the provider

* Updated cart hooks

* Remove normalizers from the hooks

* Updated cart endpoint

* Removed cart handlers

* bug fixes

* Improve quantity input behavior in cart item

* Removed endpoints folder

* Making progress on api operations

* Moved method

* Moved types

* Changed the way ops are created

* Added customer endpoint

* Login endpoint

* Added logout endpoint

* Add missing logout files

* Added signup endpoint

* Removed customers old endpoints

* Moved endpoints to nested folder

* Removed old customer endpoint builders

* Updated login operation

* Updated login operation

* Added getAllPages operation

* Renamed endpoint operations to handlers

* Changed import

* Renamed operations to handlers in usage

* Moved getAllPages everywhere

* Moved getPage

* Updated getPage usage

* Moved getSiteInfo

* Added def types for product

* Updated type

* moved products catalog endpoint

* removed old catalog endpoint

* Moved wishlist

* Removed commerce.endpoint

* Replaced references to commerce.endpoint

* Updated catalog products

* Moved checkout api

* Added the get customer wishlist operation

* Removed old wishlist stuff

* Added getAllProductPaths operation

* updated reference to operation

* Moved getAllProducts

* Updated getProduct operation

* Removed old getConfig and references

* Removed is-allowed-method from BC

* Updated types for auth hooks

* Updated useCustomer and core types

* Updated useData and util hooks

* Updated useSearch hook

* Updated types for useWishlist

* Added index for types

* Fixes

* Updated urls to the API

* Renamed fetchInput to fetcherInput

* Updated fetch type

* Fixes in search hook

* Updated Shopify Provider Structure (#340)

* Add codegen, update fragments & schemas

* Update checkout-create.ts

* Update checkout-create.ts

* Update README.md

* Update product mutations & queries

* Uptate customer fetch types

* Update schemas

* Start updates

* Moved Page, AllPages & Site Info

* Moved product, all products (paths)

* Add translations, update operations & fixes

* Update api endpoints, types & fixes

* Add api checkout endpoint

* Updates

* Fixes

* Update commerce.config.json

Co-authored-by: B <curciobelen@gmail.com>

* Added category type and normalizer

* updated init script to exclude other providers

* Excluded swell and venture temporarily

* Fix category & color normalization

* Fixed category normalizer in shopify

* Don't use getSlug for category on /search

* Update colors.ts

Co-authored-by: cond0r <pinte_catalin@yahoo.com>
Co-authored-by: B <curciobelen@gmail.com>
This commit is contained in:
Luis Alvarez D
2021-06-01 03:18:10 -05:00
committed by GitHub
parent 0792eabd4c
commit a98c95d447
249 changed files with 4646 additions and 2981 deletions

View File

@@ -1,4 +1,4 @@
import { Cart } from '../types'
import type { Cart } from '../types/cart'
import { CommerceError } from '@commerce/utils/errors'
import {
@@ -27,12 +27,6 @@ export type CheckoutPayload =
| CheckoutQuery
const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
if (!checkoutPayload) {
throw new CommerceError({
message: 'Missing checkout payload from response',
})
}
const checkout = checkoutPayload?.checkout
throwUserErrors(checkoutPayload?.checkoutUserErrors)

View File

@@ -1,5 +1,8 @@
import {
GetAllProductVendorsQuery,
GetAllProductVendorsQueryVariables,
} from '../schema'
import { ShopifyConfig } from '../api'
import fetchAllProducts from '../api/utils/fetch-all-products'
import getAllProductVendors from './queries/get-all-product-vendors-query'
export type Brand = {
@@ -14,16 +17,17 @@ export type BrandEdge = {
export type Brands = BrandEdge[]
const getVendors = async (config: ShopifyConfig): Promise<BrandEdge[]> => {
const vendors = await fetchAllProducts({
config,
query: getAllProductVendors,
const getBrands = async (config: ShopifyConfig): Promise<BrandEdge[]> => {
const { data } = await config.fetch<
GetAllProductVendorsQuery,
GetAllProductVendorsQueryVariables
>(getAllProductVendors, {
variables: {
first: 250,
},
})
let vendorsStrings = vendors.map(({ node: { vendor } }) => vendor)
let vendorsStrings = data.products.edges.map(({ node: { vendor } }) => vendor)
return [...new Set(vendorsStrings)].map((v) => {
const id = v.replace(/\s+/g, '-').toLowerCase()
@@ -37,4 +41,4 @@ const getVendors = async (config: ShopifyConfig): Promise<BrandEdge[]> => {
})
}
export default getVendors
export default getBrands

View File

@@ -1,23 +1,32 @@
import type { Category } from '../types/site'
import { ShopifyConfig } from '../api'
import { CollectionEdge } from '../schema'
import { normalizeCategory } from './normalize'
import getSiteCollectionsQuery from './queries/get-all-collections-query'
import { Category } from '@commerce/types'
const getCategories = async (config: ShopifyConfig): Promise<Category[]> => {
const { data } = await config.fetch(getSiteCollectionsQuery, {
variables: {
first: 250,
const getCategories = async ({
fetch,
locale,
}: ShopifyConfig): Promise<Category[]> => {
const { data } = await fetch(
getSiteCollectionsQuery,
{
variables: {
first: 250,
},
},
})
{
...(locale && {
headers: {
'Accept-Language': locale,
},
}),
}
)
return (
data.collections?.edges?.map(
({ node: { id, title: name, handle } }: CollectionEdge) => ({
id,
name,
slug: handle,
path: `/${handle}`,
})
data.collections?.edges?.map(({ node }: CollectionEdge) =>
normalizeCategory(node)
) ?? []
)
}

View File

@@ -1,26 +1,30 @@
import getSortVariables from './get-sort-variables'
import type { SearchProductsInput } from '../product/use-search'
import { SearchProductsBody } from '../types/product'
export const getSearchVariables = ({
brandId,
search,
categoryId,
sort,
}: SearchProductsInput) => {
locale,
}: SearchProductsBody) => {
let query = ''
if (search) {
query += `product_type:${search} OR title:${search} OR tag:${search}`
query += `product_type:${search} OR title:${search} OR tag:${search} `
}
if (brandId) {
query += `${search ? ' AND ' : ''}vendor:${brandId}`
query += `${search ? 'AND ' : ''}vendor:${brandId}`
}
return {
categoryId,
query,
...getSortVariables(sort, !!categoryId),
...(locale && {
locale,
}),
}
}

View File

@@ -1,7 +1,7 @@
export { default as handleFetchResponse } from './handle-fetch-response'
export { default as getSearchVariables } from './get-search-variables'
export { default as getSortVariables } from './get-sort-variables'
export { default as getVendors } from './get-vendors'
export { default as getBrands } from './get-brands'
export { default as getCategories } from './get-categories'
export { default as getCheckoutId } from './get-checkout-id'
export { default as checkoutCreate } from './checkout-create'

View File

@@ -1,17 +1,19 @@
import { checkoutDetailsFragment } from '../queries/get-checkout-query'
const checkoutCreateMutation = /* GraphQL */ `
mutation {
checkoutCreate(input: {}) {
mutation checkoutCreate($input: CheckoutCreateInput = {}) {
checkoutCreate(input: $input) {
checkoutUserErrors {
code
field
message
}
checkout {
${checkoutDetailsFragment}
...checkoutDetails
}
}
}
${checkoutDetailsFragment}
`
export default checkoutCreateMutation

View File

@@ -1,7 +1,10 @@
import { checkoutDetailsFragment } from '../queries/get-checkout-query'
const checkoutLineItemAddMutation = /* GraphQL */ `
mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemInput!]!) {
mutation checkoutLineItemAdd(
$checkoutId: ID!
$lineItems: [CheckoutLineItemInput!]!
) {
checkoutLineItemsAdd(checkoutId: $checkoutId, lineItems: $lineItems) {
checkoutUserErrors {
code
@@ -9,9 +12,11 @@ const checkoutLineItemAddMutation = /* GraphQL */ `
message
}
checkout {
${checkoutDetailsFragment}
...checkoutDetails
}
}
}
${checkoutDetailsFragment}
`
export default checkoutLineItemAddMutation

View File

@@ -1,7 +1,7 @@
import { checkoutDetailsFragment } from '../queries/get-checkout-query'
const checkoutLineItemRemoveMutation = /* GraphQL */ `
mutation($checkoutId: ID!, $lineItemIds: [ID!]!) {
mutation checkoutLineItemRemove($checkoutId: ID!, $lineItemIds: [ID!]!) {
checkoutLineItemsRemove(
checkoutId: $checkoutId
lineItemIds: $lineItemIds
@@ -12,9 +12,10 @@ const checkoutLineItemRemoveMutation = /* GraphQL */ `
message
}
checkout {
${checkoutDetailsFragment}
...checkoutDetails
}
}
}
${checkoutDetailsFragment}
`
export default checkoutLineItemRemoveMutation

View File

@@ -1,7 +1,10 @@
import { checkoutDetailsFragment } from '../queries/get-checkout-query'
const checkoutLineItemUpdateMutation = /* GraphQL */ `
mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemUpdateInput!]!) {
mutation checkoutLineItemUpdate(
$checkoutId: ID!
$lineItems: [CheckoutLineItemUpdateInput!]!
) {
checkoutLineItemsUpdate(checkoutId: $checkoutId, lineItems: $lineItems) {
checkoutUserErrors {
code
@@ -9,9 +12,11 @@ const checkoutLineItemUpdateMutation = /* GraphQL */ `
message
}
checkout {
${checkoutDetailsFragment}
...checkoutDetails
}
}
}
${checkoutDetailsFragment}
`
export default checkoutLineItemUpdateMutation

View File

@@ -1,4 +1,7 @@
import { Product } from '@commerce/types'
import type { Page } from '../types/page'
import type { Product } from '../types/product'
import type { Cart, LineItem } from '../types/cart'
import type { Category } from '../types/site'
import {
Product as ShopifyProduct,
@@ -9,9 +12,11 @@ import {
ProductVariantConnection,
MoneyV2,
ProductOption,
Page as ShopifyPage,
PageEdge,
Collection,
} from '../schema'
import type { Cart, LineItem } from '../types'
import { colorMap } from '@lib/colors'
const money = ({ amount, currencyCode }: MoneyV2) => {
return {
@@ -28,15 +33,18 @@ const normalizeProductOption = ({
return {
__typename: 'MultipleChoiceOption',
id,
displayName,
displayName: displayName.toLowerCase(),
values: values.map((value) => {
let output: any = {
label: value,
}
if (displayName.match(/colou?r/gi)) {
output = {
...output,
hexColors: [value],
const mapedColor = colorMap[value.toLowerCase().replace(/ /g, '')]
if (mapedColor) {
output = {
...output,
hexColors: [mapedColor],
}
}
}
return output
@@ -53,7 +61,16 @@ const normalizeProductImages = ({ edges }: ImageConnection) =>
const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
return edges?.map(
({
node: { id, selectedOptions, sku, title, priceV2, compareAtPriceV2 },
node: {
id,
selectedOptions,
sku,
title,
priceV2,
compareAtPriceV2,
requiresShipping,
availableForSale,
},
}) => {
return {
id,
@@ -61,7 +78,8 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
sku: sku ?? id,
price: +priceV2.amount,
listPrice: +compareAtPriceV2?.amount,
requiresShipping: true,
requiresShipping,
availableForSale,
options: selectedOptions.map(({ name, value }: SelectedOption) => {
const options = normalizeProductOption({
id,
@@ -75,22 +93,21 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
)
}
export function normalizeProduct(productNode: ShopifyProduct): Product {
const {
id,
title: name,
vendor,
images,
variants,
description,
descriptionHtml,
handle,
priceRange,
options,
...rest
} = productNode
const product = {
export function normalizeProduct({
id,
title: name,
vendor,
images,
variants,
description,
descriptionHtml,
handle,
priceRange,
options,
metafields,
...rest
}: ShopifyProduct): Product {
return {
id,
name,
vendor,
@@ -108,13 +125,12 @@ export function normalizeProduct(productNode: ShopifyProduct): Product {
...(descriptionHtml && { descriptionHtml }),
...rest,
}
return product
}
export function normalizeCart(checkout: Checkout): Cart {
return {
id: checkout.id,
url: checkout.webUrl,
customerId: '',
email: '',
createdAt: checkout.createdAt,
@@ -131,7 +147,7 @@ export function normalizeCart(checkout: Checkout): Cart {
}
function normalizeLineItem({
node: { id, title, variant, quantity, ...rest },
node: { id, title, variant, quantity },
}: CheckoutLineItemEdge): LineItem {
return {
id,
@@ -144,7 +160,7 @@ function normalizeLineItem({
sku: variant?.sku ?? '',
name: variant?.title!,
image: {
url: variant?.image?.originalSrc ?? '/product-img-placeholder.svg',
url: variant?.image?.originalSrc || '/product-img-placeholder.svg',
},
requiresShipping: variant?.requiresShipping ?? false,
price: variant?.priceV2?.amount,
@@ -152,14 +168,29 @@ function normalizeLineItem({
},
path: String(variant?.product?.handle),
discounts: [],
options:
// By default Shopify adds a default variant with default names, we're removing it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095
variant?.title == 'Default Title'
? []
: [
{
value: variant?.title,
},
],
options: variant?.title == 'Default Title' ? [] : variant?.selectedOptions,
}
}
export const normalizePage = (
{ title: name, handle, ...page }: ShopifyPage,
locale: string
): Page => ({
...page,
url: `/${locale}/${handle}`,
name,
})
export const normalizePages = (edges: PageEdge[], locale: string): Page[] =>
edges?.map((edge) => normalizePage(edge.node, locale))
export const normalizeCategory = ({
title: name,
handle,
id,
}: Collection): Category => ({
id,
name,
slug: handle,
path: `/${handle}`,
})

View File

@@ -1,46 +1,38 @@
export const productConnection = `
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
node {
id
title
vendor
handle
priceRange {
minVariantPrice {
amount
currencyCode
}
export const productConnectionFragment = /* GraphQL */ `
fragment productConnection on ProductConnection {
pageInfo {
hasNextPage
hasPreviousPage
}
images(first: 1) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
node {
originalSrc
altText
width
height
edges {
node {
id
title
vendor
handle
priceRange {
minVariantPrice {
amount
currencyCode
}
}
images(first: 1) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
node {
originalSrc
altText
width
height
}
}
}
}
}
}
}`
export const productsFragment = `
products(
first: $first
sortKey: $sortKey
reverse: $reverse
query: $query
) {
${productConnection}
}
`
const getAllProductsQuery = /* GraphQL */ `
@@ -50,7 +42,16 @@ const getAllProductsQuery = /* GraphQL */ `
$sortKey: ProductSortKeys = RELEVANCE
$reverse: Boolean = false
) {
${productsFragment}
products(
first: $first
sortKey: $sortKey
reverse: $reverse
query: $query
) {
...productConnection
}
}
${productConnectionFragment}
`
export default getAllProductsQuery

View File

@@ -1,65 +1,70 @@
export const checkoutDetailsFragment = `
id
webUrl
subtotalPriceV2{
amount
currencyCode
}
totalTaxV2 {
amount
currencyCode
}
totalPriceV2 {
amount
currencyCode
}
completedAt
createdAt
taxesIncluded
lineItems(first: 250) {
pageInfo {
hasNextPage
hasPreviousPage
export const checkoutDetailsFragment = /* GraphQL */ `
fragment checkoutDetails on Checkout {
id
webUrl
subtotalPriceV2 {
amount
currencyCode
}
edges {
node {
id
title
variant {
totalTaxV2 {
amount
currencyCode
}
totalPriceV2 {
amount
currencyCode
}
completedAt
createdAt
taxesIncluded
lineItems(first: 250) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
node {
id
sku
title
image {
originalSrc
altText
width
height
}
priceV2{
amount
currencyCode
}
compareAtPriceV2{
amount
currencyCode
}
product {
handle
variant {
id
sku
title
selectedOptions {
name
value
}
image {
originalSrc
altText
width
height
}
priceV2 {
amount
currencyCode
}
compareAtPriceV2 {
amount
currencyCode
}
product {
handle
}
}
quantity
}
quantity
}
}
}
`
const getCheckoutQuery = /* GraphQL */ `
query($checkoutId: ID!) {
query getCheckout($checkoutId: ID!) {
node(id: $checkoutId) {
... on Checkout {
${checkoutDetailsFragment}
}
...checkoutDetails
}
}
${checkoutDetailsFragment}
`
export default getCheckoutQuery

View File

@@ -1,4 +1,4 @@
import { productConnection } from './get-all-products-query'
import { productConnectionFragment } from './get-all-products-query'
const getCollectionProductsQuery = /* GraphQL */ `
query getProductsFromCollection(
@@ -10,15 +10,12 @@ const getCollectionProductsQuery = /* GraphQL */ `
node(id: $categoryId) {
id
... on Collection {
products(
first: $first
sortKey: $sortKey
reverse: $reverse
) {
${productConnection}
products(first: $first, sortKey: $sortKey, reverse: $reverse) {
...productConnection
}
}
}
}
${productConnectionFragment}
`
export default getCollectionProductsQuery

View File

@@ -1,5 +1,5 @@
export const getPageQuery = /* GraphQL */ `
query($id: ID!) {
query getPage($id: ID!) {
node(id: $id) {
id
... on Page {

View File

@@ -3,6 +3,7 @@ const getProductQuery = /* GraphQL */ `
productByHandle(handle: $slug) {
id
handle
availableForSale
title
productType
vendor
@@ -33,6 +34,8 @@ const getProductQuery = /* GraphQL */ `
id
title
sku
availableForSale
requiresShipping
selectedOptions {
name
value

View File

@@ -0,0 +1,8 @@
const getSiteInfoQuery = /* GraphQL */ `
query getSiteInfo {
shop {
name
}
}
`
export default getSiteInfoQuery

View File

@@ -8,3 +8,4 @@ export { default as getCheckoutQuery } from './get-checkout-query'
export { default as getAllPagesQuery } from './get-all-pages-query'
export { default as getPageQuery } from './get-page-query'
export { default as getCustomerQuery } from './get-customer-query'
export { default as getSiteInfoQuery } from './get-site-info-query'