From 77408259d28ff59be836ce34ef5ca6cb84d98520 Mon Sep 17 00:00:00 2001 From: brkcvn Date: Wed, 15 Nov 2023 12:36:17 +0300 Subject: [PATCH] auth proccess --- app/[not-found]/page.tsx | 21 --- app/account/component/AccountBook.tsx | 50 +++++++ app/account/component/AuthLayout.tsx | 11 ++ app/account/component/FormButton.tsx | 22 +++ app/account/component/FormFooter.tsx | 45 ++++++ app/account/component/FormHeader.tsx | 3 + app/account/component/OrderHistory.tsx | 14 ++ app/account/component/SignOutSection.tsx | 23 +++ app/account/login/page.tsx | 114 +++++++++++++++ app/account/page.tsx | 19 +++ app/account/recover/page.tsx | 89 ++++++++++++ app/account/register/page.tsx | 114 +++++++++++++++ app/account/reset/[id]/[resetToken]/page.tsx | 131 +++++++++++++++++ lib/isAuthenticated.ts | 8 ++ lib/shopify/index.ts | 143 ++++++++++++++++++- lib/shopify/mutations/auth.ts | 58 ++++++++ lib/shopify/queries/user.ts | 76 ++++++++++ lib/shopify/types.ts | 140 ++++++++++++++++++ middleware.ts | 29 ++++ 19 files changed, 1088 insertions(+), 22 deletions(-) delete mode 100644 app/[not-found]/page.tsx create mode 100644 app/account/component/AccountBook.tsx create mode 100644 app/account/component/AuthLayout.tsx create mode 100644 app/account/component/FormButton.tsx create mode 100644 app/account/component/FormFooter.tsx create mode 100644 app/account/component/FormHeader.tsx create mode 100644 app/account/component/OrderHistory.tsx create mode 100644 app/account/component/SignOutSection.tsx create mode 100644 app/account/login/page.tsx create mode 100644 app/account/page.tsx create mode 100644 app/account/recover/page.tsx create mode 100644 app/account/register/page.tsx create mode 100644 app/account/reset/[id]/[resetToken]/page.tsx create mode 100644 lib/isAuthenticated.ts create mode 100644 lib/shopify/mutations/auth.ts create mode 100644 lib/shopify/queries/user.ts create mode 100644 middleware.ts diff --git a/app/[not-found]/page.tsx b/app/[not-found]/page.tsx deleted file mode 100644 index 31aa7c6ae..000000000 --- a/app/[not-found]/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; - -export const runtime = 'edge'; - -export default function Error({ reset }: { reset: () => void }) { - return ( -
-

Oh no!

-

- There was an issue with our storefront. This could be a temporary issue, please try your - action again. -

- -
- ); -} diff --git a/app/account/component/AccountBook.tsx b/app/account/component/AccountBook.tsx new file mode 100644 index 000000000..193f50c42 --- /dev/null +++ b/app/account/component/AccountBook.tsx @@ -0,0 +1,50 @@ +import AddressCard from '@/components/AddressCard'; +import { Button } from '@/components/Button'; +import { Text } from '@/components/Text'; +import { Customer, MailingAddress } from '@/lib/shopify/types'; +import { convertObjectToQueryString } from '@/lib/utils'; + +export default function AccountBook({ + customer, + addresses, +}: { + customer: Customer; + addresses: MailingAddress[]; +}) { + return ( + <> +
+

Address Book

+
+ {!addresses?.length && ( + + You haven't saved any addresses yet. + + )} +
+ + Add an Address + +
+ {Boolean(addresses?.length) && ( +
+ {customer.defaultAddress && ( + + )} + {addresses + .filter(address => address.id !== customer.defaultAddress?.id) + .map(address => ( + + ))} +
+ )} +
+
+ + ); +} diff --git a/app/account/component/AuthLayout.tsx b/app/account/component/AuthLayout.tsx new file mode 100644 index 000000000..e7daa62ae --- /dev/null +++ b/app/account/component/AuthLayout.tsx @@ -0,0 +1,11 @@ +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
{children}
+
+ ); +} diff --git a/app/account/component/FormButton.tsx b/app/account/component/FormButton.tsx new file mode 100644 index 000000000..824c224d6 --- /dev/null +++ b/app/account/component/FormButton.tsx @@ -0,0 +1,22 @@ +'use client'; +import cn from 'clsx'; +export default function FormButton({ + variant = 'primary' +}: { + btnText: string; + state?: string; + variant?: 'primary' | 'outline'; +}) { + const buttonClasses = cn({ + 'bg-primary text-contrast rounded py-2 px-4 focus:shadow-outline block w-full': + variant === 'primary', + 'text-left text-primary/50 ml-6 text-sm': variant === 'outline' + }); + return ( +
+ +
+ ); +} diff --git a/app/account/component/FormFooter.tsx b/app/account/component/FormFooter.tsx new file mode 100644 index 000000000..5e6c84b7b --- /dev/null +++ b/app/account/component/FormFooter.tsx @@ -0,0 +1,45 @@ +import Link from 'next/link'; + +interface IFormFooter { + page: 'login' | 'register' | 'recover'; +} + +export default function FormFooter({ page }: IFormFooter) { + const data = { + login: { + linkText: 'Create an account', + phrase: 'New to Hydrogen?', + href: '/account/register', + }, + register: { + linkText: 'Sign In', + phrase: 'Already have an account?', + href: '/account/login', + }, + recover: { + linkText: 'Login', + phrase: 'Return to', + href: '/account/login', + }, + }; + + return ( +
+

+ {data[page].phrase} +   + + {data[page].linkText} + +

+ {page === 'login' && ( + + Forgot Password + + )} +
+ ); +} diff --git a/app/account/component/FormHeader.tsx b/app/account/component/FormHeader.tsx new file mode 100644 index 000000000..aececb1ee --- /dev/null +++ b/app/account/component/FormHeader.tsx @@ -0,0 +1,3 @@ +export default function FormHeader({ title }: { title: string }) { + return

{title}

; +} diff --git a/app/account/component/OrderHistory.tsx b/app/account/component/OrderHistory.tsx new file mode 100644 index 000000000..c24215619 --- /dev/null +++ b/app/account/component/OrderHistory.tsx @@ -0,0 +1,14 @@ +import EmptyOrders from '@/components/EmptyOrder'; +import Orders from '@/components/Orders'; +import { Order } from '@/lib/shopify/types'; + +export default function OrderHistory({ orders }: { orders: Order[] }) { + return ( +
+
+

Order History

+ {orders?.length ? : } +
+
+ ); +} diff --git a/app/account/component/SignOutSection.tsx b/app/account/component/SignOutSection.tsx new file mode 100644 index 000000000..1c4175e1a --- /dev/null +++ b/app/account/component/SignOutSection.tsx @@ -0,0 +1,23 @@ +import { cookies } from 'next/headers'; +import { redirect } from 'next/navigation'; + +export default function SignOutSection() { + const signOut = async () => { + 'use server'; + cookies().set({ + name: 'customerAccessToken', + value: '', + httpOnly: true, + path: '/', + expires: new Date(Date.now()), + }); + redirect('/account/login'); + }; + return ( +
+ +
+ ); +} diff --git a/app/account/login/page.tsx b/app/account/login/page.tsx new file mode 100644 index 000000000..85dd7f254 --- /dev/null +++ b/app/account/login/page.tsx @@ -0,0 +1,114 @@ +import { loginCustomer } from 'lib/shopify'; +import { revalidatePath } from 'next/cache'; +import { cookies } from 'next/headers'; +import { redirect } from 'next/navigation'; +import AuthLayout from '../component/AuthLayout'; +import FormButton from '../component/FormButton'; +import FormFooter from '../component/FormFooter'; +import FormHeader from '../component/FormHeader'; + +let emailError: string | null = null; +let passwordError: string | null = null; +let unidentifiedUserError: string | null = null; +export default function LoginPage() { + async function handleSubmit(data: FormData) { + 'use server'; + const loginRes = await loginCustomer({ + variables: { + input: { + email: data.get('email') as string, + password: data.get('password') as string, + }, + }, + }); + + if ( + loginRes.body.data.customerAccessTokenCreate.customerAccessToken + ?.accessToken + ) { + cookies().set({ + name: 'customerAccessToken', + value: + loginRes.body.data.customerAccessTokenCreate.customerAccessToken + .accessToken, + httpOnly: true, + path: '/', + expires: new Date(Date.now() + 20 * 60 * 1000 + 5 * 1000), + }); + redirect('/account'); + } + + if ( + loginRes.body.data.customerAccessTokenCreate.customerUserErrors.length > 0 + ) { + loginRes.body.data.customerAccessTokenCreate.customerUserErrors.filter( + (error: any) => { + if (error.field) { + if (error.field.includes('email')) { + emailError = error.message; + } + if (error.field.includes('password')) { + passwordError = error.message; + } + } else { + if (error.code === 'UNIDENTIFIED_CUSTOMER') { + unidentifiedUserError = error.message; + } + } + } + ); + } + + revalidatePath('/account/login'); + } + + return ( + + + {unidentifiedUserError && ( +

{unidentifiedUserError}

+ )} +
+
+ + {emailError && ( +

{emailError}  

+ )} +
+
+ + {passwordError && ( +

{passwordError}  

+ )} +
+ + + +
+ ); +} diff --git a/app/account/page.tsx b/app/account/page.tsx new file mode 100644 index 000000000..58a015079 --- /dev/null +++ b/app/account/page.tsx @@ -0,0 +1,19 @@ +import { getCustomer } from 'lib/shopify'; +import { cookies } from 'next/headers'; +import SignOutSection from './component/SignOutSection'; + +async function AccountPage({ searchParams }: { searchParams: { [key: string]: string } }) { + const token = cookies().get('customerAccessToken')?.value as string; + const customer = await getCustomer(token); + console.log('customer', customer); + console.log('token', token); + + return ( +
+ + Account Detail +
+ ); +} + +export default AccountPage; diff --git a/app/account/recover/page.tsx b/app/account/recover/page.tsx new file mode 100644 index 000000000..21b84afb1 --- /dev/null +++ b/app/account/recover/page.tsx @@ -0,0 +1,89 @@ +import { recoverCustomersPassword } from 'lib/shopify'; +import { revalidatePath } from 'next/cache'; +import AuthLayout from '../component/AuthLayout'; +import FormButton from '../component/FormButton'; +import FormFooter from '../component/FormFooter'; +import FormHeader from '../component/FormHeader'; + +let emailError: string | null = null; +let isSubmited: boolean = false; +const headings = { + submited: { + title: 'Request Sent.', + description: + 'If that email address is in our system, you will receive an email with instructions about how to reset your password in a few minutes.', + }, + default: { + title: 'Forgot Password.', + description: + 'Enter the email address associated with your account to receive a link to reset your password.', + }, +}; + +export default function RecoverPassword() { + async function handleSubmit(data: FormData) { + 'use server'; + try { + const response = await recoverCustomersPassword({ + variables: { + email: data.get('email') as string, + }, + }); + + if (response.body.data.customerRecover.customerUserErrors.length > 0) { + response.body.data.customerRecover.customerUserErrors.filter( + (error: any) => { + if (error.field && error.field.includes('email')) { + emailError = error.message; + } + } + ); + } else { + isSubmited = true; + } + } catch (error) { + interface ERROR { + message: string; + } + const err = error as { error: ERROR }; + emailError = err.error.message; + } + + revalidatePath('/account/recover'); + } + + return ( + + +

+ {headings[isSubmited ? 'submited' : 'default'].description} +

+ {!isSubmited && ( +
+
+ + {emailError && ( +

{emailError}  

+ )} +
+ + + + )} +
+ ); +} diff --git a/app/account/register/page.tsx b/app/account/register/page.tsx new file mode 100644 index 000000000..7b1b00414 --- /dev/null +++ b/app/account/register/page.tsx @@ -0,0 +1,114 @@ +import { createCustomer, loginCustomer } from 'lib/shopify'; +import { revalidatePath } from 'next/cache'; +import { cookies } from 'next/headers'; +import { redirect } from 'next/navigation'; +import AuthLayout from '../component/AuthLayout'; +import FormButton from '../component/FormButton'; +import FormFooter from '../component/FormFooter'; +import FormHeader from '../component/FormHeader'; + +let emailError: string | null = null; +let passwordError: string | null = null; + +export default function RegisterPage() { + async function handleSubmit(data: FormData) { + 'use server'; + const res = await createCustomer({ + variables: { + input: { + email: data.get('email') as string, + password: data.get('password') as string, + }, + }, + }); + + if (res.body.data.customerCreate.customer) { + const loginRes = await loginCustomer({ + variables: { + input: { + email: data.get('email') as string, + password: data.get('password') as string, + }, + }, + }); + + if ( + loginRes.body.data.customerAccessTokenCreate.customerAccessToken + ?.accessToken + ) { + cookies().set({ + name: 'customerAccessToken', + value: + loginRes.body.data.customerAccessTokenCreate.customerAccessToken + .accessToken, + httpOnly: true, + path: '/', + expires: new Date(Date.now() + 20 * 60 * 1000 + 5 * 1000), + }); + redirect('/account'); + } + + redirect('/account/login'); + } + + if (res.body.data.customerCreate.customerUserErrors.length > 0) { + res.body.data.customerCreate.customerUserErrors.filter((error: any) => { + if (error.field.includes('email')) { + emailError = error.message; + } + if (error.field.includes('password')) { + passwordError = error.message; + } + }); + } + + revalidatePath('/account/register'); + } + + return ( + + +
+
+ + {emailError && ( +

{emailError}  

+ )} +
+
+ + {passwordError && ( +

{passwordError}  

+ )} +
+ + + +
+ ); +} diff --git a/app/account/reset/[id]/[resetToken]/page.tsx b/app/account/reset/[id]/[resetToken]/page.tsx new file mode 100644 index 000000000..b75e15ab4 --- /dev/null +++ b/app/account/reset/[id]/[resetToken]/page.tsx @@ -0,0 +1,131 @@ +import { resetCustomersPassword } from 'lib/shopify'; +import { revalidatePath } from 'next/cache'; +import { cookies } from 'next/headers'; +import { redirect } from 'next/navigation'; +import AuthLayout from '../../../component/AuthLayout'; +import FormButton from '../../../component/FormButton'; +import FormHeader from '../../../component/FormHeader'; + +let errorMessage: string | null = null; +let passwordError: string | null = null; +let passwordConfirmError: string | null = null; + +export default function ResetPassword({ + params, +}: { + params: { id: string; resetToken: string }; +}) { + const handleSubmit = async (data: FormData) => { + 'use server'; + const id = params.id; + const resetToken = params.resetToken; + + const password = data.get('password') as string; + const passwordConfirm = data.get('passwordConfirm') as string; + + if ( + !password || + !passwordConfirm || + typeof password !== 'string' || + typeof passwordConfirm !== 'string' || + password !== passwordConfirm + ) { + passwordConfirmError = 'The two passwords entered did not match.'; + } else { + const res = await resetCustomersPassword({ + variables: { + id: `gid://shopify/Customer/${id}`, + input: { + password, + resetToken, + }, + }, + }); + + const customerAccessToken = + res.body.data.customerReset.customerAccessToken; + + if (customerAccessToken) { + const accessToken = customerAccessToken?.accessToken; + cookies().set({ + name: 'customerAccessToken', + value: accessToken, + httpOnly: true, + path: '/', + expires: new Date(Date.now() + 20 * 60 * 1000 + 5 * 1000), + }); + redirect('/account'); + } + + if (res.body.data.customerReset.customerUserErrors.length > 0) { + res.body.data.customerReset.customerUserErrors.filter((error: any) => { + if (error.field) { + if (error.field.includes('password')) { + passwordError = error.message; + } else if (error.field.includes('passwordConfirm')) { + passwordConfirmError = error.message; + } + } + + if (error.code === 'TOKEN_INVALID') { + errorMessage = error.message; + } + }); + } + } + revalidatePath('/account/reset'); + }; + + return ( + + +

Enter a new password for your account.

+ {errorMessage &&

{errorMessage}

} +
+
+ + {passwordError && ( +

{passwordError}  

+ )} +
+ +
+ + {passwordConfirmError && ( +

+ {' '} + {passwordConfirmError}   +

+ )} +
+ + +
+ ); +} diff --git a/lib/isAuthenticated.ts b/lib/isAuthenticated.ts new file mode 100644 index 000000000..955aff732 --- /dev/null +++ b/lib/isAuthenticated.ts @@ -0,0 +1,8 @@ +import { NextRequest } from 'next/server'; + +const isAuthenticated = (request: NextRequest) => { + const customerAccessToken = request.cookies.get('customerAccessToken')?.value; + return customerAccessToken; +}; + +export default isAuthenticated; diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts index e8b6637c8..0bc065113 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -4,6 +4,12 @@ import { ensureStartsWith } from 'lib/utils'; import { revalidateTag } from 'next/cache'; import { headers } from 'next/headers'; import { NextRequest, NextResponse } from 'next/server'; +import { + CUSTOMER_CREATE_MUTATION, + CUSTOMER_RECOVER_MUTATION, + CUSTOMER_RESET_MUTATION, + LOGIN_MUTATION, +} from './mutations/auth'; import { addToCartMutation, createCartMutation, @@ -23,10 +29,16 @@ import { getProductRecommendationsQuery, getProductsQuery } from './queries/product'; +import { CUSTOMER_QUERY } from './queries/user'; import { Cart, Collection, Connection, + Customer, + CustomerAccessTokenCreatePayload, + CustomerCreatePayload, + CustomerRecoverPayload, + CustomerResetPayload, Image, Menu, Page, @@ -47,7 +59,7 @@ import { ShopifyProductRecommendationsOperation, ShopifyProductsOperation, ShopifyRemoveFromCartOperation, - ShopifyUpdateCartOperation + ShopifyUpdateCartOperation, } from './types'; const domain = process.env.SHOPIFY_STORE_DOMAIN @@ -282,6 +294,135 @@ export async function getCollection(handle: string): Promise({ + query: CUSTOMER_CREATE_MUTATION, + variables, + }); + return data; +} + +export async function loginCustomer({ + variables, +}: { + variables: { + input: { + email: string; + password: string; + }; + }; +}) { + const data = await shopifyFetch<{ + data: { + customerAccessTokenCreate: CustomerAccessTokenCreatePayload; + }; + variables: { + input: { + email: string; + password: string; + }; + }; + }>({ + query: LOGIN_MUTATION, + variables, + }); + return data; +} + +export async function recoverCustomersPassword({ + variables, +}: { + variables: { + email: string; + }; +}) { + const data = await shopifyFetch<{ + data: { + customerRecover: CustomerRecoverPayload; + }; + variables: { + email: string; + }; + }>({ + query: CUSTOMER_RECOVER_MUTATION, + variables, + }); + return data; +} + +export async function resetCustomersPassword({ + variables, +}: { + variables: { + id: string; + input: { + password: string; + resetToken: string; + }; + }; +}) { + const data = await shopifyFetch<{ + data: { + customerReset: CustomerResetPayload; + }; + variables: { + id: string; + input: { + password: string; + resetToken: string; + }; + }; + }>({ + query: CUSTOMER_RESET_MUTATION, + variables, + }); + return data; +} + +export async function getCustomer( + customerAccessToken: string +): Promise { + const res = await shopifyFetch<{ + data: { customer: Customer }; + variables: { + customerAccessToken: string; + }; + }>({ + query: CUSTOMER_QUERY, + variables: { + customerAccessToken, + }, + }); + + /** + * If the customer failed to load, we assume their access token is invalid. + */ + if (!res || !res.body.data.customer) { + // log out customer + } + + return res.body.data.customer; +} + export async function getCollectionProducts({ collection, reverse, diff --git a/lib/shopify/mutations/auth.ts b/lib/shopify/mutations/auth.ts new file mode 100644 index 000000000..9e116581e --- /dev/null +++ b/lib/shopify/mutations/auth.ts @@ -0,0 +1,58 @@ +export const CUSTOMER_CREATE_MUTATION = `#graphql + mutation customerCreate($input: CustomerCreateInput!) { + customerCreate(input: $input) { + customer { + id + } + customerUserErrors { + code + field + message + } + } + } +`; + +export const LOGIN_MUTATION = `#graphql + mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) { + customerAccessTokenCreate(input: $input) { + customerUserErrors { + code + field + message + } + customerAccessToken { + accessToken + expiresAt + } + } + } +`; + +export const CUSTOMER_RECOVER_MUTATION = `#graphql + mutation customerRecover($email: String!) { + customerRecover(email: $email) { + customerUserErrors { + code + field + message + } + } + } +`; + +export const CUSTOMER_RESET_MUTATION = `#graphql + mutation customerReset($id: ID!, $input: CustomerResetInput!) { + customerReset(id: $id, input: $input) { + customerAccessToken { + accessToken + expiresAt + } + customerUserErrors { + code + field + message + } + } + } +`; diff --git a/lib/shopify/queries/user.ts b/lib/shopify/queries/user.ts new file mode 100644 index 000000000..e499e0bec --- /dev/null +++ b/lib/shopify/queries/user.ts @@ -0,0 +1,76 @@ +export const CUSTOMER_QUERY = `#graphql + query CustomerDetails( + $customerAccessToken: String! + $country: CountryCode + $language: LanguageCode + ) @inContext(country: $country, language: $language) { + customer(customerAccessToken: $customerAccessToken) { + firstName + lastName + phone + email + defaultAddress { + id + formatted + firstName + lastName + company + address1 + address2 + country + province + city + zip + phone + } + addresses(first: 6) { + edges { + node { + id + formatted + firstName + lastName + company + address1 + address2 + country + province + city + zip + phone + } + } + } + orders(first: 250, sortKey: PROCESSED_AT, reverse: true) { + edges { + node { + id + orderNumber + processedAt + financialStatus + fulfillmentStatus + currentTotalPrice { + amount + currencyCode + } + lineItems(first: 2) { + edges { + node { + variant { + image { + url + altText + height + width + } + } + title + } + } + } + } + } + } + } + } +`; diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts index 23dc02d46..1db022284 100644 --- a/lib/shopify/types.ts +++ b/lib/shopify/types.ts @@ -263,3 +263,143 @@ export type ShopifyProductsOperation = { sortKey?: string; }; }; + +export type Customer = { + __typename?: 'Customer'; + /** Indicates whether the customer has consented to be sent marketing material via email. */ + acceptsMarketing: boolean; + /** A list of addresses for the customer. */ + addresses: Maybe; + /** The date and time when the customer was created. */ + createdAt: string; + /** The customer’s default address. */ + defaultAddress?: Maybe; + /** The customer’s name, email or phone number. */ + displayName: string; + /** The customer’s email address. */ + email?: Maybe; + /** The customer’s first name. */ + firstName?: Maybe; + /** A unique identifier for the customer. */ + id: string; + /** The customer's most recently updated, incomplete checkout. */ + /** The customer’s last name. */ + lastName?: Maybe; + /** Returns a metafield found by namespace and key. */ + /** + * The metafields associated with the resource matching the supplied list of namespaces and keys. + * + */ + /** The number of orders that the customer has made at the store in their lifetime. */ + numberOfOrders: string; + /** The orders associated with the customer. */ + /** The customer’s phone number. */ + phone?: Maybe; + /** + * A comma separated list of tags that have been added to the customer. + * Additional access scope required: unauthenticated_read_customer_tags. + * + */ + tags: Array; + /** The date and time when the customer information was updated. */ + updatedAt: string; +}; + +export type Scalars = { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; + Color: string; + DateTime: string; + Decimal: string; + HTML: string; + JSON: unknown; + URL: string; + UnsignedInt64: string; +}; + +export type DisplayableError = { + /** The path to the input field that caused the error. */ + field?: Maybe>; + /** The error message. */ + message: Scalars['String']; +}; + +export type CustomerAccessToken = { + __typename?: 'CustomerAccessToken'; + /** The customer’s access token. */ + accessToken: Scalars['String']; + /** The date and time when the customer access token expires. */ + expiresAt: Scalars['DateTime']; +}; + +export type CustomerUserError = DisplayableError & { + __typename?: 'CustomerUserError'; + /** The error code. */ + code?: Maybe; + /** The path to the input field that caused the error. */ + field?: Maybe>; + /** The error message. */ + message: Scalars['String']; +}; + +export type UserError = DisplayableError & { + __typename?: 'UserError'; + /** The path to the input field that caused the error. */ + field?: Maybe>; + /** The error message. */ + message: Scalars['String']; +}; + +export type CustomerAccessTokenCreatePayload = { + __typename?: 'CustomerAccessTokenCreatePayload'; + /** The newly created customer access token object. */ + customerAccessToken?: Maybe; + /** The list of errors that occurred from executing the mutation. */ + customerUserErrors: Array; + /** + * The list of errors that occurred from executing the mutation. + * @deprecated Use `customerUserErrors` instead. + */ + userErrors: Array; +}; + +/** Return type for `customerCreate` mutation. */ +export type CustomerCreatePayload = { + __typename?: 'CustomerCreatePayload'; + /** The created customer object. */ + customer?: Maybe; + /** The list of errors that occurred from executing the mutation. */ + /** + * The list of errors that occurred from executing the mutation. + * @deprecated Use `customerUserErrors` instead. + */ +}; + +export type CustomerRecoverPayload = { + __typename?: 'CustomerRecoverPayload'; + /** The list of errors that occurred from executing the mutation. */ + customerUserErrors: Array; + /** + * The list of errors that occurred from executing the mutation. + * @deprecated Use `customerUserErrors` instead. + */ + userErrors: Array; +}; + +export type CustomerResetPayload = { + __typename?: 'CustomerResetPayload'; + /** The customer object which was reset. */ + customer?: Maybe; + /** A newly created customer access token object for the customer. */ + customerAccessToken?: Maybe; + /** The list of errors that occurred from executing the mutation. */ + customerUserErrors: Array; + /** + * The list of errors that occurred from executing the mutation. + * @deprecated Use `customerUserErrors` instead. + */ + userErrors: Array; +}; diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 000000000..f21bb3985 --- /dev/null +++ b/middleware.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from 'next/server'; +import isAuthenticated from './lib/isAuthenticated'; + +export const config = { + matcher: ['/checkout', '/account', '/account/:path*'], +}; + +export function middleware(request: NextRequest) { + const isLoginPage = request.nextUrl.pathname === '/account/login'; + const isRecoverPasswordPage = + request.nextUrl.pathname.startsWith('/account/recover'); + const isResetPasswordPage = + request.nextUrl.pathname.startsWith('/account/reset'); + const isRegisterPage = request.nextUrl.pathname === '/account/register'; + + const authPages = + isLoginPage || + isRecoverPasswordPage || + isRegisterPage || + isResetPasswordPage; + + if (authPages && isAuthenticated(request)) { + return NextResponse.redirect(new URL('/account', request.url)); + } + + if (!authPages && !isAuthenticated(request)) { + return NextResponse.redirect(new URL('/account/login', request.url)); + } +}