auth proccess

This commit is contained in:
brkcvn 2023-11-15 12:36:17 +03:00
parent 27facd7520
commit 77408259d2
19 changed files with 1088 additions and 22 deletions

View File

@ -1,21 +0,0 @@
'use client';
export const runtime = 'edge';
export default function Error({ reset }: { reset: () => void }) {
return (
<div className="mx-auto my-4 flex max-w-xl flex-col rounded-lg border border-neutral-200 bg-white p-8 dark:border-neutral-800 dark:bg-black md:p-12">
<h2 className="text-xl font-bold">Oh no!</h2>
<p className="my-2">
There was an issue with our storefront. This could be a temporary issue, please try your
action again.
</p>
<button
className="mx-auto mt-4 flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90"
onClick={() => reset()}
>
Try Again
</button>
</div>
);
}

View File

@ -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 (
<>
<div className="grid w-full gap-4 p-4 py-6 md:gap-8 md:p-8 lg:p-12">
<h3 className="font-bold text-lead">Address Book</h3>
<div>
{!addresses?.length && (
<Text className="mb-1" width="narrow" as="p" size="copy">
You haven&apos;t saved any addresses yet.
</Text>
)}
<div className="w-48">
<a
href={`account?${convertObjectToQueryString({
modal: 'address-add',
})}`}
className="inline-block rounded font-medium text-center py-3 px-6 border border-primary/10 bg-contrast text-primary mt-2 text-sm w-full mb-6"
>
Add an Address
</a>
</div>
{Boolean(addresses?.length) && (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{customer.defaultAddress && (
<AddressCard address={customer.defaultAddress} defaultAddress />
)}
{addresses
.filter(address => address.id !== customer.defaultAddress?.id)
.map(address => (
<AddressCard key={address.id} address={address} />
))}
</div>
)}
</div>
</div>
</>
);
}

View File

@ -0,0 +1,11 @@
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex justify-center my-24 px-4">
<div className="max-w-md w-full">{children}</div>
</div>
);
}

View File

@ -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 (
<div className="flex items-center justify-between">
<button className={buttonClasses} type="submit">
Submit
</button>
</div>
);
}

View File

@ -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 (
<div className="flex items-center justify-between gap-5 mt-8 border-t border-gray-300">
<p className="align-baseline text-sm mt-6">
{data[page].phrase}
&nbsp;
<Link className="inline underline" href={data[page].href}>
{data[page].linkText}
</Link>
</p>
{page === 'login' && (
<Link
className="mt-6 inline-block align-baseline text-sm text-primary/50x"
href="/account/recover"
>
Forgot Password
</Link>
)}
</div>
);
}

View File

@ -0,0 +1,3 @@
export default function FormHeader({ title }: { title: string }) {
return <h1 className="text-4xl">{title}</h1>;
}

View File

@ -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 (
<div className="mt-6">
<div className="grid w-full gap-4 p-4 py-6 md:gap-8 md:p-8 lg:p-12">
<h2 className="font-bold text-lead">Order History</h2>
{orders?.length ? <Orders orders={orders} /> : <EmptyOrders />}
</div>
</div>
);
}

View File

@ -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 (
<form action={signOut} noValidate>
<button type="submit" className="text-primary/50">
Sign out
</button>
</form>
);
}

114
app/account/login/page.tsx Normal file
View File

@ -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 (
<AuthLayout>
<FormHeader title="Sign in." />
{unidentifiedUserError && (
<p className="text-red-500 mt-4">{unidentifiedUserError}</p>
)}
<form
action={handleSubmit}
noValidate
className="pt-6 pb-8 mt-4 mb-4 space-y-3"
>
<div>
<input
className={`mb-1}`}
id="email"
name="email"
type="email"
autoComplete="email"
required
placeholder="Email address"
aria-label="Email address"
autoFocus
/>
{emailError && (
<p className="text-red-500 text-xs">{emailError} &nbsp;</p>
)}
</div>
<div>
<input
className={`mb-1`}
id="password"
name="password"
type="password"
autoComplete="current-password"
placeholder="Password"
aria-label="Password"
minLength={8}
required
autoFocus
/>
{passwordError && (
<p className="text-red-500 text-xs"> {passwordError} &nbsp;</p>
)}
</div>
<FormButton btnText="Sign in" />
<FormFooter page="login" />
</form>
</AuthLayout>
);
}

