Merge branch 'master' of https://github.com/vercel/commerce into custom-checkout

This commit is contained in:
Bel Curcio
2021-06-03 12:37:47 -03:00
329 changed files with 5858 additions and 3515 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,16 @@
import isAllowedMethod from '../utils/is-allowed-method'
import createApiHandler, {
ShopifyApiHandler,
} from '../utils/create-api-handler'
import {
SHOPIFY_CHECKOUT_ID_COOKIE,
SHOPIFY_CHECKOUT_URL_COOKIE,
SHOPIFY_CUSTOMER_TOKEN_COOKIE,
} from '../../const'
import { getConfig } from '..'
import associateCustomerWithCheckoutMutation from '../../utils/mutations/associate-customer-with-checkout'
const METHODS = ['GET']
const checkoutApi: ShopifyApiHandler<any> = async (req, res, config) => {
if (!isAllowedMethod(req, res, METHODS)) return
config = getConfig()
} from '../../../const'
import associateCustomerWithCheckoutMutation from '../../../utils/mutations/associate-customer-with-checkout'
import type { CheckoutEndpoint } from '.'
const checkout: CheckoutEndpoint['handlers']['checkout'] = async ({
req,
res,
config,
}) => {
const { cookies } = req
const checkoutUrl = cookies[SHOPIFY_CHECKOUT_URL_COOKIE]
const customerCookie = cookies[SHOPIFY_CUSTOMER_TOKEN_COOKIE]
@@ -43,4 +35,4 @@ const checkoutApi: ShopifyApiHandler<any> = async (req, res, config) => {
}
}
export default createApiHandler(checkoutApi, {}, {})
export default checkout

View File

