Merge branch 'master' into jb/product-improvements

This commit is contained in:
Julián Benegas 2020-10-23 09:23:36 -03:00
commit 008d5b17d1
16 changed files with 142 additions and 12 deletions

View File

@ -0,0 +1,23 @@
import { serialize } from 'cookie'
import { LogoutHandlers } from '../logout'
const logoutHandler: LogoutHandlers['logout'] = async ({
res,
body: { redirectTo },
config,
}) => {
// Remove the cookie
res.setHeader(
'Set-Cookie',
serialize(config.customerCookie, '', { maxAge: -1, path: '/' })
)
// Only allow redirects to a relative URL
if (redirectTo?.startsWith('/')) {
res.redirect(redirectTo)
} else {
res.status(200).json({ data: null })
}
}
export default logoutHandler

View File

@ -0,0 +1,42 @@
import createApiHandler, {
BigcommerceApiHandler,
BigcommerceHandler,
} from '../utils/create-api-handler'
import isAllowedMethod from '../utils/is-allowed-method'
import { BigcommerceApiError } from '../utils/errors'
import logout from './handlers/logout'
export type LogoutHandlers = {
logout: BigcommerceHandler<null, { redirectTo?: string }>
}
const METHODS = ['GET']
const logoutApi: BigcommerceApiHandler<null, LogoutHandlers> = async (
req,
res,
config,
handlers
) => {
if (!isAllowedMethod(req, res, METHODS)) return
try {
const redirectTo = req.query.redirect_to
const body = typeof redirectTo === 'string' ? { redirectTo } : {}
return await handlers['logout']({ req, res, config, body })
} catch (error) {
console.error(error)
const message =
error instanceof BigcommerceApiError
? 'An unexpected error ocurred with the Bigcommerce API'
: 'An unexpected error ocurred'
res.status(500).json({ data: null, errors: [{ message }] })
}
}
const handlers = { logout }
export default createApiHandler(logoutApi, handlers, {})

View File

