forked from crowetic/commerce
normalizations & missing files
This commit is contained in:
parent
3c985278c6
commit
2dffcb7bfb
1
framework/shopify/api/cart/index.ts
Normal file
1
framework/shopify/api/cart/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
1
framework/shopify/api/catalog/index.ts
Normal file
1
framework/shopify/api/catalog/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
1
framework/shopify/api/catalog/products.ts
Normal file
1
framework/shopify/api/catalog/products.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
103
framework/shopify/api/checkout/index.ts
Normal file
103
framework/shopify/api/checkout/index.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { CommerceAPIFetchOptions } from '@commerce/api'
|
||||
import {
|
||||
CheckoutCreateInput,
|
||||
CheckoutCreatePayload,
|
||||
Maybe,
|
||||
} from '@framework/schema'
|
||||
import { getProductQuery } from 'framework/bigcommerce/product/get-product'
|
||||
import Cookies from 'js-cookie'
|
||||
import { getConfig, ShopifyConfig } from '..'
|
||||
|
||||
const createCheckoutMutation = `
|
||||
mutation($input) {
|
||||
checkoutCreate(input: $input) {
|
||||
checkout {
|
||||
id
|
||||
webUrl
|
||||
lineItems(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const getCheckoutQuery = `
|
||||
query {
|
||||
shop {
|
||||
name
|
||||
currencyCode
|
||||
checkout {
|
||||
id
|
||||
webUrl
|
||||
lineItems(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const createCheckout = async (fetcher: any, input: CheckoutCreateInput) => {
|
||||
return await fetcher(createCheckoutMutation, {
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const getCheckout = async (req: any, res: any, config: any): Promise<any> => {
|
||||
console.log(config)
|
||||
|
||||
return
|
||||
config = getConfig(config)
|
||||
|
||||
const { data: shop } = await config.fetch(getProductQuery)
|
||||
|
||||
const checkout = shop?.checkout
|
||||
const completedAt = checkout.completedAt
|
||||
|
||||
const checkoutId = Cookies.get('nextjs-commerce-shopify-token')
|
||||
|
||||
const checkoutCreateInput = {
|
||||
presentmentCurrencyCode: shop?.currencyCode,
|
||||
}
|
||||
|
||||
// we could have a cart id stored in session storage
|
||||
// user could be refreshing or navigating back and forthlet checkoutResource
|
||||
let checkoutCreatePayload: Maybe<CheckoutCreatePayload>
|
||||
|
||||
if (checkoutId) {
|
||||
checkoutCreatePayload = await createCheckout(
|
||||
config.fetch,
|
||||
checkoutCreateInput
|
||||
)
|
||||
const existingCheckout = checkoutCreatePayload?.checkout
|
||||
const completedAt = existingCheckout?.completedAt
|
||||
if (completedAt) {
|
||||
checkoutCreatePayload = await createCheckout(
|
||||
config.fetch,
|
||||
checkoutCreateInput
|
||||
)
|
||||
}
|
||||
} else {
|
||||
checkoutCreatePayload = await createCheckout(
|
||||
config.fetch,
|
||||
checkoutCreateInput
|
||||
)
|
||||
}
|
||||
|
||||
console.log(checkout)
|
||||
}
|
||||
|
||||
export default getCheckout
|
1
framework/shopify/api/customer.ts
Normal file
1
framework/shopify/api/customer.ts
Normal file
@ -0,0 +1 @@
|
||||
export default function () {}
|
@ -1,38 +0,0 @@
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
import type { LoginHandlers } from '../login'
|
||||
|
||||
const loginHandler: LoginHandlers['login'] = async ({
|
||||
res,
|
||||
body: { email, password },
|
||||
config,
|
||||
}) => {
|
||||
if (!(email && password)) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
} catch (error) {
|
||||
// Check if the email and password didn't match an existing account
|
||||
if (error instanceof FetcherError) {
|
||||
return res.status(401).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'Cannot find an account that matches the provided credentials',
|
||||
code: 'invalid_credentials',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
res.status(200).json({ data: null })
|
||||
}
|
||||
|
||||
export default loginHandler
|
@ -9,32 +9,25 @@ import type { Cart } from '@commerce/types'
|
||||
import checkoutLineItemAddMutation from '../utils/mutations/checkout-line-item-add'
|
||||
import getCheckoutId from '@framework/utils/get-checkout-id'
|
||||
import { checkoutToCart } from './utils'
|
||||
import { AddCartItemBody, CartItemBody } from '@framework/types'
|
||||
import { AddItemBody } from '../types'
|
||||
import { MutationCheckoutLineItemsAddArgs } from '@framework/schema'
|
||||
|
||||
const defaultOpts = {
|
||||
query: checkoutLineItemAddMutation,
|
||||
}
|
||||
|
||||
export type AddItemInput = UseAddItemInput<any>
|
||||
export type AddItemInput = UseAddItemInput<CartItemBody>
|
||||
|
||||
export const fetcher: HookFetcher<Cart, any> = async (
|
||||
options,
|
||||
{ checkoutId, item },
|
||||
fetch
|
||||
) => {
|
||||
if (
|
||||
item.quantity &&
|
||||
(!Number.isInteger(item.quantity) || item.quantity! < 1)
|
||||
) {
|
||||
throw new CommerceError({
|
||||
message: 'The item quantity has to be a valid integer greater than 0',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch<any, any>({
|
||||
export const fetcher: HookFetcher<
|
||||
Cart,
|
||||
MutationCheckoutLineItemsAddArgs
|
||||
> = async (options, { checkoutId, lineItems }, fetch) => {
|
||||
const data = await fetch<any, AddCartItemBody>({
|
||||
...options,
|
||||
variables: {
|
||||
checkoutId,
|
||||
lineItems: [item],
|
||||
lineItems,
|
||||
},
|
||||
})
|
||||
|
||||
@ -49,10 +42,12 @@ export function extendHook(customFetcher: typeof fetcher) {
|
||||
return useCallback(
|
||||
async function addItem(input: AddItemInput) {
|
||||
const data = await fn({
|
||||
item: {
|
||||
variantId: input.variantId,
|
||||
quantity: input.quantity ?? 1,
|
||||
},
|
||||
lineItems: [
|
||||
{
|
||||
variantId: input.variantId,
|
||||
quantity: input.quantity ?? 1,
|
||||
},
|
||||
],
|
||||
checkoutId: getCheckoutId(cart?.id),
|
||||
})
|
||||
await mutate(data, false)
|
||||
|
1
framework/shopify/customer/index.ts
Normal file
1
framework/shopify/customer/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as useCustomer } from './use-customer'
|
124
framework/shopify/lib/normalize.ts
Normal file
124
framework/shopify/lib/normalize.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import {
|
||||
Product as ShopifyProduct,
|
||||
Checkout,
|
||||
CheckoutLineItemEdge,
|
||||
SelectedOption,
|
||||
ImageConnection,
|
||||
ProductVariantConnection,
|
||||
ProductOption,
|
||||
MoneyV2,
|
||||
} from '@framework/schema'
|
||||
|
||||
import type { Cart, LineItem } from '../types'
|
||||
|
||||
const money = ({ amount, currencyCode }: MoneyV2) => {
|
||||
return {
|
||||
value: +amount,
|
||||
currencyCode,
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeProductOption = ({
|
||||
name: displayName,
|
||||
values,
|
||||
...rest
|
||||
}: ProductOption) => ({
|
||||
__typename: 'MultipleChoiceOption',
|
||||
displayName,
|
||||
values: values.map((value) => ({
|
||||
label: value,
|
||||
})),
|
||||
...rest,
|
||||
})
|
||||
|
||||
const normalizeProductImages = ({ edges }: ImageConnection) =>
|
||||
edges?.map(({ node: { originalSrc: url, ...rest } }) => ({
|
||||
url,
|
||||
...rest,
|
||||
}))
|
||||
|
||||
const normalizeProductVariants = ({ edges }: ProductVariantConnection) =>
|
||||
edges?.map(({ node: { id, selectedOptions } }) => ({
|
||||
id,
|
||||
options: selectedOptions.map(({ name, value }: SelectedOption) =>
|
||||
normalizeProductOption({
|
||||
id,
|
||||
name,
|
||||
values: [value],
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
export function normalizeProduct(productNode: ShopifyProduct): any {
|
||||
const {
|
||||
id,
|
||||
title: name,
|
||||
vendor,
|
||||
images,
|
||||
variants,
|
||||
description,
|
||||
handle,
|
||||
priceRange,
|
||||
options,
|
||||
...rest
|
||||
} = productNode
|
||||
|
||||
return {
|
||||
id: { $set: String(id) },
|
||||
name,
|
||||
vendor,
|
||||
description,
|
||||
path: `/${handle}`,
|
||||
slug: handle?.replace(/^\/+|\/+$/g, ''),
|
||||
price: money(priceRange?.minVariantPrice),
|
||||
images: normalizeProductImages(images),
|
||||
variants: variants ? normalizeProductVariants(variants) : null,
|
||||
options: options ? options.map((o) => normalizeProductOption) : [],
|
||||
...rest,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeCart(data: Checkout): Cart {
|
||||
return {
|
||||
id: data.id,
|
||||
customerId: '',
|
||||
email: '',
|
||||
createdAt: data.createdAt,
|
||||
currency: {
|
||||
code: data.currencyCode,
|
||||
},
|
||||
taxesIncluded: data.taxesIncluded,
|
||||
lineItems: data.lineItems?.edges.map(normalizeLineItem),
|
||||
lineItemsSubtotalPrice: data.subtotalPrice,
|
||||
subtotalPrice: data.subtotalPrice,
|
||||
totalPrice: data.totalPrice,
|
||||
discounts: data.discountApplications?.edges.map(({ value }: any) => ({
|
||||
value,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLineItem({ node: item }: CheckoutLineItemEdge): LineItem {
|
||||
return {
|
||||
id: item.id,
|
||||
variantId: String(item.variant?.id),
|
||||
productId: String(item.variant?.id),
|
||||
name: item.title,
|
||||
quantity: item.quantity,
|
||||
variant: {
|
||||
id: String(item.variant?.id),
|
||||
sku: item.variant?.sku ?? '',
|
||||
name: item.title,
|
||||
image: {
|
||||
url: item.variant?.image?.originalSrc,
|
||||
},
|
||||
requiresShipping: item.variant?.requiresShipping ?? false,
|
||||
price: item.variant?.price,
|
||||
listPrice: item.variant?.compareAtPrice,
|
||||
},
|
||||
path: '',
|
||||
discounts: item.discountAllocations.map(({ value }: any) => ({
|
||||
value,
|
||||
})),
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { GraphQLFetcherResult } from '@commerce/api'
|
||||
import toCommerceProducts from '../utils/to-commerce-products'
|
||||
import { getConfig, ShopifyConfig } from '../api'
|
||||
import { Product } from '../schema'
|
||||
import { Product, ProductEdge } from '../schema'
|
||||
import { getAllProductsQuery } from '../utils/queries'
|
||||
import { normalizeProduct } from '@framework/lib/normalize'
|
||||
|
||||
export type ProductNode = Product
|
||||
|
||||
@ -28,8 +28,9 @@ const getAllProducts = async (options: {
|
||||
{ variables }
|
||||
)
|
||||
|
||||
const shopifyProducts = data.products?.edges
|
||||
const products = toCommerceProducts(shopifyProducts)
|
||||
const products = data?.products?.edges?.map(({ node: p }: ProductEdge) =>
|
||||
normalizeProduct(p)
|
||||
)
|
||||
|
||||
return {
|
||||
products,
|
||||
|
@ -2,8 +2,8 @@ import { GraphQLFetcherResult } from '@commerce/api'
|
||||
|
||||
import { getConfig, ShopifyConfig } from '../api'
|
||||
import { Product } from '../schema'
|
||||
import { toCommerceProduct } from '../utils/to-commerce-products'
|
||||
import getProductQuery from '../utils/queries/get-product-query'
|
||||
import { normalizeProduct } from '@framework/lib/normalize'
|
||||
|
||||
export type ProductNode = Product
|
||||
|
||||
@ -25,12 +25,14 @@ const getProduct = async (options: Options): Promise<ReturnType> => {
|
||||
let { config, variables = { first: 250 } } = options ?? {}
|
||||
config = getConfig(config)
|
||||
|
||||
const {
|
||||
data: { productByHandle: product },
|
||||
}: GraphQLFetcherResult = await config.fetch(getProductQuery, { variables })
|
||||
const { data }: GraphQLFetcherResult = await config.fetch(getProductQuery, {
|
||||
variables,
|
||||
})
|
||||
|
||||
const product = data?.productByHandle?.product
|
||||
|
||||
return {
|
||||
product: product ? toCommerceProduct(product) : null,
|
||||
product: product ? normalizeProduct(product) : null,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
import useCommerceSearch from '@commerce/products/use-search'
|
||||
|
||||
import toCommerceProducts from '@framework/utils/to-commerce-products'
|
||||
import getAllProductsQuery from '@framework/utils/queries/get-all-products-query'
|
||||
|
||||
import type { Product } from 'framework/bigcommerce/schema'
|
||||
@ -14,10 +12,7 @@ import {
|
||||
} from '@framework/utils/get-search-variables'
|
||||
|
||||
import sortBy from '@framework/utils/get-sort-variables'
|
||||
|
||||
export type CommerceProductEdge = {
|
||||
node: Product
|
||||
}
|
||||
import { normalizeProduct } from '@framework/lib/normalize'
|
||||
|
||||
export type SearchProductsInput = {
|
||||
search?: string
|
||||
@ -49,8 +44,10 @@ export const fetcher: HookFetcher<
|
||||
}).then(
|
||||
({ products }): SearchProductsData => {
|
||||
return {
|
||||
products: toCommerceProducts(products.edges),
|
||||
found: !!products.edges.length,
|
||||
products: products?.edges?.map(({ node: p }: ProductEdge) =>
|
||||
normalizeProduct(p)
|
||||
),
|
||||
found: !!products?.edges?.length,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
46
framework/shopify/types.ts
Normal file
46
framework/shopify/types.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import * as Core from '@commerce/types'
|
||||
import { CheckoutLineItem } from './schema'
|
||||
|
||||
export type ShopifyCheckout = {
|
||||
id: string
|
||||
webUrl: string
|
||||
lineItems: CheckoutLineItem[]
|
||||
}
|
||||
|
||||
export interface Cart extends Core.Cart {
|
||||
lineItems: LineItem[]
|
||||
}
|
||||
|
||||
export interface LineItem extends Core.LineItem {}
|
||||
|
||||
/**
|
||||
* Cart mutations
|
||||
*/
|
||||
|
||||
export type OptionSelections = {
|
||||
option_id: number
|
||||
option_value: number | string
|
||||
}
|
||||
|
||||
export interface CartItemBody extends Core.CartItemBody {
|
||||
productId: string // The product id is always required for BC
|
||||
optionSelections?: OptionSelections
|
||||
}
|
||||
|
||||
export interface GetCartHandlerBody extends Core.GetCartHandlerBody {}
|
||||
|
||||
export interface AddCartItemBody extends Core.AddCartItemBody<CartItemBody> {}
|
||||
|
||||
export interface AddCartItemHandlerBody
|
||||
extends Core.AddCartItemHandlerBody<CartItemBody> {}
|
||||
|
||||
export interface UpdateCartItemBody
|
||||
extends Core.UpdateCartItemBody<CartItemBody> {}
|
||||
|
||||
export interface UpdateCartItemHandlerBody
|
||||
extends Core.UpdateCartItemHandlerBody<CartItemBody> {}
|
||||
|
||||
export interface RemoveCartItemBody extends Core.RemoveCartItemBody {}
|
||||
|
||||
export interface RemoveCartItemHandlerBody
|
||||
extends Core.RemoveCartItemHandlerBody {}
|
@ -35,7 +35,10 @@ const getAllProductsQuery = /* GraphQL */ `
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
src
|
||||
originalSrc
|
||||
altText
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export const checkoutDetailsFragment = /* GraphQL */ `
|
||||
title
|
||||
variant {
|
||||
id
|
||||
sku
|
||||
title
|
||||
image {
|
||||
src
|
||||
|
@ -48,7 +48,10 @@ const getProductQuery = /* GraphQL */ `
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
src
|
||||
originalSrc
|
||||
altText
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,96 +0,0 @@
|
||||
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}`,
|
||||
})
|
||||
)
|
||||
}
|
@ -34,4 +34,7 @@ module.exports = {
|
||||
},
|
||||
]
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user