diff --git a/framework/bigcommerce/api/endpoints/login/login.ts b/framework/bigcommerce/api/endpoints/login/login.ts index f55c3b54f..a12c1f497 100644 --- a/framework/bigcommerce/api/endpoints/login/login.ts +++ b/framework/bigcommerce/api/endpoints/login/login.ts @@ -1,4 +1,3 @@ -import { FetcherError } from '@commerce/utils/errors' import type { LoginEndpoint } from '.' const invalidCredentials = /invalid credentials/i @@ -23,11 +22,6 @@ const login: LoginEndpoint['handlers']['login'] = async ({ try { await commerce.login({ variables: { email, password }, config, res }) } catch (error) { - // Check if the email and password didn't match an existing account - if ( - error instanceof FetcherError && - invalidCredentials.test(error.message) - ) { return res.status(401).json({ data: null, errors: [ @@ -39,8 +33,6 @@ const login: LoginEndpoint['handlers']['login'] = async ({ ], }) } - - throw error } res.status(200).json({ data: null }) diff --git a/framework/ordercloud/api/endpoints/customer/get-logged-in-customer.ts b/framework/ordercloud/api/endpoints/customer/get-logged-in-customer.ts new file mode 100644 index 000000000..ecf57489c --- /dev/null +++ b/framework/ordercloud/api/endpoints/customer/get-logged-in-customer.ts @@ -0,0 +1,35 @@ +import { CustomerEndpoint } from "." + +const getLoggedInCustomer: CustomerEndpoint['handlers']['getLoggedInCustomer'] = async ({ + req, + res, + config, + }) => { + const token = req.cookies[config.customerCookie] + + // if (token) { + // const { data } = await config.fetch( + // getLoggedInCustomerQuery, + // undefined, + // { + // headers: { + // cookie: `${config.customerCookie}=${token}`, + // }, + // } + // ) + // const { customer } = data + + // if (!customer) { + // return res.status(400).json({ + // data: null, + // errors: [{ message: 'Customer not found', code: 'not_found' }], + // }) + // } + + // return res.status(200).json({ data: { customer } }) + // } + + res.status(200).json({ data: null }) + } + + export default getLoggedInCustomer \ No newline at end of file diff --git a/framework/ordercloud/api/endpoints/customer/index.ts b/framework/ordercloud/api/endpoints/customer/index.ts index 491bf0ac9..c86e4409d 100644 --- a/framework/ordercloud/api/endpoints/customer/index.ts +++ b/framework/ordercloud/api/endpoints/customer/index.ts @@ -1 +1,19 @@ -export default function noopApi(...args: any[]): void {} +import { GetAPISchema, createEndpoint } from '@commerce/api' +import customerEndpoint from '@commerce/api/endpoints/customer' +import { CustomerSchema } from '@commerce/types/customer' +import { OrdercloudAPI } from '@framework/api' +import getLoggedInCustomer from './get-logged-in-customer'; + +export type CustomerAPI = GetAPISchema + +export type CustomerEndpoint = CustomerAPI['endpoint'] + +export const handlers: CustomerEndpoint['handlers'] = { getLoggedInCustomer } + +const customerApi = createEndpoint({ + handler: customerEndpoint, + handlers, +}) + +export default customerApi + diff --git a/framework/ordercloud/api/endpoints/login/index.ts b/framework/ordercloud/api/endpoints/login/index.ts index 491bf0ac9..c6327205b 100644 --- a/framework/ordercloud/api/endpoints/login/index.ts +++ b/framework/ordercloud/api/endpoints/login/index.ts @@ -1 +1,18 @@ -export default function noopApi(...args: any[]): void {} +import { createEndpoint, GetAPISchema } from "@commerce/api" +import loginEndpoint from "@commerce/api/endpoints/login" +import { LoginSchema } from "@commerce/types/login" +import { OrdercloudAPI } from "@framework/api" +import login from "./login" + +export type LoginAPI = GetAPISchema + +export type LoginEndpoint = LoginAPI['endpoint'] + +export const handlers: LoginEndpoint['handlers'] = { login } + +const loginApi = createEndpoint({ + handler: loginEndpoint, + handlers, +}) + +export default loginApi diff --git a/framework/ordercloud/api/endpoints/login/login.ts b/framework/ordercloud/api/endpoints/login/login.ts new file mode 100644 index 000000000..dfe652045 --- /dev/null +++ b/framework/ordercloud/api/endpoints/login/login.ts @@ -0,0 +1,57 @@ +import { FetcherError } from '@commerce/utils/errors' +import { passwordLogin } from '@framework/api/utils/fetch-rest' +import { provider } from 'framework/local/api' +import type { LoginEndpoint } from '.' + +const invalidCredentials = /invalid credentials/i + +const login: LoginEndpoint['handlers']['login'] = async ({ + req, + res, + body: { email, password }, + config: { commerceUrl }, +}) => { + console.log(email, password) + // TODO: Add proper validations with something like Ajv + if (!(email && password)) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + + // Get token from cookies + let response: any; + + try { + response = await passwordLogin(email, password, commerceUrl); + } catch (error) { + // Check if the email and password didn't match an existing account + if ( + error instanceof FetcherError && + invalidCredentials.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 + } + + console.log(response) + + // set buyer token + global.token = response.access_token; + + res.status(200).json({ data: null }) +} + +export default login diff --git a/framework/ordercloud/api/endpoints/logout/index.ts b/framework/ordercloud/api/endpoints/logout/index.ts index 491bf0ac9..50a570fa0 100644 --- a/framework/ordercloud/api/endpoints/logout/index.ts +++ b/framework/ordercloud/api/endpoints/logout/index.ts @@ -1 +1,18 @@ -export default function noopApi(...args: any[]): void {} +import { GetAPISchema, createEndpoint } from '@commerce/api' +import logoutEndpoint from '@commerce/api/endpoints/logout' +import { LogoutSchema } from '@commerce/types/logout' +import { OrdercloudAPI } from '@framework/api' +import logout from './logout' + +export type LogoutAPI = GetAPISchema + +export type LogoutEndpoint = LogoutAPI['endpoint'] + +export const handlers: LogoutEndpoint['handlers'] = { logout } + +const logoutApi = createEndpoint({ + handler: logoutEndpoint, + handlers, +}) + +export default logoutApi diff --git a/framework/ordercloud/api/endpoints/logout/logout.ts b/framework/ordercloud/api/endpoints/logout/logout.ts new file mode 100644 index 000000000..6ccde3217 --- /dev/null +++ b/framework/ordercloud/api/endpoints/logout/logout.ts @@ -0,0 +1,20 @@ +import { LogoutEndpoint } from "." + +const logout: LogoutEndpoint['handlers']['logout'] = async ({ + res, + body: { redirectTo }, + config, + }) => { + + // Remove the buyer token + global.token = null; + + // Only allow redirects to a relative URL + if (redirectTo?.startsWith('/')) { + res.redirect(redirectTo) + } else { + res.status(200).json({ data: null }) + } + } + + export default logout \ No newline at end of file diff --git a/framework/ordercloud/api/endpoints/signup/index.ts b/framework/ordercloud/api/endpoints/signup/index.ts deleted file mode 100644 index 491bf0ac9..000000000 --- a/framework/ordercloud/api/endpoints/signup/index.ts +++ /dev/null @@ -1 +0,0 @@ -export default function noopApi(...args: any[]): void {} diff --git a/framework/ordercloud/api/endpoints/signup/signup.ts b/framework/ordercloud/api/endpoints/signup/signup.ts new file mode 100644 index 000000000..45415d824 --- /dev/null +++ b/framework/ordercloud/api/endpoints/signup/signup.ts @@ -0,0 +1,79 @@ +import { createEndpoint, GetAPISchema } from '@commerce/api'; +import signupEndpoint from '@commerce/api/endpoints/signup'; +import { SignupSchema } from '@commerce/types/signup'; +import { OrdercloudAPI } from '@framework/api'; +import { passwordLogin } from '@framework/api/utils/fetch-rest'; + +const signup: SignupEndpoint['handlers']['signup'] = async ({ + req, + res, + body: { firstName, lastName, email, password }, + config: { restBuyerFetch, commerceUrl, tokenCookie }, +}) => { + // TODO: Add proper validations with something like Ajv + if (!(firstName && lastName && email && password)) { + return res.status(400).json({ + data: null, + errors: [{ message: 'Invalid request' }], + }) + } + // TODO: validate the password and email + // Passwords must be at least 7 characters and contain both alphabetic + // and numeric characters. + + // Get token from cookies + const token = req.cookies[tokenCookie]; + + const newUser = { + Active: true, + Username: email, + Password: password, + FirstName: firstName, + LastName: lastName, + Email: email + } + + + + // create user record + try { + await restBuyerFetch( + 'PUT', + `/me/register?anonUserToken=${token}`, + newUser, + { token } + ); + } catch (error) { + var message = (error as any).errors[0].message + //const ordercloud_error = JSON.parse(); + console.log(message) + return res.status(400).json({ + data: null, + errors: [ + { + message: message, + code: message + }, + ], + }) + } + + // Login the customer right after creating it + await passwordLogin(email, password, commerceUrl); + + res.status(200).json({ data: null }) +} + +export type SignupAPI = GetAPISchema + +export type SignupEndpoint = SignupAPI['endpoint'] + +export const handlers: SignupEndpoint['handlers'] = { signup } + +const signupApi = createEndpoint({ + handler: signupEndpoint, + handlers, +}) + +export default signupApi + diff --git a/framework/ordercloud/api/utils/fetch-rest.ts b/framework/ordercloud/api/utils/fetch-rest.ts index fd686b958..b5c2cf8c8 100644 --- a/framework/ordercloud/api/utils/fetch-rest.ts +++ b/framework/ordercloud/api/utils/fetch-rest.ts @@ -6,6 +6,7 @@ import { OrdercloudConfig } from '../index' // Get an instance to vercel fetch const fetch = vercelFetch() + // Get token util async function getToken({ baseUrl, @@ -92,6 +93,17 @@ export async function fetchData(opts: { } } +export async function passwordLogin(email: string, password: string, commerceUrl: string) { + return await fetch(`${commerceUrl}/oauth/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + }, + body: `username=${email}&password=${password}&grant_type=password`, + }) +} + export const createMiddlewareFetcher: ( getConfig: () => OrdercloudConfig ) => ( @@ -129,7 +141,7 @@ export const createMiddlewareFetcher: ( } export const createBuyerFetcher: ( - getConfig: () => OrdercloudConfig + getConfig: () => OrdercloudConfig, ) => ( method: string, path: string, diff --git a/framework/ordercloud/auth/use-login.tsx b/framework/ordercloud/auth/use-login.tsx index 28351dc7f..9f15ccea9 100644 --- a/framework/ordercloud/auth/use-login.tsx +++ b/framework/ordercloud/auth/use-login.tsx @@ -1,16 +1,40 @@ import { MutationHook } from '@commerce/utils/types' import useLogin, { UseLogin } from '@commerce/auth/use-login' +import { LoginHook } from '@commerce/types/login' +import { CommerceError } from '@commerce/utils/errors' +import useCustomer from '@commerce/customer/use-customer' +import { useCallback } from 'react' export default useLogin as UseLogin -export const handler: MutationHook = { +export const handler: MutationHook = { fetchOptions: { - query: '', + url: '/api/login', + method: 'POST', }, - async fetcher() { - return null + async fetcher({ input: { email, password }, options, fetch }) { + if (!(email && password)) { + throw new CommerceError({ + message: + 'An email and password are required to login', + }) + } + + return fetch({ + ...options, + body: { email, password }, + }) }, - useHook: () => () => { - return async function () {} + useHook: ({ fetch }) => () => { + const { revalidate } = useCustomer() + + return useCallback( + async function login(input) { + const data = await fetch({ input }) + await revalidate() + return data + }, + [fetch, revalidate] + ) }, } diff --git a/framework/ordercloud/auth/use-logout.tsx b/framework/ordercloud/auth/use-logout.tsx index 9b3fc3e44..0a462d68a 100644 --- a/framework/ordercloud/auth/use-logout.tsx +++ b/framework/ordercloud/auth/use-logout.tsx @@ -1,17 +1,26 @@ import { MutationHook } from '@commerce/utils/types' import useLogout, { UseLogout } from '@commerce/auth/use-logout' +import { LogoutHook } from '@commerce/types/logout' +import useCustomer from '@commerce/customer/use-customer' +import { useCallback } from 'react' export default useLogout as UseLogout -export const handler: MutationHook = { +export const handler: MutationHook = { fetchOptions: { - query: '', + url: '/api/logout', + method: 'GET', }, - async fetcher() { - return null + useHook: ({ fetch }) => () => { + const { mutate } = useCustomer() + + return useCallback( + async function logout() { + const data = await fetch() + await mutate(null, false) + return data + }, + [fetch, mutate] + ) }, - useHook: - ({ fetch }) => - () => - async () => {}, } diff --git a/framework/ordercloud/auth/use-signup.tsx b/framework/ordercloud/auth/use-signup.tsx index e9ad13458..dff5ffc3d 100644 --- a/framework/ordercloud/auth/use-signup.tsx +++ b/framework/ordercloud/auth/use-signup.tsx @@ -2,18 +2,43 @@ import { useCallback } from 'react' import useCustomer from '../customer/use-customer' import { MutationHook } from '@commerce/utils/types' import useSignup, { UseSignup } from '@commerce/auth/use-signup' +import { CommerceError } from '@commerce/utils/errors' +import { SignupHook } from '@commerce/types/signup' export default useSignup as UseSignup -export const handler: MutationHook = { +export const handler: MutationHook = { fetchOptions: { - query: '', + url: '/api/signup', + method: 'POST', }, - async fetcher() { - return null + async fetcher({ + input: { firstName, lastName, email, password }, + options, + fetch, + }) { + if (!(firstName && lastName && email && password)) { + throw new CommerceError({ + message: + 'A first name, last name, email and password are required to signup', + }) + } + + return fetch({ + ...options, + body: { firstName, lastName, 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 }) => - () => - () => {}, } diff --git a/framework/ordercloud/commerce.config.json b/framework/ordercloud/commerce.config.json index e329bd4c1..09e25c02d 100644 --- a/framework/ordercloud/commerce.config.json +++ b/framework/ordercloud/commerce.config.json @@ -4,7 +4,7 @@ "wishlist": false, "cart": true, "search": true, - "customerAuth": false, + "customerAuth": true, "customCheckout": true } } diff --git a/framework/ordercloud/customer/use-customer.tsx b/framework/ordercloud/customer/use-customer.tsx index 41757cd0d..a67754c62 100644 --- a/framework/ordercloud/customer/use-customer.tsx +++ b/framework/ordercloud/customer/use-customer.tsx @@ -1,15 +1,23 @@ import { SWRHook } from '@commerce/utils/types' import useCustomer, { UseCustomer } from '@commerce/customer/use-customer' +import { CustomerHook } from '@commerce/types/customer' export default useCustomer as UseCustomer -export const handler: SWRHook = { +export const handler: SWRHook = { fetchOptions: { - query: '', + url: '/api/customer', + method: 'GET', }, - async fetcher({ input, options, fetch }) {}, - useHook: () => () => { - return async function addItem() { - return {} - } + async fetcher({ options, fetch }) { + const data = await fetch(options) + return data?.customer ?? null + }, + useHook: ({ useData }) => (input) => { + return useData({ + swrOptions: { + revalidateOnFocus: false, + ...input?.swrOptions, + }, + }) }, } diff --git a/package.json b/package.json index f42b2619b..a579cab07 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nextjs-commerce", "version": "1.0.0", "scripts": { - "dev": "NODE_OPTIONS='--inspect' next dev", + "dev": "next dev", "build": "next build", "start": "next start", "analyze": "BUNDLE_ANALYZE=both yarn build", diff --git a/pages/api/logout.ts b/pages/api/logout.ts index 0cf0fc4d2..e3b693f89 100644 --- a/pages/api/logout.ts +++ b/pages/api/logout.ts @@ -1,4 +1,4 @@ -import logoutApi from '@framework/api/endpoints/logout' +import logoutApi from '@framework/api/endpoints/logout/logout' import commerce from '@lib/api/commerce' export default logoutApi(commerce) diff --git a/pages/api/signup.ts b/pages/api/signup.ts index e19d67ee8..63e2b6d84 100644 --- a/pages/api/signup.ts +++ b/pages/api/signup.ts @@ -1,4 +1,4 @@ -import singupApi from '@framework/api/endpoints/signup' +import singupApi from '@framework/api/endpoints/signup/signup' import commerce from '@lib/api/commerce' export default singupApi(commerce) diff --git a/tsconfig.json b/tsconfig.json index 340929669..340dad193 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,8 +23,8 @@ "@components/*": ["components/*"], "@commerce": ["framework/commerce"], "@commerce/*": ["framework/commerce/*"], - "@framework": ["framework/local"], - "@framework/*": ["framework/local/*"] + "@framework": ["framework/ordercloud"], + "@framework/*": ["framework/ordercloud/*"] } }, "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],