@@ -0,0 +1,18 @@
import { GetAPISchema, createEndpoint } from '@commerce/api'
import checkoutEndpoint from '@commerce/api/endpoints/checkout'
import type { CheckoutSchema } from '../../../types/checkout'
import type { ShopifyAPI } from '../..'
import checkout from './checkout'
export type CheckoutAPI = GetAPISchema<ShopifyAPI, CheckoutSchema>
export type CheckoutEndpoint = CheckoutAPI['endpoint']
export const handlers: CheckoutEndpoint['handlers'] = { checkout }
const checkoutApi = createEndpoint<CheckoutAPI>({
handler: checkoutEndpoint,
handlers,
})
export default checkoutApi

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,20 @@
import type { CommerceAPIConfig } from '@commerce/api'
import {
CommerceAPI,
CommerceAPIConfig,
getCommerceApi as commerceApi,
} from '@commerce/api'
import {
API_URL,
API_TOKEN,
SHOPIFY_CHECKOUT_ID_COOKIE,
SHOPIFY_CUSTOMER_TOKEN_COOKIE,
SHOPIFY_CHECKOUT_ID_COOKIE,
} from '../const'
import fetchGraphqlApi from './utils/fetch-graphql-api'
import * as operations from './operations'
if (!API_URL) {
throw new Error(
`The environment variable NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN is missing and it's required to access your store`
@@ -18,44 +26,30 @@ if (!API_TOKEN) {
`The environment variable NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN is missing and it's required to access your store`
)
}
import fetchGraphqlApi from './utils/fetch-graphql-api'
export interface ShopifyConfig extends CommerceAPIConfig {}
export class Config {
private config: ShopifyConfig
const ONE_DAY = 60 * 60 * 24
constructor(config: ShopifyConfig) {
this.config = config
}
getConfig(userConfig: Partial<ShopifyConfig> = {}) {
return Object.entries(userConfig).reduce<ShopifyConfig>(
(cfg, [key, value]) => Object.assign(cfg, { [key]: value }),
{ ...this.config }
)
}
setConfig(newConfig: Partial<ShopifyConfig>) {
Object.assign(this.config, newConfig)
}
}
const config = new Config({
locale: 'en-US',
const config: ShopifyConfig = {
commerceUrl: API_URL,
apiToken: API_TOKEN!,
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
cartCookieMaxAge: 60 * 60 * 24 * 30,
fetch: fetchGraphqlApi,
apiToken: API_TOKEN,
customerCookie: SHOPIFY_CUSTOMER_TOKEN_COOKIE,
})
export function getConfig(userConfig?: Partial<ShopifyConfig>) {
return config.getConfig(userConfig)
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
cartCookieMaxAge: ONE_DAY * 30,
fetch: fetchGraphqlApi,
}
export function setConfig(newConfig: Partial<ShopifyConfig>) {
return config.setConfig(newConfig)
export const provider = {
config,
operations,
}
export type Provider = typeof provider
export type ShopifyAPI<P extends Provider = Provider> = CommerceAPI<P>
export function getCommerceApi<P extends Provider>(
customProvider: P = provider as any
): ShopifyAPI<P> {
return commerceApi(customProvider)
}

View File

@@ -0,0 +1,67 @@
import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import {
GetAllPagesQuery,
GetAllPagesQueryVariables,
PageEdge,
} from '../../schema'
import { normalizePages } from '../../utils'
import type { ShopifyConfig, Provider } from '..'
import type { GetAllPagesOperation, Page } from '../../types/page'
import getAllPagesQuery from '../../utils/queries/get-all-pages-query'
export default function getAllPagesOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllPages<T extends GetAllPagesOperation>(opts?: {
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']>
async function getAllPages<T extends GetAllPagesOperation>(
opts: {
config?: Partial<ShopifyConfig>
preview?: boolean
} & OperationOptions
): Promise<T['data']>
async function getAllPages<T extends GetAllPagesOperation>({
query = getAllPagesQuery,
config,
variables,
}: {
url?: string
config?: Partial<ShopifyConfig>
variables?: GetAllPagesQueryVariables
preview?: boolean
query?: string
} = {}): Promise<T['data']> {
const { fetch, locale, locales = ['en-US'] } = commerce.getConfig(config)
const { data } = await fetch<GetAllPagesQuery, GetAllPagesQueryVariables>(
query,
{
variables,
},
{
...(locale && {
headers: {
'Accept-Language': locale,
},
}),
}
)
return {
pages: locales.reduce<Page[]>(
(arr, locale) =>
arr.concat(normalizePages(data.pages.edges as PageEdge[], locale)),
[]
),
}
}
return getAllPages
}

View File

@@ -0,0 +1,55 @@
import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import { GetAllProductPathsOperation } from '../../types/product'
import {
GetAllProductPathsQuery,
GetAllProductPathsQueryVariables,
ProductEdge,
} from '../../schema'
import type { ShopifyConfig, Provider } from '..'
import { getAllProductsQuery } from '../../utils'
export default function getAllProductPathsOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllProductPaths<
T extends GetAllProductPathsOperation
>(opts?: {
variables?: T['variables']
config?: ShopifyConfig
}): Promise<T['data']>
async function getAllProductPaths<T extends GetAllProductPathsOperation>(
opts: {
variables?: T['variables']
config?: ShopifyConfig
} & OperationOptions
): Promise<T['data']>
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
query = getAllProductsQuery,
config,
variables,
}: {
query?: string
config?: ShopifyConfig
variables?: T['variables']
} = {}): Promise<T['data']> {
config = commerce.getConfig(config)
const { data } = await config.fetch<
GetAllProductPathsQuery,
GetAllProductPathsQueryVariables
>(query, { variables })
return {
products: data.products.edges.map(({ node: { handle } }) => ({
path: `/${handle}`,
})),
}
}
return getAllProductPaths
}

View File

@@ -0,0 +1,67 @@
import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import { GetAllProductsOperation } from '../../types/product'
import {
GetAllProductsQuery,
GetAllProductsQueryVariables,
Product as ShopifyProduct,
} from '../../schema'
import type { ShopifyConfig, Provider } from '..'
import getAllProductsQuery from '../../utils/queries/get-all-products-query'
import { normalizeProduct } from '../../utils'
export default function getAllProductsOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllProducts<T extends GetAllProductsOperation>(opts?: {
variables?: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']>
async function getAllProducts<T extends GetAllProductsOperation>(
opts: {
variables?: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
} & OperationOptions
): Promise<T['data']>
async function getAllProducts<T extends GetAllProductsOperation>({
query = getAllProductsQuery,
variables,
config,
}: {
query?: string
variables?: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
} = {}): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(config)
const { data } = await fetch<
GetAllProductsQuery,
GetAllProductsQueryVariables
>(
query,
{ variables },
{
...(locale && {
headers: {
'Accept-Language': locale,
},
}),
}
)
return {
products: data.products.edges.map(({ node }) =>
normalizeProduct(node as ShopifyProduct)
),
}
}
return getAllProducts
}

View File

@@ -0,0 +1,64 @@
import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import { normalizePage } from '../../utils'
import type { ShopifyConfig, Provider } from '..'
import {
GetPageQuery,
GetPageQueryVariables,
Page as ShopifyPage,
} from '../../schema'
import { GetPageOperation } from '../../types/page'
import getPageQuery from '../../utils/queries/get-page-query'
export default function getPageOperation({
commerce,
}: OperationContext<Provider>) {
async function getPage<T extends GetPageOperation>(opts: {
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']>
async function getPage<T extends GetPageOperation>(
opts: {
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
} & OperationOptions
): Promise<T['data']>
async function getPage<T extends GetPageOperation>({
query = getPageQuery,
variables,
config,
}: {
query?: string
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']> {
const { fetch, locale = 'en-US' } = commerce.getConfig(config)
const {
data: { node: page },
} = await fetch<GetPageQuery, GetPageQueryVariables>(
query,
{
variables,
},
{
...(locale && {
headers: {
'Accept-Language': locale,
},
}),
}
)
return page ? { page: normalizePage(page as ShopifyPage, locale) } : {}
}
return getPage
}

View File

@@ -0,0 +1,63 @@
import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import { GetProductOperation } from '../../types/product'
import { normalizeProduct, getProductQuery } from '../../utils'
import type { ShopifyConfig, Provider } from '..'
import { GetProductBySlugQuery, Product as ShopifyProduct } from '../../schema'
export default function getProductOperation({
commerce,
}: OperationContext<Provider>) {
async function getProduct<T extends GetProductOperation>(opts: {
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']>
async function getProduct<T extends GetProductOperation>(
opts: {
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
} & OperationOptions
): Promise<T['data']>
async function getProduct<T extends GetProductOperation>({
query = getProductQuery,
variables,
config: cfg,
}: {
query?: string
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(cfg)
const {
data: { productByHandle },
} = await fetch<GetProductBySlugQuery>(
query,
{
variables,
},
{
...(locale && {
headers: {
'Accept-Language': locale,
},
}),
}
)
return {
...(productByHandle && {
product: normalizeProduct(productByHandle as ShopifyProduct),
}),
}
}
return getProduct
}

View File

@@ -0,0 +1,62 @@
import type {
OperationContext,
OperationOptions,
} from '@commerce/api/operations'
import { GetSiteInfoQueryVariables } from '../../schema'
import type { ShopifyConfig, Provider } from '..'
import { GetSiteInfoOperation } from '../../types/site'
import { getCategories, getBrands, getSiteInfoQuery } from '../../utils'
export default function getSiteInfoOperation({
commerce,
}: OperationContext<Provider>) {
async function getSiteInfo<T extends GetSiteInfoOperation>(opts?: {
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<T['data']>
async function getSiteInfo<T extends GetSiteInfoOperation>(
opts: {
config?: Partial<ShopifyConfig>
preview?: boolean
} & OperationOptions
): Promise<T['data']>
async function getSiteInfo<T extends GetSiteInfoOperation>({
query = getSiteInfoQuery,
config,
variables,
}: {
query?: string
config?: Partial<ShopifyConfig>
preview?: boolean
variables?: GetSiteInfoQueryVariables
} = {}): Promise<T['data']> {
const cfg = commerce.getConfig(config)
const categories = await getCategories(cfg)
const brands = await getBrands(cfg)
/*
const { fetch, locale } = cfg
const { data } = await fetch<GetSiteInfoQuery, GetSiteInfoQueryVariables>(
query,
{ variables },
{
...(locale && {
headers: {
'Accept-Language': locale,
},
}),
}
)
*/
return {
categories,
brands,
}
}
return getSiteInfo
}

View File

@@ -0,0 +1,7 @@
export { default as getAllPages } from './get-all-pages'
export { default as getPage } from './get-page'
export { default as getAllProducts } from './get-all-products'
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'

View File

@@ -0,0 +1,48 @@
import type { ServerResponse } from 'http'
import type { OperationContext } from '@commerce/api/operations'
import type { LoginOperation } from '../../types/login'
import type { ShopifyConfig, Provider } from '..'
import {
customerAccessTokenCreateMutation,
setCustomerToken,
throwUserErrors,
} from '../../utils'
import { CustomerAccessTokenCreateMutation } from '../../schema'
export default function loginOperation({
commerce,
}: OperationContext<Provider>) {
async function login<T extends LoginOperation>({
query = customerAccessTokenCreateMutation,
variables,
config,
}: {
query?: string
variables: T['variables']
res: ServerResponse
config?: ShopifyConfig
}): Promise<T['data']> {
config = commerce.getConfig(config)
const {
data: { customerAccessTokenCreate },
} = await config.fetch<CustomerAccessTokenCreateMutation>(query, {
variables,
})
throwUserErrors(customerAccessTokenCreate?.customerUserErrors)
const customerAccessToken = customerAccessTokenCreate?.customerAccessToken
const accessToken = customerAccessToken?.accessToken
if (accessToken) {
setCustomerToken(accessToken)
}
return {
result: customerAccessToken?.accessToken,
}
}
return login
}

View File

@@ -1,58 +0,0 @@
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
import { ShopifyConfig, getConfig } from '..'
export type ShopifyApiHandler<
T = any,
H extends ShopifyHandlers = {},
Options extends {} = {}
> = (
req: NextApiRequest,
res: NextApiResponse<ShopifyApiResponse<T>>,
config: ShopifyConfig,
handlers: H,
// Custom configs that may be used by a particular handler
options: Options
) => void | Promise<void>
export type ShopifyHandler<T = any, Body = null> = (options: {
req: NextApiRequest
res: NextApiResponse<ShopifyApiResponse<T>>
config: ShopifyConfig
body: Body
}) => void | Promise<void>
export type ShopifyHandlers<T = any> = {
[k: string]: ShopifyHandler<T, any>
}
export type ShopifyApiResponse<T> = {
data: T | null
errors?: { message: string; code?: string }[]
}
export default function createApiHandler<
T = any,
H extends ShopifyHandlers = {},
Options extends {} = {}
>(
handler: ShopifyApiHandler<T, H, Options>,
handlers: H,
defaultOptions: Options
) {
return function getApiHandler({
config,
operations,
options,
}: {
config?: ShopifyConfig
operations?: Partial<H>
options?: Options extends {} ? Partial<Options> : never
} = {}): NextApiHandler {
const ops = { ...operations, ...handlers }
const opts = { ...defaultOptions, ...options }
return function apiHandler(req, res) {
return handler(req, res, getConfig(config), ops, opts)
}
}
}

View File

@@ -1,41 +0,0 @@
import { ProductEdge } from '../../schema'
import { ShopifyConfig } from '..'
const fetchAllProducts = async ({
config,
query,
variables,
acc = [],
cursor,
}: {
config: ShopifyConfig
query: string
acc?: ProductEdge[]
variables?: any
cursor?: string
}): Promise<ProductEdge[]> => {
const { data } = await config.fetch(query, {
variables: { ...variables, cursor },
})
const edges: ProductEdge[] = data.products?.edges ?? []
const hasNextPage = data.products?.pageInfo?.hasNextPage
acc = acc.concat(edges)
if (hasNextPage) {
const cursor = edges.pop()?.cursor
if (cursor) {
return fetchAllProducts({
config,
query,
variables,
acc,
cursor,
})
}
}
return acc
}
export default fetchAllProducts

View File

@@ -1,28 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next'
export default function isAllowedMethod(
req: NextApiRequest,
res: NextApiResponse,
allowedMethods: string[]
) {
const methods = allowedMethods.includes('OPTIONS')
? allowedMethods
: [...allowedMethods, 'OPTIONS']
if (!req.method || !methods.includes(req.method)) {
res.status(405)
res.setHeader('Allow', methods.join(', '))
res.end()
return false
}
if (req.method === 'OPTIONS') {
res.status(200)
res.setHeader('Allow', methods.join(', '))
res.setHeader('Content-Length', '0')
res.end()
return false
}
return true
}

View File

@@ -1,2 +0,0 @@
export type WishlistItem = { product: any; id: number }
export default function () {}