diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts
index e30517171..425759df5 100644
--- a/framework/bigcommerce/api/index.ts
+++ b/framework/bigcommerce/api/index.ts
@@ -11,6 +11,7 @@ import fetchStoreApi from './utils/fetch-store-api'
import type { CartAPI } from './cart'
import type { CustomerAPI } from './customer'
+import type { LoginAPI } from './login'
import login from './operations/login'
export interface BigcommerceConfig extends CommerceAPIConfig {
@@ -112,7 +113,7 @@ export const provider = {
export type Provider = typeof provider
-export type APIs = CartAPI | CustomerAPI
+export type APIs = CartAPI | CustomerAPI | LoginAPI
export type BigcommerceAPI
= CommerceAPI
diff --git a/framework/bigcommerce/api/login/index.ts b/framework/bigcommerce/api/login/index.ts
new file mode 100644
index 000000000..8b9543c29
--- /dev/null
+++ b/framework/bigcommerce/api/login/index.ts
@@ -0,0 +1,10 @@
+import type { GetAPISchema } from '@commerce/api'
+import type { LoginSchema } from '../../types/login'
+import type { BigcommerceAPI } from '..'
+import login from './login'
+
+export type LoginAPI = GetAPISchema
+
+export type LoginEndpoint = LoginAPI['endpoint']
+
+export const operations = { login }
diff --git a/framework/bigcommerce/api/login/login.ts b/framework/bigcommerce/api/login/login.ts
new file mode 100644
index 000000000..670734963
--- /dev/null
+++ b/framework/bigcommerce/api/login/login.ts
@@ -0,0 +1,49 @@
+import { FetcherError } from '@commerce/utils/errors'
+import type { LoginEndpoint } from '.'
+
+const invalidCredentials = /invalid credentials/i
+
+const login: LoginEndpoint['operations']['login'] = async ({
+ res,
+ body: { email, password },
+ config,
+ commerce,
+}) => {
+ // TODO: Add proper validations with something like Ajv
+ if (!(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.
+
+ 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: [
+ {
+ message:
+ 'Cannot find an account that matches the provided credentials',
+ code: 'invalid_credentials',
+ },
+ ],
+ })
+ }
+
+ throw error
+ }
+
+ res.status(200).json({ data: null })
+}
+
+export default login
diff --git a/framework/bigcommerce/types/login.ts b/framework/bigcommerce/types/login.ts
new file mode 100644
index 000000000..3313c60d8
--- /dev/null
+++ b/framework/bigcommerce/types/login.ts
@@ -0,0 +1 @@
+export * from '@commerce/types/login'
diff --git a/framework/commerce/api/endpoints/login.ts b/framework/commerce/api/endpoints/login.ts
new file mode 100644
index 000000000..b5b70a1da
--- /dev/null
+++ b/framework/commerce/api/endpoints/login.ts
@@ -0,0 +1,35 @@
+import type { LoginSchema } from '../../types/login'
+import { CommerceAPIError } from '../utils/errors'
+import isAllowedOperation from '../utils/is-allowed-operation'
+import type { GetAPISchema } from '..'
+
+const loginEndpoint: GetAPISchema<
+ any,
+ LoginSchema
+>['endpoint']['handler'] = async (ctx) => {
+ const { req, res, operations } = ctx
+
+ if (
+ !isAllowedOperation(req, res, {
+ POST: operations['login'],
+ })
+ ) {
+ return
+ }
+
+ try {
+ const body = req.body ?? {}
+ return await operations['login']({ ...ctx, body })
+ } catch (error) {
+ console.error(error)
+
+ const message =
+ error instanceof CommerceAPIError
+ ? 'An unexpected error ocurred with the Commerce API'
+ : 'An unexpected error ocurred'
+
+ res.status(500).json({ data: null, errors: [{ message }] })
+ }
+}
+
+export default loginEndpoint
diff --git a/framework/commerce/api/index.ts b/framework/commerce/api/index.ts
index f076657c0..86ac17e43 100644
--- a/framework/commerce/api/index.ts
+++ b/framework/commerce/api/index.ts
@@ -3,6 +3,7 @@ import type { RequestInit, Response } from '@vercel/fetch'
import type { APIEndpoint, APIHandler } from './utils/types'
import type { CartSchema } from '../types/cart'
import type { CustomerSchema } from '../types/customer'
+import type { LoginSchema } from '../types/login'
import {
defaultOperations,
OPERATIONS,
@@ -10,7 +11,7 @@ import {
APIOperations,
} from './operations'
-export type APISchemas = CartSchema | CustomerSchema
+export type APISchemas = CartSchema | CustomerSchema | LoginSchema
export type GetAPISchema<
C extends CommerceAPI,
diff --git a/framework/commerce/types/login.ts b/framework/commerce/types/login.ts
new file mode 100644
index 000000000..5974ed2db
--- /dev/null
+++ b/framework/commerce/types/login.ts
@@ -0,0 +1,20 @@
+export type LoginBody = {
+ email: string
+ password: string
+}
+
+export type LoginTypes = {
+ body: LoginBody
+}
+
+export type LoginSchema = {
+ endpoint: {
+ options: {}
+ operations: {
+ login: {
+ data: null
+ body: T['body']
+ }
+ }
+ }
+}
diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts
index 6ef673d77..93fd8153c 100644
--- a/framework/commerce/utils/types.ts
+++ b/framework/commerce/utils/types.ts
@@ -71,7 +71,7 @@ export type HookSchemaBase = {
data: any
// Input expected by the hook
input: {}
- // Input expected before doing a fetch operation
+ // Input expected before doing a fetch operation (aka fetch handler)
fetchInput?: {}
// Data expected by the fetch operation
body?: {}
diff --git a/pages/api/login.ts b/pages/api/login.ts
new file mode 100644
index 000000000..5f87a24e8
--- /dev/null
+++ b/pages/api/login.ts
@@ -0,0 +1,8 @@
+import login from '@commerce/api/endpoints/login'
+import { LoginAPI, operations } from '@framework/api/login'
+import commerce from '@lib/api/commerce'
+
+export default commerce.endpoint({
+ handler: login as LoginAPI['endpoint']['handler'],
+ operations,
+})