forked from crowetic/commerce
Merge branch 'master' into jb/product-improvements
This commit is contained in:
commit
008d5b17d1
23
lib/bigcommerce/api/customers/handlers/logout.ts
Normal file
23
lib/bigcommerce/api/customers/handlers/logout.ts
Normal 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
|
42
lib/bigcommerce/api/customers/logout.ts
Normal file
42
lib/bigcommerce/api/customers/logout.ts
Normal 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, {})
|
@ -66,9 +66,12 @@ if (!(STORE_API_URL && STORE_API_TOKEN && STORE_API_CLIENT_ID)) {
|
|||||||
export class Config {
|
export class Config {
|
||||||
private config: BigcommerceConfig
|
private config: BigcommerceConfig
|
||||||
|
|
||||||
constructor(config: BigcommerceConfigOptions) {
|
constructor(config: Omit<BigcommerceConfigOptions, 'customerCookie'>) {
|
||||||
this.config = {
|
this.config = {
|
||||||
...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),
|
imageVariables: this.getImageVariables(config.images),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||||
import filterEdges from '../utils/filter-edges'
|
import filterEdges from '../utils/filter-edges'
|
||||||
import { BigcommerceConfig, getConfig } from '..'
|
import { BigcommerceConfig, getConfig } from '..'
|
||||||
|
|
||||||
export const getAllProductPathsQuery = /* GraphQL */ `
|
export const getAllProductPathsQuery = /* GraphQL */ `
|
||||||
query getAllProductPaths {
|
query getAllProductPaths($first: Int = 100) {
|
||||||
site {
|
site {
|
||||||
products {
|
products(first: $first) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
path
|
path
|
||||||
@ -23,11 +26,14 @@ export type ProductPath = NonNullable<
|
|||||||
|
|
||||||
export type ProductPaths = ProductPath[]
|
export type ProductPaths = ProductPath[]
|
||||||
|
|
||||||
|
export type { GetAllProductPathsQueryVariables }
|
||||||
|
|
||||||
export type GetAllProductPathsResult<
|
export type GetAllProductPathsResult<
|
||||||
T extends { products: any[] } = { products: ProductPaths }
|
T extends { products: any[] } = { products: ProductPaths }
|
||||||
> = T
|
> = T
|
||||||
|
|
||||||
async function getAllProductPaths(opts?: {
|
async function getAllProductPaths(opts?: {
|
||||||
|
variables?: GetAllProductPathsQueryVariables
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
}): Promise<GetAllProductPathsResult>
|
}): Promise<GetAllProductPathsResult>
|
||||||
|
|
||||||
@ -36,14 +42,17 @@ async function getAllProductPaths<
|
|||||||
V = any
|
V = any
|
||||||
>(opts: {
|
>(opts: {
|
||||||
query: string
|
query: string
|
||||||
|
variables?: V
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
}): Promise<GetAllProductPathsResult<T>>
|
}): Promise<GetAllProductPathsResult<T>>
|
||||||
|
|
||||||
async function getAllProductPaths({
|
async function getAllProductPaths({
|
||||||
query = getAllProductPathsQuery,
|
query = getAllProductPathsQuery,
|
||||||
|
variables,
|
||||||
config,
|
config,
|
||||||
}: {
|
}: {
|
||||||
query?: string
|
query?: string
|
||||||
|
variables?: GetAllProductPathsQueryVariables
|
||||||
config?: BigcommerceConfig
|
config?: BigcommerceConfig
|
||||||
} = {}): Promise<GetAllProductPathsResult> {
|
} = {}): Promise<GetAllProductPathsResult> {
|
||||||
config = getConfig(config)
|
config = getConfig(config)
|
||||||
@ -51,7 +60,7 @@ async function getAllProductPaths({
|
|||||||
// required in case there's a custom `query`
|
// required in case there's a custom `query`
|
||||||
const { data } = await config.fetch<
|
const { data } = await config.fetch<
|
||||||
RecursivePartial<GetAllProductPathsQuery>
|
RecursivePartial<GetAllProductPathsQuery>
|
||||||
>(query)
|
>(query, { variables })
|
||||||
const products = data.site?.products?.edges
|
const products = data.site?.products?.edges
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -14,7 +14,7 @@ export type BigcommerceApiHandler<
|
|||||||
options: Options
|
options: Options
|
||||||
) => void | Promise<void>
|
) => void | Promise<void>
|
||||||
|
|
||||||
export type BigcommerceHandler<T = any, Body = any> = (options: {
|
export type BigcommerceHandler<T = any, Body = null> = (options: {
|
||||||
req: NextApiRequest
|
req: NextApiRequest
|
||||||
res: NextApiResponse<BigcommerceApiResponse<T>>
|
res: NextApiResponse<BigcommerceApiResponse<T>>
|
||||||
config: BigcommerceConfig
|
config: BigcommerceConfig
|
||||||
|
4
lib/bigcommerce/schema.d.ts
vendored
4
lib/bigcommerce/schema.d.ts
vendored
@ -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' } & {
|
export type GetAllProductPathsQuery = { __typename?: 'Query' } & {
|
||||||
site: { __typename?: 'Site' } & {
|
site: { __typename?: 'Site' } & {
|
||||||
|
35
lib/bigcommerce/use-logout.tsx
Normal file
35
lib/bigcommerce/use-logout.tsx
Normal 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)
|
@ -3,6 +3,7 @@ export interface CommerceAPIConfig {
|
|||||||
apiToken: string
|
apiToken: string
|
||||||
cartCookie: string
|
cartCookie: string
|
||||||
cartCookieMaxAge: number
|
cartCookieMaxAge: number
|
||||||
|
customerCookie: string
|
||||||
fetch<Data = any, Variables = any>(
|
fetch<Data = any, Variables = any>(
|
||||||
query: string,
|
query: string,
|
||||||
queryData?: CommerceAPIFetchOptions<Variables>,
|
queryData?: CommerceAPIFetchOptions<Variables>,
|
||||||
|
@ -21,7 +21,7 @@ export type CommerceConfig = { fetcher: Fetcher<any> } & Omit<
|
|||||||
>
|
>
|
||||||
|
|
||||||
export type CommerceContextValue = {
|
export type CommerceContextValue = {
|
||||||
fetcherRef: MutableRefObject<any>
|
fetcherRef: MutableRefObject<Fetcher<any>>
|
||||||
locale: string
|
locale: string
|
||||||
cartCookie: string
|
cartCookie: string
|
||||||
}
|
}
|
||||||
|
5
lib/commerce/use-logout.tsx
Normal file
5
lib/commerce/use-logout.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import useAction from './utils/use-action'
|
||||||
|
|
||||||
|
const useLogout = useAction
|
||||||
|
|
||||||
|
export default useLogout
|
@ -9,7 +9,7 @@ export type FetcherOptions = {
|
|||||||
body?: any
|
body?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HookFetcher<T, Input> = (
|
export type HookFetcher<T, Input = null> = (
|
||||||
options: HookFetcherOptions | null,
|
options: HookFetcherOptions | null,
|
||||||
input: Input,
|
input: Input,
|
||||||
fetch: Fetcher<T>
|
fetch: Fetcher<T>
|
||||||
@ -22,5 +22,3 @@ export type HookFetcherOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type HookInput = [string, string | number | undefined][]
|
export type HookInput = [string, string | number | undefined][]
|
||||||
|
|
||||||
export type HookDeps = string | number | undefined[]
|
|
||||||
|
@ -2,7 +2,7 @@ import { useCallback } from 'react'
|
|||||||
import type { HookFetcher, HookFetcherOptions } from './types'
|
import type { HookFetcher, HookFetcherOptions } from './types'
|
||||||
import { useCommerce } from '..'
|
import { useCommerce } from '..'
|
||||||
|
|
||||||
export default function useAction<T, Input>(
|
export default function useAction<T, Input = null>(
|
||||||
options: HookFetcherOptions,
|
options: HookFetcherOptions,
|
||||||
fetcher: HookFetcher<T, Input>
|
fetcher: HookFetcher<T, Input>
|
||||||
) {
|
) {
|
||||||
|
@ -18,6 +18,12 @@ module.exports = {
|
|||||||
source: '/checkout',
|
source: '/checkout',
|
||||||
destination: '/api/bigcommerce/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=/',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
3
pages/api/bigcommerce/customers/logout.ts
Normal file
3
pages/api/bigcommerce/customers/logout.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import logoutApi from '@lib/bigcommerce/api/customers/logout'
|
||||||
|
|
||||||
|
export default logoutApi()
|
@ -2,10 +2,12 @@ import useSignup from '@lib/bigcommerce/use-signup'
|
|||||||
import { Layout } from '@components/core'
|
import { Layout } from '@components/core'
|
||||||
import { Logo, Modal, Button } from '@components/ui'
|
import { Logo, Modal, Button } from '@components/ui'
|
||||||
import useLogin from '@lib/bigcommerce/use-login'
|
import useLogin from '@lib/bigcommerce/use-login'
|
||||||
|
import useLogout from '@lib/bigcommerce/use-logout'
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const signup = useSignup()
|
const signup = useSignup()
|
||||||
const login = useLogin()
|
const login = useLogin()
|
||||||
|
const logout = useLogout()
|
||||||
// TODO: use this method. It can take more than 5 seconds to do a signup
|
// TODO: use this method. It can take more than 5 seconds to do a signup
|
||||||
const handleSignup = async () => {
|
const handleSignup = async () => {
|
||||||
// TODO: validate the password and email before calling the signup
|
// TODO: validate the password and email before calling the signup
|
||||||
|
@ -27,6 +27,7 @@ export async function getStaticPaths() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
paths: products.map((product) => `/product${product.node.path}`),
|
paths: products.map((product) => `/product${product.node.path}`),
|
||||||
|
// If your store has tons of products, enable fallback mode to improve build times!
|
||||||
fallback: false,
|
fallback: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user