mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Updated Saleor Provider (#356)
* Initial work, copied from the Shopify provider * Added basis setup and type generation for the products queries * refactor: adjust the types * task: relax the Node.js constraint * fix: page/product properties * disable unknown fields * mention Saleor in the README * setup debugging for Next.js * Check nextjs-commerce bug if no images are added for a product * fix: client/server pecularities for env visibility Must prefix with `NEXT_PUBLIC_` so that the API URL is visible on the client * re: make search work with Saleor API (WIP) * task: update deps * task: move to Webpack 5.x * saleor: initial cart integration * update deps * saleor: shall the cart appear! * task: remove deprecated packages * saleor: adding/removing from the cart * saleor: preliminary signup process * saleor: fix the prices in the cart * update deps * update deps * Added the options for a variant to the product page * Mapped options to variants * Mapped options to variants * saleor: refine the auth process * saleor: remove unused code * saleor: handle customer find via refresh temporary solution * saleor: update deps * saleor: fix the session handling * saleor: fix the variants * saleor: simplify the naming for GraphQL statements * saleor: fix the type for collection * saleor: arrange the error codes * saleor: integrate collections * saleor: fix product sorting * saleor: set cookie location * saleor: update the schema * saleor: attach checkout to customer * saleor: fix the checkout flow * saleor: unify GraphQL naming approach * task: update deps * Add the env variables for saleor to the template * task: prettier * saleor: stub API for build/typescript compilation thanks @cond0r * task: temporarily disable for the `build` * saleor: refactor GraphQL queries * saleor: adjust the config * task: update dependencies * revert: Next.js to `10.0.9` * saleor: fix the checkout fetch query * task: update dependencies * saleor: adapt for displaying featured products * saleor: update the provider structure * saleor: make the home page representable * feature/cart: display the variant name (cond) Co-authored-by: Patryk Zawadzki <patrys@room-303.com> Co-authored-by: royderks <10717410+royderks@users.noreply.github.com>
This commit is contained in:
12
framework/saleor/utils/checkout-attach.ts
Normal file
12
framework/saleor/utils/checkout-attach.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as mutation from './mutations'
|
||||
import { CheckoutCustomerAttach } from '../schema'
|
||||
|
||||
export const checkoutAttach = async (fetch: any, { variables, headers }: any): Promise<CheckoutCustomerAttach> => {
|
||||
const data = await fetch({
|
||||
query: mutation.CheckoutAttach,
|
||||
variables,
|
||||
headers,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
25
framework/saleor/utils/checkout-create.ts
Normal file
25
framework/saleor/utils/checkout-create.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
import * as mutation from './mutations'
|
||||
import { CheckoutCreate } from '../schema'
|
||||
import { CHECKOUT_ID_COOKIE } from '@framework/const'
|
||||
|
||||
export const checkoutCreate = async (fetch: any): Promise<CheckoutCreate> => {
|
||||
const data = await fetch({ query: mutation.CheckoutCreate })
|
||||
const checkout = data.checkoutCreate?.checkout
|
||||
const checkoutId = checkout?.id
|
||||
const checkoutToken = checkout?.token
|
||||
|
||||
const value = `${checkoutId}:${checkoutToken}`
|
||||
|
||||
if (checkoutId) {
|
||||
const options = {
|
||||
expires: 60 * 60 * 24 * 30,
|
||||
}
|
||||
Cookies.set(CHECKOUT_ID_COOKIE, value, options)
|
||||
}
|
||||
|
||||
return checkout
|
||||
}
|
||||
|
||||
export default checkoutCreate
|
35
framework/saleor/utils/checkout-to-cart.ts
Normal file
35
framework/saleor/utils/checkout-to-cart.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Cart } from '../types'
|
||||
import { CommerceError } from '@commerce/utils/errors'
|
||||
|
||||
import { CheckoutLinesAdd, CheckoutLinesUpdate, CheckoutCreate, CheckoutError, Checkout, Maybe, CheckoutLineDelete } from '../schema'
|
||||
|
||||
import { normalizeCart } from './normalize'
|
||||
import throwUserErrors from './throw-user-errors'
|
||||
|
||||
export type CheckoutQuery = {
|
||||
checkout: Checkout
|
||||
errors?: Array<CheckoutError>
|
||||
}
|
||||
|
||||
export type CheckoutPayload = CheckoutLinesAdd | CheckoutLinesUpdate | CheckoutCreate | CheckoutQuery | CheckoutLineDelete
|
||||
|
||||
const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
|
||||
if (!checkoutPayload) {
|
||||
throw new CommerceError({
|
||||
message: 'Missing checkout payload from response',
|
||||
})
|
||||
}
|
||||
|
||||
const checkout = checkoutPayload?.checkout
|
||||
throwUserErrors(checkoutPayload?.errors)
|
||||
|
||||
if (!checkout) {
|
||||
throw new CommerceError({
|
||||
message: 'Missing checkout object from response',
|
||||
})
|
||||
}
|
||||
|
||||
return normalizeCart(checkout)
|
||||
}
|
||||
|
||||
export default checkoutToCart
|
25
framework/saleor/utils/customer-token.ts
Normal file
25
framework/saleor/utils/customer-token.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import Cookies, { CookieAttributes } from 'js-cookie'
|
||||
import * as Const from '../const'
|
||||
|
||||
export const getToken = () => Cookies.get(Const.SALEOR_TOKEN)
|
||||
export const setToken = (token?: string, options?: CookieAttributes) => {
|
||||
setCookie(Const.SALEOR_TOKEN, token, options)
|
||||
}
|
||||
|
||||
export const getCSRFToken = () => Cookies.get(Const.SALEOR_CRSF_TOKEN)
|
||||
export const setCSRFToken = (token?: string, options?: CookieAttributes) => {
|
||||
setCookie(Const.SALEOR_CRSF_TOKEN, token, options)
|
||||
}
|
||||
|
||||
export const getCheckoutToken = () => Cookies.get(Const.CHECKOUT_ID_COOKIE)
|
||||
export const setCheckoutToken = (token?: string, options?: CookieAttributes) => {
|
||||
setCookie(Const.CHECKOUT_ID_COOKIE, token, options)
|
||||
}
|
||||
|
||||
const setCookie = (name: string, token?: string, options?: CookieAttributes) => {
|
||||
if (!token) {
|
||||
Cookies.remove(name)
|
||||
} else {
|
||||
Cookies.set(name, token, options ?? { expires: 60 * 60 * 24 * 30 })
|
||||
}
|
||||
}
|
49
framework/saleor/utils/fragments/checkout-details.ts
Normal file
49
framework/saleor/utils/fragments/checkout-details.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
export const CheckoutDetails = /* GraphQL */ `
|
||||
fragment CheckoutDetails on Checkout {
|
||||
id
|
||||
token
|
||||
created
|
||||
totalPrice {
|
||||
currency
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
}
|
||||
subtotalPrice {
|
||||
currency
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
}
|
||||
|
||||
lines {
|
||||
id
|
||||
variant {
|
||||
id
|
||||
name
|
||||
sku
|
||||
product {
|
||||
name
|
||||
slug
|
||||
}
|
||||
media {
|
||||
url
|
||||
}
|
||||
pricing {
|
||||
price {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
quantity
|
||||
totalPrice {
|
||||
currency
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
2
framework/saleor/utils/fragments/index.ts
Normal file
2
framework/saleor/utils/fragments/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { ProductConnection } from './product'
|
||||
export { CheckoutDetails } from './checkout-details'
|
29
framework/saleor/utils/fragments/product.ts
Normal file
29
framework/saleor/utils/fragments/product.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export const ProductConnection = /* GraphQL */ `
|
||||
fragment ProductConnection on ProductCountableConnection {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
slug
|
||||
pricing {
|
||||
priceRange {
|
||||
start {
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
media {
|
||||
url
|
||||
alt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
23
framework/saleor/utils/get-categories.ts
Normal file
23
framework/saleor/utils/get-categories.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Category } from '@commerce/types/site'
|
||||
import { SaleorConfig } from '../api'
|
||||
import { CollectionCountableEdge } from '../schema'
|
||||
import * as query from './queries'
|
||||
|
||||
const getCategories = async (config: SaleorConfig): Promise<Category[]> => {
|
||||
const { data } = await config.fetch(query.CollectionMany, {
|
||||
variables: {
|
||||
first: 100,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
data.collections?.edges?.map(({ node: { id, name, slug } }: CollectionCountableEdge) => ({
|
||||
id,
|
||||
name,
|
||||
slug,
|
||||
path: `/${slug}`,
|
||||
})) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
export default getCategories
|
9
framework/saleor/utils/get-checkout-id.ts
Normal file
9
framework/saleor/utils/get-checkout-id.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { CHECKOUT_ID_COOKIE } from '../const'
|
||||
|
||||
const getCheckoutId = (id?: string) => {
|
||||
const r = Cookies.get(CHECKOUT_ID_COOKIE)?.split(':') || []
|
||||
return { checkoutId: r[0], checkoutToken: r[1] }
|
||||
}
|
||||
|
||||
export default getCheckoutId
|
18
framework/saleor/utils/get-search-variables.ts
Normal file
18
framework/saleor/utils/get-search-variables.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getSortVariables } from './get-sort-variables'
|
||||
import type { SearchProductsInput } from '../product/use-search'
|
||||
|
||||
export const getSearchVariables = ({ brandId, search, categoryId, sort }: SearchProductsInput) => {
|
||||
const sortBy = {
|
||||
field: 'NAME',
|
||||
direction: 'ASC',
|
||||
...getSortVariables(sort, !!categoryId),
|
||||
channel: 'default-channel',
|
||||
}
|
||||
return {
|
||||
categoryId,
|
||||
filter: { search },
|
||||
sortBy,
|
||||
}
|
||||
}
|
||||
|
||||
export default getSearchVariables
|
30
framework/saleor/utils/get-sort-variables.ts
Normal file
30
framework/saleor/utils/get-sort-variables.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export const getSortVariables = (sort?: string, isCategory: boolean = false) => {
|
||||
let output = {}
|
||||
switch (sort) {
|
||||
case 'price-asc':
|
||||
output = {
|
||||
field: 'PRICE',
|
||||
direction: 'ASC',
|
||||
}
|
||||
break
|
||||
case 'price-desc':
|
||||
output = {
|
||||
field: 'PRICE',
|
||||
direction: 'DESC',
|
||||
}
|
||||
break
|
||||
case 'trending-desc':
|
||||
output = {
|
||||
field: 'RANK',
|
||||
direction: 'DESC',
|
||||
}
|
||||
break
|
||||
case 'latest-desc':
|
||||
output = {
|
||||
field: 'DATE',
|
||||
direction: 'DESC',
|
||||
}
|
||||
break
|
||||
}
|
||||
return output
|
||||
}
|
41
framework/saleor/utils/get-vendors.ts
Normal file
41
framework/saleor/utils/get-vendors.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { SaleorConfig } from '../api'
|
||||
|
||||
export type Brand = {
|
||||
entityId: string
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type BrandEdge = {
|
||||
node: Brand
|
||||
}
|
||||
|
||||
export type Brands = BrandEdge[]
|
||||
|
||||
// TODO: Find a way to get vendors from meta
|
||||
const getVendors = async (config: SaleorConfig): Promise<BrandEdge[]> => {
|
||||
// const vendors = await fetchAllProducts({
|
||||
// config,
|
||||
// query: getAllProductVendors,
|
||||
// variables: {
|
||||
// first: 100,
|
||||
// },
|
||||
// })
|
||||
|
||||
// let vendorsStrings = vendors.map(({ node: { vendor } }) => vendor)
|
||||
|
||||
// return [...new Set(vendorsStrings)].map((v) => {
|
||||
// const id = v.replace(/\s+/g, '-').toLowerCase()
|
||||
// return {
|
||||
// node: {
|
||||
// entityId: id,
|
||||
// name: v,
|
||||
// path: `brands/${id}`,
|
||||
// },
|
||||
// }
|
||||
// })
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export default getVendors
|
27
framework/saleor/utils/handle-fetch-response.ts
Normal file
27
framework/saleor/utils/handle-fetch-response.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
|
||||
export function getError(errors: any[], status: number) {
|
||||
errors = errors ?? [{ message: 'Failed to fetch Saleor API' }]
|
||||
return new FetcherError({ errors, status })
|
||||
}
|
||||
|
||||
export async function getAsyncError(res: Response) {
|
||||
const data = await res.json()
|
||||
return getError(data.errors, res.status)
|
||||
}
|
||||
|
||||
const handleFetchResponse = async (res: Response) => {
|
||||
if (res.ok) {
|
||||
const { data, errors } = await res.json()
|
||||
|
||||
if (errors && errors.length) {
|
||||
throw getError(errors, res.status)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
throw await getAsyncError(res)
|
||||
}
|
||||
|
||||
export default handleFetchResponse
|
35
framework/saleor/utils/handle-login.ts
Normal file
35
framework/saleor/utils/handle-login.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { FetcherOptions } from '@commerce/utils/types'
|
||||
import { CreateToken, Mutation, MutationTokenCreateArgs } from '../schema'
|
||||
import { setToken, setCSRFToken } from './customer-token'
|
||||
import * as mutation from './mutations'
|
||||
import throwUserErrors from './throw-user-errors'
|
||||
|
||||
const handleLogin = (data: CreateToken) => {
|
||||
throwUserErrors(data?.errors)
|
||||
|
||||
const token = data?.token
|
||||
|
||||
if (token) {
|
||||
setToken(token)
|
||||
setCSRFToken(token)
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
export const handleAutomaticLogin = async (
|
||||
fetch: <T = any, B = Body>(options: FetcherOptions<B>) => Promise<T>,
|
||||
input: MutationTokenCreateArgs
|
||||
) => {
|
||||
try {
|
||||
const { tokenCreate } = await fetch<Mutation, MutationTokenCreateArgs>({
|
||||
query: mutation.SessionCreate,
|
||||
variables: { ...input },
|
||||
})
|
||||
handleLogin(tokenCreate!)
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
export default handleLogin
|
19
framework/saleor/utils/index.ts
Normal file
19
framework/saleor/utils/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export { getSortVariables } from './get-sort-variables'
|
||||
|
||||
export { default as handleFetchResponse } from './handle-fetch-response'
|
||||
export { default as getSearchVariables } from './get-search-variables'
|
||||
export { default as getVendors } from './get-vendors'
|
||||
export { default as getCategories } from './get-categories'
|
||||
export { default as getCheckoutId } from './get-checkout-id'
|
||||
|
||||
export { default as checkoutCreate } from './checkout-create'
|
||||
export { checkoutAttach } from './checkout-attach'
|
||||
|
||||
export { default as checkoutToCart } from './checkout-to-cart'
|
||||
export { default as handleLogin, handleAutomaticLogin } from './handle-login'
|
||||
export { default as throwUserErrors } from './throw-user-errors'
|
||||
|
||||
export * from './queries'
|
||||
export * from './mutations'
|
||||
export * from './normalize'
|
||||
export * from './customer-token'
|
15
framework/saleor/utils/mutations/account-create.ts
Normal file
15
framework/saleor/utils/mutations/account-create.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const AccountCreate = /* GraphQL */ `
|
||||
mutation AccountCreate($input: AccountRegisterInput!) {
|
||||
accountRegister(input: $input) {
|
||||
errors {
|
||||
code
|
||||
field
|
||||
message
|
||||
}
|
||||
user {
|
||||
email
|
||||
isActive
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
12
framework/saleor/utils/mutations/checkout-attach.ts
Normal file
12
framework/saleor/utils/mutations/checkout-attach.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export const CheckoutAttach = /* GraphQl */ `
|
||||
mutation CheckoutAttach($checkoutId: ID!) {
|
||||
checkoutCustomerAttach(checkoutId: $checkoutId) {
|
||||
errors {
|
||||
message
|
||||
}
|
||||
checkout {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
17
framework/saleor/utils/mutations/checkout-create.ts
Normal file
17
framework/saleor/utils/mutations/checkout-create.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as fragment from '../fragments'
|
||||
|
||||
export const CheckoutCreate = /* GraphQL */ `
|
||||
mutation CheckoutCreate {
|
||||
checkoutCreate(input: { email: "customer@example.com", lines: [], channel: "default-channel" }) {
|
||||
errors {
|
||||
code
|
||||
field
|
||||
message
|
||||
}
|
||||
checkout {
|
||||
...CheckoutDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
${fragment.CheckoutDetails}
|
||||
`
|
17
framework/saleor/utils/mutations/checkout-line-add.ts
Normal file
17
framework/saleor/utils/mutations/checkout-line-add.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as fragment from '../fragments'
|
||||
|
||||
export const CheckoutLineAdd = /* GraphQL */ `
|
||||
mutation CheckoutLineAdd($checkoutId: ID!, $lineItems: [CheckoutLineInput!]!) {
|
||||
checkoutLinesAdd(checkoutId: $checkoutId, lines: $lineItems) {
|
||||
errors {
|
||||
code
|
||||
field
|
||||
message
|
||||
}
|
||||
checkout {
|
||||
...CheckoutDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
${fragment.CheckoutDetails}
|
||||
`
|
17
framework/saleor/utils/mutations/checkout-line-remove.ts
Normal file
17
framework/saleor/utils/mutations/checkout-line-remove.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as fragment from '../fragments'
|
||||
|
||||
export const CheckoutLineDelete = /* GraphQL */ `
|
||||
mutation CheckoutLineDelete($checkoutId: ID!, $lineId: ID!) {
|
||||
checkoutLineDelete(checkoutId: $checkoutId, lineId: $lineId) {
|
||||
errors {
|
||||
code
|
||||
field
|
||||
message
|
||||
}
|
||||
checkout {
|
||||
...CheckoutDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
${fragment.CheckoutDetails}
|
||||
`
|
17
framework/saleor/utils/mutations/checkout-line-update.ts
Normal file
17
framework/saleor/utils/mutations/checkout-line-update.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as fragment from '../fragments'
|
||||
|
||||
export const CheckoutLineUpdate = /* GraphQL */ `
|
||||
mutation CheckoutLineUpdate($checkoutId: ID!, $lineItems: [CheckoutLineInput!]!) {
|
||||
checkoutLinesUpdate(checkoutId: $checkoutId, lines: $lineItems) {
|
||||
errors {
|
||||
code
|
||||
field
|
||||
message
|
||||
}
|
||||
checkout {
|
||||
...CheckoutDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
${fragment.CheckoutDetails}
|
||||
`
|
8
framework/saleor/utils/mutations/index.ts
Normal file
8
framework/saleor/utils/mutations/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export { AccountCreate } from './account-create'
|
||||
export { CheckoutCreate } from './checkout-create'
|
||||
export { CheckoutLineAdd } from './checkout-line-add'
|
||||
export { CheckoutLineUpdate } from './checkout-line-update'
|
||||
export { CheckoutLineDelete } from './checkout-line-remove'
|
||||
export { SessionCreate } from './session-create'
|
||||
export { SessionDestroy } from './session-destroy'
|
||||
export { CheckoutAttach } from './checkout-attach'
|
14
framework/saleor/utils/mutations/session-create.ts
Normal file
14
framework/saleor/utils/mutations/session-create.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const SessionCreate = /* GraphQL */ `
|
||||
mutation SessionCreate($email: String!, $password: String!) {
|
||||
tokenCreate(email: $email, password: $password) {
|
||||
token
|
||||
refreshToken
|
||||
csrfToken
|
||||
errors {
|
||||
code
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
10
framework/saleor/utils/mutations/session-destroy.ts
Normal file
10
framework/saleor/utils/mutations/session-destroy.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const SessionDestroy = /* GraphQL */ `
|
||||
mutation SessionDestroy {
|
||||
tokensDeactivateAll {
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
133
framework/saleor/utils/normalize.ts
Normal file
133
framework/saleor/utils/normalize.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { Product } from '@commerce/types/product'
|
||||
|
||||
import { Product as SaleorProduct, Checkout, CheckoutLine, Money, ProductVariant } from '../schema'
|
||||
|
||||
import type { Cart, LineItem } from '../types'
|
||||
|
||||
// TODO: Check nextjs-commerce bug if no images are added for a product
|
||||
const placeholderImg = '/product-img-placeholder.svg'
|
||||
|
||||
const money = ({ amount, currency }: Money) => {
|
||||
return {
|
||||
value: +amount,
|
||||
currencyCode: currency || 'USD',
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeProductOptions = (options: ProductVariant[]) => {
|
||||
return options
|
||||
?.map((option) => option?.attributes)
|
||||
.flat(1)
|
||||
.reduce<any>((acc, x) => {
|
||||
if (acc.find(({ displayName }: any) => displayName === x.attribute.name)) {
|
||||
return acc.map((opt: any) => {
|
||||
return opt.displayName === x.attribute.name
|
||||
? {
|
||||
...opt,
|
||||
values: [
|
||||
...opt.values,
|
||||
...x.values.map((value: any) => ({
|
||||
label: value?.name,
|
||||
})),
|
||||
],
|
||||
}
|
||||
: opt
|
||||
})
|
||||
}
|
||||
|
||||
return acc.concat({
|
||||
__typename: 'MultipleChoiceOption',
|
||||
displayName: x.attribute.name,
|
||||
variant: 'size',
|
||||
values: x.values.map((value: any) => ({
|
||||
label: value?.name,
|
||||
})),
|
||||
})
|
||||
}, [])
|
||||
}
|
||||
|
||||
const normalizeProductVariants = (variants: ProductVariant[]) => {
|
||||
return variants?.map((variant) => {
|
||||
const { id, sku, name, pricing } = variant
|
||||
const price = pricing?.price?.net && money(pricing.price.net)?.value
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
sku: sku ?? id,
|
||||
price,
|
||||
listPrice: price,
|
||||
requiresShipping: true,
|
||||
options: normalizeProductOptions([variant]),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function normalizeProduct(productNode: SaleorProduct): Product {
|
||||
const { id, name, media = [], variants, description, slug, pricing, ...rest } = productNode
|
||||
|
||||
const product = {
|
||||
id,
|
||||
name,
|
||||
vendor: '',
|
||||
description: description ? JSON.parse(description)?.blocks[0]?.data.text : '',
|
||||
path: `/${slug}`,
|
||||
slug: slug?.replace(/^\/+|\/+$/g, ''),
|
||||
price: (pricing?.priceRange?.start?.net && money(pricing.priceRange.start.net)) || {
|
||||
value: 0,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
// TODO: Check nextjs-commerce bug if no images are added for a product
|
||||
images: media?.length ? media : [{ url: placeholderImg }],
|
||||
variants: variants && variants.length > 0 ? normalizeProductVariants(variants as ProductVariant[]) : [],
|
||||
options: variants && variants.length > 0 ? normalizeProductOptions(variants as ProductVariant[]) : [],
|
||||
...rest,
|
||||
}
|
||||
|
||||
return product as Product
|
||||
}
|
||||
|
||||
export function normalizeCart(checkout: Checkout): Cart {
|
||||
const lines = checkout.lines as CheckoutLine[]
|
||||
const lineItems: LineItem[] = lines.length > 0 ? lines?.map<LineItem>(normalizeLineItem) : []
|
||||
|
||||
return {
|
||||
id: checkout.id,
|
||||
customerId: '',
|
||||
email: '',
|
||||
createdAt: checkout.created,
|
||||
currency: {
|
||||
code: checkout.totalPrice?.currency!,
|
||||
},
|
||||
taxesIncluded: false,
|
||||
lineItems,
|
||||
lineItemsSubtotalPrice: checkout.subtotalPrice?.gross?.amount!,
|
||||
subtotalPrice: checkout.subtotalPrice?.gross?.amount!,
|
||||
totalPrice: checkout.totalPrice?.gross.amount!,
|
||||
discounts: [],
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLineItem({ id, variant, quantity }: CheckoutLine): LineItem {
|
||||
return {
|
||||
id,
|
||||
variantId: String(variant?.id),
|
||||
productId: String(variant?.id),
|
||||
name: `${variant.product.name}`,
|
||||
quantity,
|
||||
variant: {
|
||||
id: String(variant?.id),
|
||||
sku: variant?.sku ?? '',
|
||||
name: variant?.name!,
|
||||
image: {
|
||||
url: variant?.media![0] ? variant?.media![0].url : placeholderImg,
|
||||
},
|
||||
requiresShipping: false,
|
||||
price: variant?.pricing?.price?.gross.amount!,
|
||||
listPrice: 0,
|
||||
},
|
||||
path: String(variant?.product?.slug),
|
||||
discounts: [],
|
||||
options: [],
|
||||
}
|
||||
}
|
12
framework/saleor/utils/queries/checkout-one.ts
Normal file
12
framework/saleor/utils/queries/checkout-one.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as fragment from '../fragments'
|
||||
|
||||
export const CheckoutOne = /* GraphQL */ `
|
||||
query CheckoutOne($checkoutId: UUID!) {
|
||||
checkout(token: $checkoutId) {
|
||||
... on Checkout {
|
||||
...CheckoutDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
${fragment.CheckoutDetails}
|
||||
`
|
13
framework/saleor/utils/queries/collection-many.ts
Normal file
13
framework/saleor/utils/queries/collection-many.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export const CollectionMany = /* GraphQL */ `
|
||||
query CollectionMany($first: Int!, $channel: String = "default-channel") {
|
||||
collections(first: $first, channel: $channel) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
13
framework/saleor/utils/queries/collection-one.ts
Normal file
13
framework/saleor/utils/queries/collection-one.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as fragment from '../fragments'
|
||||
|
||||
export const CollectionOne = /* GraphQL */ `
|
||||
query getProductsFromCollection($categoryId: ID!, $first: Int = 100, $channel: String = "default-channel") {
|
||||
collection(id: $categoryId, channel: $channel) {
|
||||
id
|
||||
products(first: $first) {
|
||||
...ProductConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
${fragment.ProductConnection}
|
||||
`
|
11
framework/saleor/utils/queries/customer-current.ts
Normal file
11
framework/saleor/utils/queries/customer-current.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const CustomerCurrent = /* GraphQL */ `
|
||||
query CustomerCurrent {
|
||||
me {
|
||||
id
|
||||
email
|
||||
firstName
|
||||
lastName
|
||||
dateJoined
|
||||
}
|
||||
}
|
||||
`
|
7
framework/saleor/utils/queries/customer-one.ts
Normal file
7
framework/saleor/utils/queries/customer-one.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const CustomerOne = /* GraphQL */ `
|
||||
query CustomerOne($customerAccessToken: String!) {
|
||||
customer(customerAccessToken: $customerAccessToken) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,16 @@
|
||||
export const getAllProductVendors = /* GraphQL */ `
|
||||
query getAllProductVendors($first: Int = 250, $cursor: String) {
|
||||
products(first: $first, after: $cursor) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
vendor
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,16 @@
|
||||
export const getAllProductsPathsQuery = /* GraphQL */ `
|
||||
query getAllProductPaths($first: Int = 100, $cursor: String, $channel: String = "default-channel") {
|
||||
products(first: $first, after: $cursor, channel: $channel) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
slug
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
14
framework/saleor/utils/queries/index.ts
Normal file
14
framework/saleor/utils/queries/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export { CollectionMany } from './collection-many'
|
||||
export { ProductOneBySlug } from './product-one-by-slug'
|
||||
export { ProductMany } from './product-many'
|
||||
export { CollectionOne } from './collection-one'
|
||||
export { CheckoutOne } from './checkout-one'
|
||||
export { PageMany } from './page-many'
|
||||
export { PageOne } from './page-one'
|
||||
export { CustomerCurrent } from './customer-current'
|
||||
|
||||
// getCustomerIdQuery
|
||||
export { CustomerOne } from './customer-one'
|
||||
|
||||
export { getAllProductsPathsQuery } from './get-all-products-paths-query'
|
||||
export { getAllProductVendors } from './get-all-product-vendors-query'
|
13
framework/saleor/utils/queries/page-many.ts
Normal file
13
framework/saleor/utils/queries/page-many.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export const PageMany = /* GraphQL */ `
|
||||
query PageMany($first: Int = 100) {
|
||||
pages(first: $first) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
9
framework/saleor/utils/queries/page-one.ts
Normal file
9
framework/saleor/utils/queries/page-one.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const PageOne = /* GraphQL */ `
|
||||
query PageOne($id: ID!) {
|
||||
page(id: $id) {
|
||||
id
|
||||
title
|
||||
slug
|
||||
}
|
||||
}
|
||||
`
|
15
framework/saleor/utils/queries/product-many.ts
Normal file
15
framework/saleor/utils/queries/product-many.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as fragment from '../fragments'
|
||||
|
||||
export const ProductMany = /* GraphQL */ `
|
||||
query ProductMany(
|
||||
$first: Int = 100
|
||||
$filter: ProductFilterInput
|
||||
$sortBy: ProductOrder
|
||||
$channel: String = "default-channel"
|
||||
) {
|
||||
products(first: $first, channel: $channel, filter: $filter, sortBy: $sortBy) {
|
||||
...ProductConnection
|
||||
}
|
||||
}
|
||||
${fragment.ProductConnection}
|
||||
`
|
43
framework/saleor/utils/queries/product-one-by-slug.ts
Normal file
43
framework/saleor/utils/queries/product-one-by-slug.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export const ProductOneBySlug = /* GraphQL */ `
|
||||
query ProductOneBySlug($slug: String!, $channel: String = "default-channel") {
|
||||
product(slug: $slug, channel: $channel) {
|
||||
id
|
||||
slug
|
||||
name
|
||||
description
|
||||
pricing {
|
||||
priceRange {
|
||||
start {
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
variants {
|
||||
id
|
||||
name
|
||||
attributes {
|
||||
attribute {
|
||||
name
|
||||
}
|
||||
values {
|
||||
name
|
||||
}
|
||||
}
|
||||
pricing {
|
||||
price {
|
||||
net {
|
||||
amount
|
||||
currency
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
media {
|
||||
url
|
||||
alt
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
20
framework/saleor/utils/throw-user-errors.ts
Normal file
20
framework/saleor/utils/throw-user-errors.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ValidationError } from '@commerce/utils/errors'
|
||||
|
||||
import { CheckoutError, CheckoutErrorCode, AppError, AccountError, AccountErrorCode } from '../schema'
|
||||
|
||||
export type UserErrors = Array<CheckoutError | AccountError | AppError>
|
||||
|
||||
export type UserErrorCode = CheckoutErrorCode | AccountErrorCode | null | undefined
|
||||
|
||||
export const throwUserErrors = (errors?: UserErrors) => {
|
||||
if (errors && errors.length) {
|
||||
throw new ValidationError({
|
||||
errors: errors.map(({ code, message }) => ({
|
||||
code: code ?? 'validation_error',
|
||||
message: message || '',
|
||||
})),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default throwUserErrors
|
Reference in New Issue
Block a user