mirror of
https://github.com/vercel/commerce.git
synced 2025-05-18 23:46:58 +00:00
auth proccess
This commit is contained in:
parent
27facd7520
commit
77408259d2
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
50
app/account/component/AccountBook.tsx
Normal file
50
app/account/component/AccountBook.tsx
Normal 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'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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
11
app/account/component/AuthLayout.tsx
Normal file
11
app/account/component/AuthLayout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
22
app/account/component/FormButton.tsx
Normal file
22
app/account/component/FormButton.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
45
app/account/component/FormFooter.tsx
Normal file
45
app/account/component/FormFooter.tsx
Normal 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}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
3
app/account/component/FormHeader.tsx
Normal file
3
app/account/component/FormHeader.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function FormHeader({ title }: { title: string }) {
|
||||||
|
return <h1 className="text-4xl">{title}</h1>;
|
||||||
|
}
|
14
app/account/component/OrderHistory.tsx
Normal file
14
app/account/component/OrderHistory.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
23
app/account/component/SignOutSection.tsx
Normal file
23
app/account/component/SignOutSection.tsx
Normal 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
114
app/account/login/page.tsx
Normal 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} </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} </p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<FormButton btnText="Sign in" />
|
||||||
|
<FormFooter page="login" />
|
||||||
|
</form>
|
||||||
|
</AuthLayout>
|
||||||
|
);
|
||||||
|
}
|
19
app/account/page.tsx
Normal file
19
app/account/page.tsx
Normal 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;
|
89
app/account/recover/page.tsx
Normal file
89
app/account/recover/page.tsx
Normal 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} </p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<FormButton btnText={'Request Reset Link'} />
|
||||||
|
<FormFooter page="recover" />
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</AuthLayout>
|
||||||
|
);
|
||||||
|
}
|
114
app/account/register/page.tsx
Normal file
114
app/account/register/page.tsx
Normal 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} </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} </p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<FormButton btnText="Create Account" />
|
||||||
|
<FormFooter page="register" />
|
||||||
|
</form>
|
||||||
|
</AuthLayout>
|
||||||
|
);
|
||||||
|
}
|
131
app/account/reset/[id]/[resetToken]/page.tsx
Normal file
131
app/account/reset/[id]/[resetToken]/page.tsx
Normal 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} </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}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<FormButton btnText={'Save'} />
|
||||||
|
</form>
|
||||||
|
</AuthLayout>
|
||||||
|
);
|
||||||
|
}
|
8
lib/isAuthenticated.ts
Normal file
8
lib/isAuthenticated.ts
Normal 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;
|
@ -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,
|
||||||
|
58
lib/shopify/mutations/auth.ts
Normal file
58
lib/shopify/mutations/auth.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
76
lib/shopify/queries/user.ts
Normal file
76
lib/shopify/queries/user.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@ -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 customer’s default address. */
|
||||||
|
defaultAddress?: Maybe<string>;
|
||||||
|
/** The customer’s name, email or phone number. */
|
||||||
|
displayName: string;
|
||||||
|
/** The customer’s email address. */
|
||||||
|
email?: Maybe<string>;
|
||||||
|
/** The customer’s first name. */
|
||||||
|
firstName?: Maybe<string>;
|
||||||
|
/** A unique identifier for the customer. */
|
||||||
|
id: string;
|
||||||
|
/** The customer's most recently updated, incomplete checkout. */
|
||||||
|
/** The customer’s 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 customer’s 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 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<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
29
middleware.ts
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user