@ -66,9 +66,12 @@ if (!(STORE_API_URL && STORE_API_TOKEN && STORE_API_CLIENT_ID)) {
export class Config {
private config: BigcommerceConfig
constructor(config: BigcommerceConfigOptions) {
constructor(config: Omit<BigcommerceConfigOptions, 'customerCookie'>) {
this.config = {
...config,
// The customerCookie is not customizable for now, BC sets the cookie and it's
// not important to rename it
customerCookie: 'SHOP_TOKEN',
imageVariables: this.getImageVariables(config.images),
}
}

View File

@ -1,12 +1,15 @@
import type { GetAllProductPathsQuery } from 'lib/bigcommerce/schema'
import type {
GetAllProductPathsQuery,
GetAllProductPathsQueryVariables,
} from 'lib/bigcommerce/schema'
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges'
import { BigcommerceConfig, getConfig } from '..'
export const getAllProductPathsQuery = /* GraphQL */ `
query getAllProductPaths {
query getAllProductPaths($first: Int = 100) {
site {
products {
products(first: $first) {
edges {
node {
path
@ -23,11 +26,14 @@ export type ProductPath = NonNullable<
export type ProductPaths = ProductPath[]
export type { GetAllProductPathsQueryVariables }
export type GetAllProductPathsResult<
T extends { products: any[] } = { products: ProductPaths }
> = T
async function getAllProductPaths(opts?: {
variables?: GetAllProductPathsQueryVariables
config?: BigcommerceConfig
}): Promise<GetAllProductPathsResult>
@ -36,14 +42,17 @@ async function getAllProductPaths<
V = any
>(opts: {
query: string
variables?: V
config?: BigcommerceConfig
}): Promise<GetAllProductPathsResult<T>>
async function getAllProductPaths({
query = getAllProductPathsQuery,
variables,
config,
}: {
query?: string
variables?: GetAllProductPathsQueryVariables
config?: BigcommerceConfig
} = {}): Promise<GetAllProductPathsResult> {
config = getConfig(config)
@ -51,7 +60,7 @@ async function getAllProductPaths({
// required in case there's a custom `query`
const { data } = await config.fetch<
RecursivePartial<GetAllProductPathsQuery>
>(query)
>(query, { variables })
const products = data.site?.products?.edges
return {

View File

@ -14,7 +14,7 @@ export type BigcommerceApiHandler<
options: Options
) => void | Promise<void>
export type BigcommerceHandler<T = any, Body = any> = (options: {
export type BigcommerceHandler<T = any, Body = null> = (options: {
req: NextApiRequest
res: NextApiResponse<BigcommerceApiResponse<T>>
config: BigcommerceConfig

View File

@ -1827,7 +1827,9 @@ export type ProductConnnectionFragment = {
>
}
export type GetAllProductPathsQueryVariables = Exact<{ [key: string]: never }>
export type GetAllProductPathsQueryVariables = Exact<{
first?: Maybe<Scalars['Int']>
}>
export type GetAllProductPathsQuery = { __typename?: 'Query' } & {
site: { __typename?: 'Site' } & {

View File

@ -0,0 +1,35 @@
import { useCallback } from 'react'
import type { HookFetcher } from '@lib/commerce/utils/types'
import useCommerceLogout from '@lib/commerce/use-logout'
const defaultOpts = {
url: '/api/bigcommerce/customers/logout',
method: 'GET',
}
export const fetcher: HookFetcher<null> = (options, _, fetch) => {
return fetch({
...defaultOpts,
...options,
})
}
export function extendHook(customFetcher: typeof fetcher) {
const useLogout = () => {
const fn = useCommerceLogout<null>(defaultOpts, customFetcher)
return useCallback(
async function login() {
const data = await fn(null)
return data
},
[fn]
)
}
useLogout.extend = extendHook
return useLogout
}
export default extendHook(fetcher)

View File

@ -3,6 +3,7 @@ export interface CommerceAPIConfig {
apiToken: string
cartCookie: string
cartCookieMaxAge: number
customerCookie: string
fetch<Data = any, Variables = any>(
query: string,
queryData?: CommerceAPIFetchOptions<Variables>,

View File

@ -21,7 +21,7 @@ export type CommerceConfig = { fetcher: Fetcher<any> } & Omit<
>
export type CommerceContextValue = {
fetcherRef: MutableRefObject<any>
fetcherRef: MutableRefObject<Fetcher<any>>
locale: string
cartCookie: string
}

View File

@ -0,0 +1,5 @@
import useAction from './utils/use-action'
const useLogout = useAction
export default useLogout

View File

@ -9,7 +9,7 @@ export type FetcherOptions = {
body?: any
}
export type HookFetcher<T, Input> = (
export type HookFetcher<T, Input = null> = (
options: HookFetcherOptions | null,
input: Input,
fetch: Fetcher<T>
@ -22,5 +22,3 @@ export type HookFetcherOptions = {
}
export type HookInput = [string, string | number | undefined][]
export type HookDeps = string | number | undefined[]

View File

@ -2,7 +2,7 @@ import { useCallback } from 'react'
import type { HookFetcher, HookFetcherOptions } from './types'
import { useCommerce } from '..'
export default function useAction<T, Input>(
export default function useAction<T, Input = null>(
options: HookFetcherOptions,
fetcher: HookFetcher<T, Input>
) {

View File

@ -18,6 +18,12 @@ module.exports = {
source: '/checkout',
destination: '/api/bigcommerce/checkout',
},
// The logout is also an action so this route is not required, but it's also another way
// you can allow a logout!
{
source: '/logout',
destination: '/api/bigcommerce/customers/logout?redirect_to=/',
},
]
},
}

View File

@ -0,0 +1,3 @@
import logoutApi from '@lib/bigcommerce/api/customers/logout'
export default logoutApi()

View File

@ -2,10 +2,12 @@ import useSignup from '@lib/bigcommerce/use-signup'
import { Layout } from '@components/core'
import { Logo, Modal, Button } from '@components/ui'
import useLogin from '@lib/bigcommerce/use-login'
import useLogout from '@lib/bigcommerce/use-logout'
export default function Login() {
const signup = useSignup()
const login = useLogin()
const logout = useLogout()
// TODO: use this method. It can take more than 5 seconds to do a signup
const handleSignup = async () => {
// TODO: validate the password and email before calling the signup

View File

@ -27,6 +27,7 @@ export async function getStaticPaths() {
return {
paths: products.map((product) => `/product${product.node.path}`),
// If your store has tons of products, enable fallback mode to improve build times!
fallback: false,
}
}