mirror of
https://github.com/vercel/commerce.git
synced 2025-05-18 07:26:59 +00:00
feat: Add customer authentication
This commit is contained in:
parent
42a02d40ff
commit
0f381d4cc6
@ -1,6 +1,6 @@
|
|||||||
import { FC, useEffect, useState, useCallback } from 'react'
|
import { FC, useEffect, useState, useCallback } from 'react'
|
||||||
import { Logo, Button, Input } from '@components/ui'
|
import { Logo, Button, Input } from '@components/ui'
|
||||||
import useLogin from '@framework/auth/use-login'
|
import useLogin from '@commerce/auth/use-login'
|
||||||
import { useUI } from '@components/ui/context'
|
import { useUI } from '@components/ui/context'
|
||||||
import { validate } from 'email-validator'
|
import { validate } from 'email-validator'
|
||||||
|
|
||||||
@ -18,7 +18,6 @@ const LoginView: React.FC = () => {
|
|||||||
|
|
||||||
const handleLogin = async (e: React.SyntheticEvent<EventTarget>) => {
|
const handleLogin = async (e: React.SyntheticEvent<EventTarget>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (!dirty && !disabled) {
|
if (!dirty && !disabled) {
|
||||||
setDirty(true)
|
setDirty(true)
|
||||||
handleValidation()
|
handleValidation()
|
||||||
|
@ -17,7 +17,7 @@ const loginEndpoint: GetAPISchema<
|
|||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
debugger
|
||||||
try {
|
try {
|
||||||
const body = req.body ?? {}
|
const body = req.body ?? {}
|
||||||
return await handlers['login']({ ...ctx, body })
|
return await handlers['login']({ ...ctx, body })
|
||||||
|
@ -146,6 +146,7 @@ export const createEndpoint =
|
|||||||
options?: API['schema']['endpoint']['options']
|
options?: API['schema']['endpoint']['options']
|
||||||
}
|
}
|
||||||
): NextApiHandler => {
|
): NextApiHandler => {
|
||||||
|
debugger
|
||||||
return getEndpoint(commerce, { ...endpoint, ...context })
|
return getEndpoint(commerce, { ...endpoint, ...context })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1 @@
|
|||||||
COMMERCE_PROVIDER=commercelayer
|
COMMERCE_PROVIDER=commercelayer
|
||||||
|
|
||||||
COMMERCELAYER_CLIENT_ID=
|
|
||||||
COMMERCELAYER_ENDPOINT=
|
|
||||||
COMMERCELAYER_MARKET_SCOPE=
|
|
@ -1,23 +1 @@
|
|||||||
# Commerce Layer Provider
|
# Next.js Local Provider
|
||||||
|
|
||||||
⚠️ This provider is still a work in progress.
|
|
||||||
|
|
||||||
Before getting started, you should do the following:
|
|
||||||
|
|
||||||
- Create a Commerce Layer [developer account](https://commercelayer.io).
|
|
||||||
- Create a new [organization](https://commercelayer.io/docs/data-model/users-and-organizations/) for your business.
|
|
||||||
- Create an application with `sales_channel` kind.
|
|
||||||
|
|
||||||
Next, copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp framework/commercelayer/.env.template .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, add the application credentials from your organization application dashboard in `.env.local`.
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
Our commitment to Open Source can be found [here](https://vercel.com/oss).
|
|
||||||
|
|
||||||
If you find an issue with the provider or want a new feature, feel free to open a PR or [create a new issue](https://github.com/vercel/commerce/issues).
|
|
||||||
|
@ -1,18 +1,35 @@
|
|||||||
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
import { GetAPISchema } from '@commerce/api'
|
||||||
import loginEndpoint from '@commerce/api/endpoints/login'
|
import { CommerceAPIError } from '@commerce/api/utils/errors'
|
||||||
import type { LoginSchema } from '../../../types/login'
|
import isAllowedOperation from '@commerce/api/utils/is-allowed-operation'
|
||||||
import type { CommercelayerAPI } from '../..'
|
import { LoginSchema } from '@commerce/types/login'
|
||||||
import login from './login'
|
|
||||||
|
|
||||||
export type LoginAPI = GetAPISchema<CommercelayerAPI, LoginSchema>
|
const loginEndpoint: GetAPISchema<
|
||||||
|
any,
|
||||||
|
LoginSchema<any>
|
||||||
|
>['endpoint']['handler'] = async (ctx) => {
|
||||||
|
const { req, res, handlers } = ctx
|
||||||
|
debugger
|
||||||
|
if (
|
||||||
|
!isAllowedOperation(req, res, {
|
||||||
|
POST: handlers['login'],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debugger
|
||||||
|
try {
|
||||||
|
const body = req.body ?? {}
|
||||||
|
return await handlers['login']({ ...ctx, body })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
export type LoginEndpoint = LoginAPI['endpoint']
|
const message =
|
||||||
|
error instanceof CommerceAPIError
|
||||||
|
? 'An unexpected error ocurred with the Commerce API'
|
||||||
|
: 'An unexpected error ocurred'
|
||||||
|
|
||||||
export const handlers: LoginEndpoint['handlers'] = { login }
|
res.status(500).json({ data: null, errors: [{ message }] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loginApi = createEndpoint<LoginAPI>({
|
export default loginEndpoint
|
||||||
handler: loginEndpoint,
|
|
||||||
handlers,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default loginApi
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
import { FetcherError } from '@commerce/utils/errors'
|
|
||||||
import { getAccessToken } from '../../index'
|
|
||||||
import type { LoginEndpoint } from '.'
|
|
||||||
|
|
||||||
const login: LoginEndpoint['handlers']['login'] = async ({
|
|
||||||
res,
|
|
||||||
body: { email, password },
|
|
||||||
config,
|
|
||||||
commerce,
|
|
||||||
}) => {
|
|
||||||
if (!(email && password)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
data: null,
|
|
||||||
errors: [{ message: 'Invalid request' }],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = { email, password };
|
|
||||||
const getToken = await getAccessToken(user);
|
|
||||||
await config.apiFetch(`/api/customers/${getToken.customerId}`, {
|
|
||||||
method: 'GET'
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof FetcherError &&
|
|
||||||
/invalid credentials/i.test(error.message)) {
|
|
||||||
return res.status(401).json({
|
|
||||||
data: null,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message:
|
|
||||||
'Cannot find an account that matches the provided credentials',
|
|
||||||
code: 'INVALID_CREDENTIALS',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
await commerce.login({ variables: { email, password }, config, res })
|
|
||||||
|
|
||||||
res.status(200).json({ data: null })
|
|
||||||
}
|
|
||||||
|
|
||||||
export default login
|
|
@ -1,18 +1 @@
|
|||||||
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
export default function noopApi(...args: any[]): void {}
|
||||||
import signupEndpoint from '@commerce/api/endpoints/signup'
|
|
||||||
import type { SignupSchema } from '../../../types/signup'
|
|
||||||
import type { CommercelayerAPI } from '../..'
|
|
||||||
import signup from './signup'
|
|
||||||
|
|
||||||
export type SignupAPI = GetAPISchema<CommercelayerAPI, SignupSchema>
|
|
||||||
|
|
||||||
export type SignupEndpoint = SignupAPI['endpoint']
|
|
||||||
|
|
||||||
export const handlers: SignupEndpoint['handlers'] = { signup }
|
|
||||||
|
|
||||||
const singupApi = createEndpoint<SignupAPI>({
|
|
||||||
handler: signupEndpoint,
|
|
||||||
handlers,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default singupApi
|
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
import { CommercelayerApiError } from '../../utils/errors'
|
|
||||||
import type { SignupEndpoint } from '.'
|
|
||||||
|
|
||||||
const signup: SignupEndpoint['handlers']['signup'] = async ({
|
|
||||||
res,
|
|
||||||
body: { email, password },
|
|
||||||
config,
|
|
||||||
commerce,
|
|
||||||
}) => {
|
|
||||||
if (!(email && password)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
data: null,
|
|
||||||
errors: [{ message: 'Invalid request' }],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await config.apiFetch('/api/customers', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
data: {
|
|
||||||
type: 'customers',
|
|
||||||
attributes: {
|
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof CommercelayerApiError && error.status === 422) {
|
|
||||||
const inputEmail = error.data?.errors.meta.value
|
|
||||||
|
|
||||||
if ('code' in error.data?.errors) {
|
|
||||||
return res.status(400).json({
|
|
||||||
data: null,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: `A user already exists with ${inputEmail}`,
|
|
||||||
code: 'USER_EXISTS',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
await commerce.login({ variables: { email, password }, res, config })
|
|
||||||
|
|
||||||
res.status(200).json({ data: null })
|
|
||||||
}
|
|
||||||
|
|
||||||
export default signup
|
|
@ -1,7 +1,6 @@
|
|||||||
import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
|
import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
|
||||||
import type { RequestInit, Response, Fetch } from '@vercel/fetch'
|
|
||||||
import { getCommerceApi as commerceApi } from '@commerce/api'
|
import { getCommerceApi as commerceApi } from '@commerce/api'
|
||||||
import createFetcher from './utils/fetch-api'
|
import createFetcher from './utils/fetch-local'
|
||||||
|
|
||||||
import getAllPages from './operations/get-all-pages'
|
import getAllPages from './operations/get-all-pages'
|
||||||
import getPage from './operations/get-page'
|
import getPage from './operations/get-page'
|
||||||
@ -11,63 +10,14 @@ import getAllProductPaths from './operations/get-all-product-paths'
|
|||||||
import getAllProducts from './operations/get-all-products'
|
import getAllProducts from './operations/get-all-products'
|
||||||
import getProduct from './operations/get-product'
|
import getProduct from './operations/get-product'
|
||||||
|
|
||||||
import { getToken } from './utils/get-token'
|
export interface LocalConfig extends CommerceAPIConfig {}
|
||||||
|
const config: LocalConfig = {
|
||||||
export interface CommercelayerConfig extends Omit<CommerceAPIConfig, 'fetch'> {
|
commerceUrl: '',
|
||||||
apiClientId: string
|
apiToken: '',
|
||||||
apiToken: string
|
|
||||||
apiFetch(
|
|
||||||
query: string,
|
|
||||||
endpoint: string,
|
|
||||||
fetchOptions?: RequestInit,
|
|
||||||
user?: UserCredentials
|
|
||||||
): Promise<{ data: any; res: Response }>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UserCredentials = {
|
|
||||||
email: string
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const CLIENT_ID = process.env.COMMERCELAYER_CLIENT_ID
|
|
||||||
const ENDPOINT = process.env.COMMERCELAYER_ENDPOINT
|
|
||||||
const MARKET_SCOPE = process.env.COMMERCELAYER_MARKET_SCOPE
|
|
||||||
|
|
||||||
if (!CLIENT_ID) {
|
|
||||||
throw new Error(
|
|
||||||
`The environment variable COMMERCELAYER_CLIENT_ID is missing and it's required.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ENDPOINT) {
|
|
||||||
throw new Error(
|
|
||||||
`The environment variable COMMERCELAYER_ENDPOINT is missing and it's required.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!MARKET_SCOPE) {
|
|
||||||
throw new Error(
|
|
||||||
`The environment variable COMMERCELAYER_MARKET_SCOPE is missing and it's required.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAccessToken(user?: UserCredentials) {
|
|
||||||
return await getToken({
|
|
||||||
clientId: CLIENT_ID,
|
|
||||||
endpoint: ENDPOINT,
|
|
||||||
scope: MARKET_SCOPE,
|
|
||||||
user,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const config: CommercelayerConfig = {
|
|
||||||
commerceUrl: ENDPOINT,
|
|
||||||
apiClientId: CLIENT_ID,
|
|
||||||
cartCookie: '',
|
cartCookie: '',
|
||||||
customerCookie: '',
|
customerCookie: '',
|
||||||
cartCookieMaxAge: 2592000,
|
cartCookieMaxAge: 2592000,
|
||||||
apiToken: '',
|
fetch: createFetcher(() => getCommerceApi().getConfig()),
|
||||||
apiFetch: createFetcher(() => getCommerceApi().getConfig()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const operations = {
|
const operations = {
|
||||||
@ -83,12 +33,10 @@ const operations = {
|
|||||||
export const provider = { config, operations }
|
export const provider = { config, operations }
|
||||||
|
|
||||||
export type Provider = typeof provider
|
export type Provider = typeof provider
|
||||||
export type CommercelayerAPI<P extends Provider = Provider> = CommerceAPI<
|
export type LocalAPI<P extends Provider = Provider> = CommerceAPI<P | any>
|
||||||
P | any
|
|
||||||
>
|
|
||||||
|
|
||||||
export function getCommerceApi<P extends Provider>(
|
export function getCommerceApi<P extends Provider>(
|
||||||
customProvider: P = provider as any
|
customProvider: P = provider as any
|
||||||
): CommercelayerAPI<P> {
|
): LocalAPI<P> {
|
||||||
return commerceApi(customProvider as any)
|
return commerceApi(customProvider as any)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export type Page = { url: string }
|
export type Page = { url: string }
|
||||||
export type GetAllPagesResult = { pages: Page[] }
|
export type GetAllPagesResult = { pages: Page[] }
|
||||||
import type { CommercelayerConfig } from '../index'
|
import type { LocalConfig } from '../index'
|
||||||
|
|
||||||
export default function getAllPagesOperation() {
|
export default function getAllPagesOperation() {
|
||||||
function getAllPages({
|
function getAllPages({
|
||||||
@ -8,7 +8,7 @@ export default function getAllPagesOperation() {
|
|||||||
preview,
|
preview,
|
||||||
}: {
|
}: {
|
||||||
url?: string
|
url?: string
|
||||||
config?: Partial<CommercelayerConfig>
|
config?: Partial<LocalConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
}): Promise<GetAllPagesResult> {
|
}): Promise<GetAllPagesResult> {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Product } from '@commerce/types/product'
|
import { Product } from '@commerce/types/product'
|
||||||
import { GetAllProductsOperation } from '@commerce/types/product'
|
import { GetAllProductsOperation } from '@commerce/types/product'
|
||||||
import type { OperationContext } from '@commerce/api/operations'
|
import type { OperationContext } from '@commerce/api/operations'
|
||||||
import type { CommercelayerConfig, Provider } from '../index'
|
import type { LocalConfig, Provider } from '../index'
|
||||||
import data from '../../data.json'
|
import data from '../../data.json'
|
||||||
|
|
||||||
export default function getAllProductsOperation({
|
export default function getAllProductsOperation({
|
||||||
@ -14,7 +14,7 @@ export default function getAllProductsOperation({
|
|||||||
}: {
|
}: {
|
||||||
query?: string
|
query?: string
|
||||||
variables?: T['variables']
|
variables?: T['variables']
|
||||||
config?: Partial<CommercelayerConfig>
|
config?: Partial<LocalConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
} = {}): Promise<{ products: Product[] | any[] }> {
|
} = {}): Promise<{ products: Product[] | any[] }> {
|
||||||
return {
|
return {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { CommercelayerConfig } from '../index'
|
import type { LocalConfig } from '../index'
|
||||||
import { Product } from '@commerce/types/product'
|
import { Product } from '@commerce/types/product'
|
||||||
import { GetProductOperation } from '@commerce/types/product'
|
import { GetProductOperation } from '@commerce/types/product'
|
||||||
import data from '../../data.json'
|
import data from '../../data.json'
|
||||||
@ -14,7 +14,7 @@ export default function getProductOperation({
|
|||||||
}: {
|
}: {
|
||||||
query?: string
|
query?: string
|
||||||
variables?: T['variables']
|
variables?: T['variables']
|
||||||
config?: Partial<CommercelayerConfig>
|
config?: Partial<LocalConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
} = {}): Promise<Product | {} | any> {
|
} = {}): Promise<Product | {} | any> {
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { OperationContext } from '@commerce/api/operations'
|
import { OperationContext } from '@commerce/api/operations'
|
||||||
import { Category } from '@commerce/types/site'
|
import { Category } from '@commerce/types/site'
|
||||||
import { CommercelayerConfig } from '../index'
|
import { LocalConfig } from '../index'
|
||||||
|
|
||||||
export type GetSiteInfoResult<
|
export type GetSiteInfoResult<
|
||||||
T extends { categories: any[]; brands: any[] } = {
|
T extends { categories: any[]; brands: any[] } = {
|
||||||
@ -17,7 +17,7 @@ export default function getSiteInfoOperation({}: OperationContext<any>) {
|
|||||||
}: {
|
}: {
|
||||||
query?: string
|
query?: string
|
||||||
variables?: any
|
variables?: any
|
||||||
config?: Partial<CommercelayerConfig>
|
config?: Partial<LocalConfig>
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
} = {}): Promise<GetSiteInfoResult> {
|
} = {}): Promise<GetSiteInfoResult> {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
15
framework/commercelayer/api/utils/cookies.ts
Normal file
15
framework/commercelayer/api/utils/cookies.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Cookies, { CookieAttributes } from 'js-cookie'
|
||||||
|
|
||||||
|
const setCookie = (
|
||||||
|
name: string,
|
||||||
|
token?: string,
|
||||||
|
options?: CookieAttributes
|
||||||
|
) => {
|
||||||
|
if (!token) {
|
||||||
|
Cookies.remove(name)
|
||||||
|
} else {
|
||||||
|
Cookies.set(name, token, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default setCookie
|
@ -1,11 +0,0 @@
|
|||||||
// Email must start with and contain an alphanumeric character, contain a @ character, and . character
|
|
||||||
export const validateEmail = (email: string) => {
|
|
||||||
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
||||||
return re.test(String(email).toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Passwords must be at least eight characters and must contain at least one uppercase letter, one lowercase letter, one number and one special character
|
|
||||||
export const validatePassword = (password: string) => {
|
|
||||||
const re = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
|
|
||||||
return re.test(String(password).toLowerCase());
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
import type { Response } from '@vercel/fetch'
|
|
||||||
|
|
||||||
export class CommercelayerApiError extends Error {
|
|
||||||
status: number
|
|
||||||
res: Response
|
|
||||||
data: any
|
|
||||||
|
|
||||||
constructor(msg: string, res: Response, data?: any) {
|
|
||||||
super(msg)
|
|
||||||
this.name = 'CommercelayerApiError'
|
|
||||||
this.status = res.status
|
|
||||||
this.res = res
|
|
||||||
this.data = data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CommercelayerNetworkError extends Error {
|
|
||||||
constructor(msg: string) {
|
|
||||||
super(msg)
|
|
||||||
this.name = 'CommercelayerNetworkError'
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
import type { RequestInit } from '@vercel/fetch'
|
|
||||||
import { FetcherError } from '@commerce/utils/errors'
|
|
||||||
import { CommercelayerConfig, getAccessToken, UserCredentials } from '../index'
|
|
||||||
import fetch from './fetch'
|
|
||||||
|
|
||||||
const fetchApi =
|
|
||||||
(getConfig: () => CommercelayerConfig) =>
|
|
||||||
async (
|
|
||||||
query: string,
|
|
||||||
endpoint: string,
|
|
||||||
fetchOptions?: RequestInit,
|
|
||||||
user?: UserCredentials
|
|
||||||
) => {
|
|
||||||
const config = getConfig()
|
|
||||||
const getToken = await getAccessToken(user)
|
|
||||||
const token = getToken?.accessToken
|
|
||||||
const res = await fetch(config.commerceUrl + endpoint, {
|
|
||||||
...fetchOptions,
|
|
||||||
headers: {
|
|
||||||
...fetchOptions?.headers,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify([
|
|
||||||
query,
|
|
||||||
]),
|
|
||||||
})
|
|
||||||
|
|
||||||
const json = await res.json()
|
|
||||||
if (json.errors) {
|
|
||||||
throw new FetcherError({
|
|
||||||
errors: json.errors ?? [{ message: 'Failed to fetch Commerce Layer API' }],
|
|
||||||
status: res.status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return { data: json.data, res }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default fetchApi
|
|
35
framework/commercelayer/api/utils/fetch-local.ts
Normal file
35
framework/commercelayer/api/utils/fetch-local.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { FetcherError } from '@commerce/utils/errors'
|
||||||
|
import type { GraphQLFetcher } from '@commerce/api'
|
||||||
|
import type { LocalConfig } from '../index'
|
||||||
|
import fetch from './fetch'
|
||||||
|
|
||||||
|
const fetchGraphqlApi: (getConfig: () => LocalConfig) => GraphQLFetcher =
|
||||||
|
(getConfig) =>
|
||||||
|
async (query: string, { variables, preview } = {}, fetchOptions) => {
|
||||||
|
debugger
|
||||||
|
const config = getConfig()
|
||||||
|
const res = await fetch(config.commerceUrl, {
|
||||||
|
...fetchOptions,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
...fetchOptions?.headers,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const json = await res.json()
|
||||||
|
if (json.errors) {
|
||||||
|
throw new FetcherError({
|
||||||
|
errors: json.errors ?? [{ message: 'Failed to fetch for API' }],
|
||||||
|
status: res.status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: json.data, res }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fetchGraphqlApi
|
@ -1,40 +0,0 @@
|
|||||||
import Cookies from 'js-cookie'
|
|
||||||
import { getSalesChannelToken } from '@commercelayer/js-auth'
|
|
||||||
|
|
||||||
type GetTokenObj = {
|
|
||||||
clientId?: string
|
|
||||||
endpoint?: string
|
|
||||||
scope?: string
|
|
||||||
user?: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getToken({
|
|
||||||
clientId,
|
|
||||||
endpoint,
|
|
||||||
scope = 'market:all',
|
|
||||||
user,
|
|
||||||
}: GetTokenObj) {
|
|
||||||
const getCookieToken = Cookies.get('clAccessToken')
|
|
||||||
if (!getCookieToken && clientId && endpoint) {
|
|
||||||
const auth = await getSalesChannelToken(
|
|
||||||
{
|
|
||||||
clientId,
|
|
||||||
endpoint,
|
|
||||||
scope,
|
|
||||||
},
|
|
||||||
user
|
|
||||||
)
|
|
||||||
Cookies.set('clAccessToken', auth?.accessToken as string, {
|
|
||||||
// @ts-ignore
|
|
||||||
expires: auth?.expires,
|
|
||||||
})
|
|
||||||
return auth
|
|
||||||
? {
|
|
||||||
accessToken: auth.accessToken,
|
|
||||||
customerId: auth.data.owner_id,
|
|
||||||
...auth.data,
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
return { accessToken: getCookieToken }
|
|
||||||
}
|
|
@ -1,40 +1,40 @@
|
|||||||
import { useCallback } from 'react'
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
import type { MutationHook } from '@commerce/utils/types'
|
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
|
||||||
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
import useLogin, { UseLogin } from '@commerce/auth/use-login'
|
||||||
import type { LoginHook } from '../types/login'
|
import { CommerceError } from '@commerce/utils/errors'
|
||||||
import useCustomer from '../customer/use-customer'
|
import { getCustomerToken } from '@commercelayer/js-auth'
|
||||||
|
import setCookie from '@framework/api/utils/cookies'
|
||||||
|
|
||||||
export default useLogin as UseLogin<typeof handler>
|
export default useLogin as UseLogin<typeof handler>
|
||||||
|
|
||||||
export const handler: MutationHook<LoginHook> = {
|
export const handler: MutationHook<any> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/customers',
|
// query: 'login',
|
||||||
method: 'GET',
|
url: '/customer',
|
||||||
},
|
},
|
||||||
async fetcher({ input: { email, password }, options, fetch }) {
|
async fetcher({ input: { email, password }, options, fetch }) {
|
||||||
if (!(email && password)) {
|
if (!(email && password)) {
|
||||||
throw new CommerceError({
|
throw new CommerceError({
|
||||||
message:
|
message: 'An email and password are required to login',
|
||||||
'An email address and password are required to login',
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const token = await getCustomerToken(
|
||||||
return fetch({
|
{
|
||||||
...options,
|
endpoint: process.env.NEXT_PUBLIC_COMMERCELAYER_ENDPOINT as string,
|
||||||
body: { email, password },
|
clientId: process.env.NEXT_PUBLIC_COMMERCELAYER_CLIENT_ID as string,
|
||||||
})
|
scope: process.env.NEXT_PUBLIC_COMMERCELAYER_MARKET_SCOPE as string,
|
||||||
},
|
},
|
||||||
useHook: ({ fetch }) => () => {
|
{ username: email, password }
|
||||||
const { revalidate } = useCustomer()
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
async function login(input) {
|
|
||||||
const data = await fetch({ input })
|
|
||||||
await revalidate()
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
[fetch, revalidate]
|
|
||||||
)
|
)
|
||||||
|
token &&
|
||||||
|
setCookie('CL_TOKEN', token.accessToken, { expires: token.expires })
|
||||||
|
return token
|
||||||
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() => {
|
||||||
|
return async function login(input) {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
return data
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
@ -1,44 +1,19 @@
|
|||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import type { MutationHook } from '@commerce/utils/types'
|
|
||||||
import { CommerceError } from '@commerce/utils/errors'
|
|
||||||
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
|
||||||
import type { SignupHook } from '../types/signup'
|
|
||||||
import useCustomer from '../customer/use-customer'
|
import useCustomer from '../customer/use-customer'
|
||||||
|
import { MutationHook } from '@commerce/utils/types'
|
||||||
|
import useSignup, { UseSignup } from '@commerce/auth/use-signup'
|
||||||
|
|
||||||
export default useSignup as UseSignup<typeof handler>
|
export default useSignup as UseSignup<typeof handler>
|
||||||
|
|
||||||
export const handler: MutationHook<SignupHook> = {
|
export const handler: MutationHook<any> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
url: '/api/customers',
|
query: '',
|
||||||
method: 'POST',
|
|
||||||
},
|
},
|
||||||
async fetcher({
|
async fetcher() {
|
||||||
input: { email, password },
|
return null
|
||||||
options,
|
|
||||||
fetch,
|
|
||||||
}) {
|
|
||||||
if (!(email && password)) {
|
|
||||||
throw new CommerceError({
|
|
||||||
message:
|
|
||||||
'An email address and password are required to signup',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch({
|
|
||||||
...options,
|
|
||||||
body: { email, password },
|
|
||||||
})
|
|
||||||
},
|
|
||||||
useHook: ({ fetch }) => () => {
|
|
||||||
const { revalidate } = useCustomer()
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
async function signup(input) {
|
|
||||||
const data = await fetch({ input })
|
|
||||||
await revalidate()
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
[fetch, revalidate]
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
useHook:
|
||||||
|
({ fetch }) =>
|
||||||
|
() =>
|
||||||
|
() => {},
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ import { Fetcher } from '@commerce/utils/types'
|
|||||||
|
|
||||||
export const fetcher: Fetcher = async () => {
|
export const fetcher: Fetcher = async () => {
|
||||||
console.log('FETCHER')
|
console.log('FETCHER')
|
||||||
|
debugger
|
||||||
const res = await fetch('./data.json')
|
const res = await fetch('./data.json')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const { data } = await res.json()
|
const { data } = await res.json()
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { commerceLayerProvider } from './provider'
|
import { localProvider } from './provider'
|
||||||
import {
|
import {
|
||||||
CommerceConfig,
|
CommerceConfig,
|
||||||
CommerceProvider as CoreCommerceProvider,
|
CommerceProvider as CoreCommerceProvider,
|
||||||
useCommerce as useCoreCommerce,
|
useCommerce as useCoreCommerce,
|
||||||
} from '@commerce'
|
} from '@commerce'
|
||||||
|
|
||||||
export const commerceLayerConfig: CommerceConfig = {
|
export const localConfig: CommerceConfig = {
|
||||||
locale: 'en-us',
|
locale: 'en-us',
|
||||||
cartCookie: 'session',
|
cartCookie: 'session',
|
||||||
}
|
}
|
||||||
@ -21,8 +21,8 @@ export function CommerceProvider({
|
|||||||
} & Partial<CommerceConfig>) {
|
} & Partial<CommerceConfig>) {
|
||||||
return (
|
return (
|
||||||
<CoreCommerceProvider
|
<CoreCommerceProvider
|
||||||
provider={commerceLayerProvider}
|
provider={localProvider}
|
||||||
config={{ ...commerceLayerConfig, ...config }}
|
config={{ ...localConfig, ...config }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</CoreCommerceProvider>
|
</CoreCommerceProvider>
|
||||||
|
@ -9,8 +9,8 @@ import { handler as useLogin } from './auth/use-login'
|
|||||||
import { handler as useLogout } from './auth/use-logout'
|
import { handler as useLogout } from './auth/use-logout'
|
||||||
import { handler as useSignup } from './auth/use-signup'
|
import { handler as useSignup } from './auth/use-signup'
|
||||||
|
|
||||||
export type Provider = typeof commerceLayerProvider
|
export type Provider = typeof localProvider
|
||||||
export const commerceLayerProvider = {
|
export const localProvider = {
|
||||||
locale: 'en-us',
|
locale: 'en-us',
|
||||||
cartCookie: 'session',
|
cartCookie: 'session',
|
||||||
fetcher: fetcher,
|
fetcher: fetcher,
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/cart'
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/checkout'
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/common'
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/customer'
|
|
@ -1,25 +0,0 @@
|
|||||||
import * as Cart from './cart'
|
|
||||||
import * as Checkout from './checkout'
|
|
||||||
import * as Common from './common'
|
|
||||||
import * as Customer from './customer'
|
|
||||||
import * as Login from './login'
|
|
||||||
import * as Logout from './logout'
|
|
||||||
import * as Page from './page'
|
|
||||||
import * as Product from './product'
|
|
||||||
import * as Signup from './signup'
|
|
||||||
import * as Site from './site'
|
|
||||||
import * as Wishlist from './wishlist'
|
|
||||||
|
|
||||||
export type {
|
|
||||||
Cart,
|
|
||||||
Checkout,
|
|
||||||
Common,
|
|
||||||
Customer,
|
|
||||||
Login,
|
|
||||||
Logout,
|
|
||||||
Page,
|
|
||||||
Product,
|
|
||||||
Signup,
|
|
||||||
Site,
|
|
||||||
Wishlist,
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
import * as Core from '@commerce/types/login'
|
|
||||||
import { LoginBody, LoginTypes } from '@commerce/types/login'
|
|
||||||
|
|
||||||
export * from '@commerce/types/login'
|
|
||||||
|
|
||||||
export type LoginHook<T extends LoginTypes = LoginTypes> = {
|
|
||||||
data: null
|
|
||||||
actionInput: LoginBody
|
|
||||||
fetcherInput: LoginBody
|
|
||||||
body: T['body']
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/logout'
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/page'
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/product'
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/signup'
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/site'
|
|
@ -1 +0,0 @@
|
|||||||
export * from '@commerce/types/wishlist'
|
|
@ -21,7 +21,7 @@
|
|||||||
"node": ">=14.x"
|
"node": ">=14.x"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@commercelayer/js-auth": "^2.0.6",
|
"@commercelayer/js-auth": "^2.0.7",
|
||||||
"@commercelayer/js-sdk": "^4.1.3",
|
"@commercelayer/js-sdk": "^4.1.3",
|
||||||
"@chec/commerce.js": "^2.8.0",
|
"@chec/commerce.js": "^2.8.0",
|
||||||
"@react-spring/web": "^9.4.1",
|
"@react-spring/web": "^9.4.1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user