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.
+
+ )}
+
+ {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 (
+
+ );
+}
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}
+ )}
+
+
+ );
+}
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 && (
+
+ )}
+
+ );
+}
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 (
+
+
+
+
+ );
+}
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}
}
+
+
+ );
+}
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));
+ }
+}