changed shopify fetch and graphQL fetch types to include useAdminApi flag, generated shopify admin api schema, changed RemoveItemHook of commerce/types/wishlist, created shopify wishlist api routes (addItem, removeItem, getWishlist), added shopify api operation getCustomerWishlist, updated index files of api/operations, utils and api/wishlist, created shopify wishlist types created getCustomerId function as a util in shopify added customerUpdate mutation, added customer query with admin api, added getCustomerId query

This commit is contained in:
Christos Emmanouilidis 2022-03-31 15:33:35 +03:00
parent 7fb6734274
commit 890836bc24
24 changed files with 105091 additions and 5569 deletions

21629
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,12 +11,19 @@
"dev": "turbo run dev",
"start": "turbo run start",
"types": "turbo run types",
"prettier-fix": "prettier --write ."
"prettier-fix": "prettier --write .",
"generate": "graphql-codegen --config codegen.yml"
},
"devDependencies": {
"husky": "^7.0.4",
"prettier": "^2.5.1",
"turbo": "^1.1.2"
"turbo": "^1.1.2",
"@graphql-codegen/typescript-document-nodes": "2.2.8",
"@graphql-codegen/typescript": "2.4.8",
"@graphql-codegen/typescript-operations": "2.3.5",
"@graphql-codegen/typescript-graphql-files-modules": "2.1.1",
"@graphql-codegen/introspection": "2.1.1",
"@graphql-codegen/cli": "2.6.2"
},
"husky": {
"hooks": {

View File

@ -160,7 +160,8 @@ export interface CommerceAPIConfig {
fetch<Data = any, Variables = any>(
query: string,
queryData?: CommerceAPIFetchOptions<Variables>,
fetchOptions?: FetchOptions
fetchOptions?: FetchOptions,
useAdminApi?: boolean
): Promise<GraphQLFetcherResult<Data>>
}
@ -170,7 +171,8 @@ export type GraphQLFetcher<
> = (
query: string,
queryData?: CommerceAPIFetchOptions<Variables>,
fetchOptions?: FetchOptions
fetchOptions?: FetchOptions,
useAdminApi?: boolean
) => Promise<Data>
export interface GraphQLFetcherResult<Data = any> {

View File

@ -27,11 +27,10 @@ export type AddItemHook<T extends WishlistTypes = WishlistTypes> = {
}
export type RemoveItemHook<T extends WishlistTypes = WishlistTypes> = {
data: T['wishlist'] | null
body: { itemId: string }
fetcherInput: { itemId: string }
actionInput: { id: string }
input: { wishlist?: { includeProducts?: boolean } }
data: T['wishlist']
body: { item: T['itemBody'] }
fetcherInput: { item: T['itemBody'] }
actionInput: T['itemBody']
}
export type WishlistSchema<T extends WishlistTypes = WishlistTypes> = {

28080
packages/shopify/admin-schema.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,14 @@
{
"schema": {
"https://${NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN}/api/2021-07/graphql.json": {
"https://${NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN}/admin/api/2022-01/graphql.json": {
"headers": {
"X-Shopify-Storefront-Access-Token": "${NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN}"
"X-Shopify-Access-Token": "${NEXT_PUBLIC_SHOPIFY_ADMIN_ACCESS_TOKEN}"
}
}
},
"documents": [
{
"./src/**/*.{ts,tsx}": {
"noRequire": true
}
}
],
"documents": [{}],
"generates": {
"./schema.d.ts": {
"./admin-schema.d.ts": {
"plugins": ["typescript", "typescript-operations"],
"config": {
"scalars": {
@ -22,7 +16,7 @@
}
}
},
"./schema.graphql": {
"./admin-schema.graphql": {
"plugins": ["schema-ast"]
}
},

View File

@ -51,6 +51,7 @@
"dependencies": {
"@vercel/commerce": "^0.0.1",
"@vercel/fetch": "^6.1.1",
"graphql": "^15.8.0",
"lodash.debounce": "^4.0.8"
},
"peerDependencies": {

View File

@ -1 +0,0 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1,84 @@
import type { WishlistEndpoint } from '.'
import { customerUpdateMutation, getCustomerId } from '../../../utils'
import type {
MutationCustomerUpdateArgs,
Mutation,
MetafieldInput,
} from '../../../../admin-schema'
import { WishlistItem } from './../../../types/wishlist'
const addWishlistItem: WishlistEndpoint['handlers']['addItem'] = async ({
res,
body: { item, customerToken },
config,
commerce,
}) => {
if (!item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing item!' }],
})
}
if (customerToken) {
const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
if (!customerId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
let wishlistItems: WishlistItem[] = []
let metafieldInput: MetafieldInput = {}
const { wishlist } = await commerce.getCustomerWishlist({
variables: { customerId },
config,
})
if (wishlist) {
wishlistItems = wishlist?.items?.map(({ product, ...rest }) => {
return rest
})!
metafieldInput = { id: wishlist?.id! }
} else {
metafieldInput = { namespace: 'my_fields', key: 'wishlist' }
}
wishlistItems?.push(item)
const jsonString = JSON.stringify(wishlistItems)
try {
const {
data: { customerUpdate },
} = await config.fetch<Mutation, MutationCustomerUpdateArgs>(
customerUpdateMutation,
{
variables: {
input: {
id: customerId,
metafields: [
{
...metafieldInput,
value: jsonString,
},
],
},
},
},
{},
true
)
return res.status(200).json({ data: customerUpdate?.customer! })
} catch (error: any) {
console.log(error)
res.status(500).json({ data: null, errors: [{ message: error }] })
}
}
}
export default addWishlistItem

View File

@ -0,0 +1,40 @@
import type { Wishlist } from '../../../types/wishlist'
import type { WishlistEndpoint } from '.'
import { getCustomerId } from '../../../utils'
import { SHOPIFY_CUSTOMER_TOKEN_COOKIE } from '../../../const'
// Return wishlist info
const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({
req,
res,
config,
commerce,
}) => {
const { cookies } = req
const customerToken = cookies[SHOPIFY_CUSTOMER_TOKEN_COOKIE]
let result: { data?: Wishlist } = {}
if (customerToken) {
const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
if (!customerId) {
// If the customerToken is invalid, then this request is too
return res.status(404).json({
data: null,
errors: [{ message: 'Wishlist not found' }],
})
}
const { wishlist } = await commerce.getCustomerWishlist({
variables: { customerId },
config,
})
result = { data: wishlist }
}
res.status(200).json({ data: result.data ?? null })
}
export default getWishlist

View File

@ -0,0 +1,24 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import wishlistEndpoint from '@vercel/commerce/api/endpoints/wishlist'
import type { WishlistSchema } from '../../../types/wishlist'
import type { ShopifyAPI } from '../..'
import addItem from './add-item'
import removeItem from './remove-item'
import getWishlist from './get-wishlist'
export type WishlistAPI = GetAPISchema<ShopifyAPI, WishlistSchema>
export type WishlistEndpoint = WishlistAPI['endpoint']
export const handlers: WishlistEndpoint['handlers'] = {
getWishlist,
addItem,
removeItem,
}
const wishlistApi = createEndpoint<WishlistAPI>({
handler: wishlistEndpoint,
handlers,
})
export default wishlistApi

View File

@ -0,0 +1,76 @@
import { WishlistItemBody } from './../../../../../commerce/src/types/wishlist'
import type { WishlistEndpoint } from '.'
import { customerUpdateMutation, getCustomerId } from '../../../utils'
import type {
MutationCustomerUpdateArgs,
Mutation,
} from '../../../../admin-schema'
const removeWishlistItem: WishlistEndpoint['handlers']['removeItem'] = async ({
res,
body: { item, customerToken },
config,
commerce,
}) => {
if (!item) {
return res.status(400).json({
data: null,
errors: [{ message: 'Missing item!' }],
})
}
if (customerToken) {
const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
if (!customerId) {
return res.status(400).json({
data: null,
errors: [{ message: 'Invalid request' }],
})
}
const { wishlist } = await commerce.getCustomerWishlist({
variables: { customerId },
config,
})
const WishlistItems: WishlistItemBody[] = wishlist?.items
?.filter((wishlistItem) => wishlistItem.productId !== item.productId)
.map(({ product, ...rest }) => {
return rest
})!
const jsonString = JSON.stringify(WishlistItems)
try {
const {
data: { customerUpdate },
} = await config.fetch<Mutation, MutationCustomerUpdateArgs>(
customerUpdateMutation,
{
variables: {
input: {
id: customerId,
metafields: [
{
id: wishlist?.id,
value: jsonString,
},
],
},
},
},
{},
true
)
return res.status(200).json({ data: customerUpdate?.customer! })
} catch (error: any) {
console.log(error)
res.status(500).json({ data: null, errors: [{ message: error }] })
}
}
}
export default removeWishlistItem

View File

@ -0,0 +1,92 @@
import { ShopifyConfig, Provider } from './../index'
import { getCustomerAdminQuery } from '../../utils/queries'
import type {
OperationContext,
OperationOptions,
} from '@vercel/commerce/api/operations'
import type {
GetCustomerWishlistOperation,
Wishlist,
} from '../../types/wishlist'
import { Product } from '../../types/product'
export default function getCustomerWishlistOperation({
commerce,
}: OperationContext<Provider>) {
async function getCustomerWishlist<
T extends GetCustomerWishlistOperation
>(opts: {
variables: T['variables']
config?: ShopifyConfig
includeProducts?: boolean
}): Promise<T['data']>
async function getCustomerWishlist<T extends GetCustomerWishlistOperation>(
opts: {
variables: T['variables']
config?: ShopifyConfig
includeProducts?: boolean
} & OperationOptions
): Promise<T['data']>
async function getCustomerWishlist<T extends GetCustomerWishlistOperation>({
query = getCustomerAdminQuery,
config,
variables,
includeProducts,
}: {
query?: string
variables: T['variables']
config?: ShopifyConfig
includeProducts?: boolean
}): Promise<T['data']> {
const { fetch } = commerce.getConfig(config)
const { data } = await fetch(
query,
{
variables: {
id: variables.customerId,
},
},
undefined,
true
)
if (!data.customer.metafield) {
return data
}
const tempWishlist =
data.customer.metafield && JSON.parse(data.customer?.metafield?.value!)
const wishlist: Wishlist = {
id: String(data.customer?.metafield?.id!),
items: tempWishlist,
}
const { products } = await commerce.getAllProducts({
variables: { first: 250 },
config,
})
const wishlistItems = products.filter((item: Product) =>
wishlist.items?.find(({ productId }) => item.id === productId)
)
wishlist.items?.forEach((item) => {
const product =
item &&
wishlistItems.find((wishlistItem) => wishlistItem.id === item.productId)
if (item && product) {
item.product = product as any
}
})
return {
wishlist,
}
}
return getCustomerWishlist
}

View File

@ -5,3 +5,4 @@ export { default as getAllProductPaths } from './get-all-product-paths'
export { default as getProduct } from './get-product'
export { default as getSiteInfo } from './get-site-info'
export { default as login } from './login'
export { default as getCustomerWishlist } from './get-customer-wishlist'

View File

@ -1 +1,34 @@
import { Product } from '@vercel/commerce/types/product'
import * as Core from '@vercel/commerce/types/wishlist'
import { Customer } from '../../admin-schema'
export * from '@vercel/commerce/types/wishlist'
export type WishlistItem = NonNullable<Core.WishlistItemBody> & {
product?: Product
}
export type Wishlist = {
id: string
items?: WishlistItem[]
}
export type WishlistTypes = {
wishlist: Wishlist
itemBody: Core.WishlistItemBody
customer: Customer
}
export type RemoveItemHook<T extends WishlistTypes = WishlistTypes> = {
body: { item: T['itemBody'] }
fetcherInput: { item: T['itemBody'] }
actionInput: T['itemBody']
}
export type WishlistSchema = Core.WishlistSchema<WishlistTypes>
export type GetCustomerWishlistOperation =
Core.GetCustomerWishlistOperation<WishlistTypes>
export type GetWishlistHook = Core.GetWishlistHook<WishlistTypes>
export * from '@vercel/commerce/types/wishlist'

View File

@ -0,0 +1,31 @@
import { ShopifyConfig } from '../api'
import { getCustomerIdQuery } from './queries'
import type {
GetCustomerIdQuery,
GetCustomerQueryVariables,
} from '../../schema'
export type customerId = {
id: string
}
const getCustomerId = async ({
config,
customerToken,
}: {
config: ShopifyConfig
customerToken: string
}): Promise<string | undefined | null> => {
const { data } = await config.fetch<
GetCustomerIdQuery,
GetCustomerQueryVariables
>(getCustomerIdQuery, {
variables: {
customerAccessToken: customerToken,
},
})
return String(data.customer?.id)
}
export default getCustomerId

View File

@ -2,6 +2,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 getBrands } from './get-brands'
export { default as getCustomerId } from './get-customer-id'
export { default as getCategories } from './get-categories'
export { default as getCheckoutId } from './get-checkout-id'
export { default as checkoutCreate } from './checkout-create'

View File

@ -0,0 +1,22 @@
const customerUpdateMutation = /* GraphQL */ `
mutation customerUpdate($input: CustomerInput!) {
customerUpdate(input: $input) {
customer {
id
}
userErrors {
field
message
}
customer {
id
metafield(key: "my_fields", namespace: "wishlist") {
id
value
}
}
}
}
`
export default customerUpdateMutation

View File

@ -7,3 +7,4 @@ export { default as customerAccessTokenCreateMutation } from './customer-access-
export { default as customerAccessTokenDeleteMutation } from './customer-access-token-delete'
export { default as customerActivateMutation } from './customer-activate'
export { default as customerActivateByUrlMutation } from './customer-activate-by-url'
export { default as customerUpdateMutation } from './customer-update'

View File

@ -0,0 +1,11 @@
export const getCustomerAdminQuery = /* GraphQL */ `
query getCustomerAdmin($id: ID!) {
customer(id: $id) {
metafield(namespace: "my_fields", key: "wishlist") {
id
value
}
}
}
`
export default getCustomerAdminQuery

View File

@ -0,0 +1,8 @@
export const getCustomerIdQuery = /* GraphQL */ `
query getCustomerId($customerAccessToken: String!) {
customer(customerAccessToken: $customerAccessToken) {
id
}
}
`
export default getCustomerIdQuery

View File

@ -9,3 +9,5 @@ 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'
export { default as getCustomerAdminQuery } from './get-customer-admin-query'
export { default as getCustomerIdQuery } from './get-customer-id-query'

10541
yarn.lock

File diff suppressed because it is too large Load Diff