19
app/account/page.tsx Normal file
View File

@ -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 (
<div>
<SignOutSection />
Account Detail
</div>
);
}
export default AccountPage;

View File

@ -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 (
<AuthLayout>
<FormHeader title={headings[isSubmited ? 'submited' : 'default'].title} />
<p className="mt-4">
{headings[isSubmited ? 'submited' : 'default'].description}
</p>
{!isSubmited && (
<form
action={handleSubmit}
noValidate
className="pt-6 pb-8 mt-4 mb-4 space-y-3"
>
<div>
<input
className={`mb-1`}
id="email"
name="email"
type="email"
autoComplete="email"
required
placeholder="Email address"
aria-label="Email address"
autoFocus
/>
{emailError && (
<p className="text-red-500 text-xs">{emailError} &nbsp;</p>
)}
</div>
<FormButton btnText={'Request Reset Link'} />
<FormFooter page="recover" />
</form>
)}
</AuthLayout>
);
}

View File

@ -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 (
<AuthLayout>
<FormHeader title="Create an Account" />
<form
action={handleSubmit}
noValidate
className="pt-6 pb-8 mt-4 mb-4 space-y-3"
>
<div>
<input
className={`mb-1`}
id="email"
name="email"
type="email"
autoComplete="email"
required
placeholder="Email address"
aria-label="Email address"
autoFocus
/>
{emailError && (
<p className="text-red-500 text-xs">{emailError} &nbsp;</p>
)}
</div>
<div>
<input
className={`mb-1`}
id="password"
name="password"
type="password"
autoComplete="current-password"
placeholder="Password"
aria-label="Password"
minLength={8}
required
autoFocus
/>
{passwordError && (
<p className="text-red-500 text-xs"> {passwordError} &nbsp;</p>
)}
</div>
<FormButton btnText="Create Account" />
<FormFooter page="register" />
</form>
</AuthLayout>
);
}

View File

@ -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 (
<AuthLayout>
<FormHeader title="Reset Password." />
<p className="mt-4">Enter a new password for your account.</p>
{errorMessage && <p className="text-red-500 mt-4">{errorMessage}</p>}
<form
action={handleSubmit}
noValidate
className="pt-6 pb-8 mt-4 mb-4 space-y-3"
>
<div>
<input
className={`mb-1`}
id="password"
name="password"
type="password"
autoComplete="current-password"
placeholder="Password"
aria-label="Password"
minLength={8}
required
autoFocus
/>
{passwordError && (
<p className="text-red-500 text-xs"> {passwordError} &nbsp;</p>
)}
</div>
<div>
<input
className={`mb-1`}
id="passwordConfirm"
name="passwordConfirm"
type="password"
autoComplete="current-password"
placeholder="Re-enter password"
aria-label="Re-enter password"
minLength={8}
required
autoFocus
/>
{passwordConfirmError && (
<p className="text-red-500 text-xs">
{' '}
{passwordConfirmError} &nbsp;
</p>
)}
</div>
<FormButton btnText={'Save'} />
</form>
</AuthLayout>
);
}

8
lib/isAuthenticated.ts Normal file
View File

@ -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;

View File

@ -4,6 +4,12 @@ import { ensureStartsWith } from 'lib/utils';
import { revalidateTag } from 'next/cache'; import { revalidateTag } from 'next/cache';
import { headers } from 'next/headers'; import { headers } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
import {
CUSTOMER_CREATE_MUTATION,
CUSTOMER_RECOVER_MUTATION,
CUSTOMER_RESET_MUTATION,
LOGIN_MUTATION,
} from './mutations/auth';
import { import {
addToCartMutation, addToCartMutation,
createCartMutation, createCartMutation,
@ -23,10 +29,16 @@ import {
getProductRecommendationsQuery, getProductRecommendationsQuery,
getProductsQuery getProductsQuery
} from './queries/product'; } from './queries/product';
import { CUSTOMER_QUERY } from './queries/user';
import { import {
Cart, Cart,
Collection, Collection,
Connection, Connection,
Customer,
CustomerAccessTokenCreatePayload,
CustomerCreatePayload,
CustomerRecoverPayload,
CustomerResetPayload,
Image, Image,
Menu, Menu,
Page, Page,
@ -47,7 +59,7 @@ import {
ShopifyProductRecommendationsOperation, ShopifyProductRecommendationsOperation,
ShopifyProductsOperation, ShopifyProductsOperation,
ShopifyRemoveFromCartOperation, ShopifyRemoveFromCartOperation,
ShopifyUpdateCartOperation ShopifyUpdateCartOperation,
} from './types'; } from './types';
const domain = process.env.SHOPIFY_STORE_DOMAIN const domain = process.env.SHOPIFY_STORE_DOMAIN
@ -282,6 +294,135 @@ export async function getCollection(handle: string): Promise<Collection | undefi
return reshapeCollection(res.body.data.collection); return reshapeCollection(res.body.data.collection);
} }
export async function createCustomer({
variables,
}: {
variables: {
input: {
email: string;
password: string;
};
};
}) {
const data = await shopifyFetch<{
data: {
customerCreate: CustomerCreatePayload;
};
variables: {
input: {
email: string;
password: string;
};
};
}>({
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<Customer> {
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({ export async function getCollectionProducts({
collection, collection,
reverse, reverse,

View File

@ -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
}
}
}
`;

View File

@ -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
}
}
}
}
}
}
}
}
`;

View File

@ -263,3 +263,143 @@ export type ShopifyProductsOperation = {
sortKey?: string; 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<string>;
/** The date and time when the customer was created. */
createdAt: string;
/** The customers default address. */
defaultAddress?: Maybe<string>;
/** The customers name, email or phone number. */
displayName: string;
/** The customers email address. */
email?: Maybe<string>;
/** The customers first name. */
firstName?: Maybe<string>;
/** A unique identifier for the customer. */
id: string;
/** The customer's most recently updated, incomplete checkout. */
/** The customers last name. */
lastName?: Maybe<string>;
/** 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 customers phone number. */
phone?: Maybe<string>;
/**
* A comma separated list of tags that have been added to the customer.
* Additional access scope required: unauthenticated_read_customer_tags.
*
*/
tags: Array<string>;
/** 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<Array<Scalars['String']>>;
/** The error message. */
message: Scalars['String'];
};
export type CustomerAccessToken = {
__typename?: 'CustomerAccessToken';
/** The customers 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<any>;
/** The path to the input field that caused the error. */
field?: Maybe<Array<Scalars['String']>>;
/** The error message. */
message: Scalars['String'];
};
export type UserError = DisplayableError & {
__typename?: 'UserError';
/** The path to the input field that caused the error. */
field?: Maybe<Array<Scalars['String']>>;
/** The error message. */
message: Scalars['String'];
};
export type CustomerAccessTokenCreatePayload = {
__typename?: 'CustomerAccessTokenCreatePayload';
/** The newly created customer access token object. */
customerAccessToken?: Maybe<CustomerAccessToken>;
/** The list of errors that occurred from executing the mutation. */
customerUserErrors: Array<CustomerUserError>;
/**
* The list of errors that occurred from executing the mutation.
* @deprecated Use `customerUserErrors` instead.
*/
userErrors: Array<UserError>;
};
/** Return type for `customerCreate` mutation. */
export type CustomerCreatePayload = {
__typename?: 'CustomerCreatePayload';
/** The created customer object. */
customer?: Maybe<Customer>;
/** 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<CustomerUserError>;
/**
* The list of errors that occurred from executing the mutation.
* @deprecated Use `customerUserErrors` instead.
*/
userErrors: Array<UserError>;
};
export type CustomerResetPayload = {
__typename?: 'CustomerResetPayload';
/** The customer object which was reset. */
customer?: Maybe<Customer>;
/** A newly created customer access token object for the customer. */
customerAccessToken?: Maybe<CustomerAccessToken>;
/** The list of errors that occurred from executing the mutation. */
customerUserErrors: Array<CustomerUserError>;
/**
* The list of errors that occurred from executing the mutation.
* @deprecated Use `customerUserErrors` instead.
*/
userErrors: Array<UserError>;
};

29
middleware.ts Normal file
View File

@ -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));
}
}