mirror of
https://github.com/vercel/commerce.git
synced 2025-06-28 01:11:24 +00:00
refactor: change layout profile and home page
This commit is contained in:
parent
19802716d4
commit
6775f7363b
@ -80,13 +80,13 @@ export default function CheckoutPage() {
|
|||||||
<div className="flew-row col-span-4 row-span-2 flex">
|
<div className="flew-row col-span-4 row-span-2 flex">
|
||||||
<Accordion defaultExpandedKeys={['1']} className="text-white md:w-2/3">
|
<Accordion defaultExpandedKeys={['1']} className="text-white md:w-2/3">
|
||||||
<AccordionItem key="1" title="Shipping Info" className="text-white">
|
<AccordionItem key="1" title="Shipping Info" className="text-white">
|
||||||
<ShippingForm className="p-4" handleChangeAction={handleChangeShipping} />
|
<ShippingForm handleChangeAction={handleChangeShipping} />
|
||||||
<Checkbox onValueChange={(v) => setSameBilling(v)} className="mt-2">
|
<Checkbox onValueChange={(v) => setSameBilling(v)} className="mt-2">
|
||||||
Use same address for billing?
|
Use same address for billing?
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem key="2" title="Billing Info" className="text-white">
|
<AccordionItem key="2" title="Billing Info" className="text-white">
|
||||||
<ShippingForm className="p-4" handleChangeAction={handleChangeBilling} />
|
<ShippingForm handleChangeAction={handleChangeBilling} />
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem key="3" title="Payment" className="text-white">
|
<AccordionItem key="3" title="Payment" className="text-white">
|
||||||
<div className="flex flex-col justify-between overflow-hidden p-1">
|
<div className="flex flex-col justify-between overflow-hidden p-1">
|
||||||
|
73
app/page.tsx
73
app/page.tsx
@ -1,18 +1,77 @@
|
|||||||
import { Carousel } from 'components/carousel';
|
|
||||||
import { ThreeItemGrid } from 'components/grid/three-items';
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: 'High-performance ecommerce store built with Next.js, Vercel, and Shopify.',
|
description: 'High-performance ecommerce store built with Next.js, Vercel, and Shopify.',
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: 'website'
|
type: 'website'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
import { Carousel } from 'components/carousel';
|
||||||
|
import { ThreeItemGrid } from 'components/grid/three-items';
|
||||||
|
import { Category } from 'lib/woocomerce/models/base';
|
||||||
|
import { Product } from 'lib/woocomerce/models/product';
|
||||||
|
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||||
|
import { wordpress } from 'lib/wordpress/wordpress';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
|
const categories: Category[] = await woocommerce.get('products/categories');
|
||||||
|
const productsByCategory: Record<string, Product[]> = {};
|
||||||
|
await Promise.all(
|
||||||
|
categories.map((category) =>
|
||||||
|
woocommerce.get('products', { category: category.id.toString() }).then((products) => {
|
||||||
|
productsByCategory[category.name] = products;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const posts = await wordpress.get('posts');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<section>
|
||||||
<ThreeItemGrid />
|
{categories.map((category, index) => (
|
||||||
<Carousel />
|
<div key={category.id} className={index % 2 === 0 ? 'bg-blue-600 py-4' : 'bg-white py-4'}>
|
||||||
</>
|
<div className="mb-2 mt-6 flex items-center justify-between px-4">
|
||||||
|
<span className={`${index % 2 === 0 ? 'text-white' : 'text-black'} text-2xl font-bold`}>
|
||||||
|
{category.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mb-6 px-4">
|
||||||
|
<span className={`${index % 2 === 0 ? 'text-white' : 'text-black'}`}>
|
||||||
|
{category.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<React.Fragment key={category.id}>
|
||||||
|
{productsByCategory[category.name] && (
|
||||||
|
<ThreeItemGrid products={productsByCategory[category.name] ?? []} />
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
{index === 1 && (
|
||||||
|
<div className="my-6 flex flex-col px-4">
|
||||||
|
<span className="mb-2 text-2xl font-bold">Top products</span>
|
||||||
|
<Carousel />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="my-6 flex flex-col px-4">
|
||||||
|
<span className="mb-2 text-2xl font-bold">Latest posts</span>
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{posts.map((post: any) => (
|
||||||
|
<div
|
||||||
|
key={post.id + '-post'}
|
||||||
|
className="rounded-lg border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-black"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={post.featured_image}
|
||||||
|
alt={post.title.rendered}
|
||||||
|
className="h-48 w-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="p-4">
|
||||||
|
<h2 className="text-xl font-bold">{post.title.rendered}</h2>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
7
app/profile/area/page.tsx
Normal file
7
app/profile/area/page.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default async function PersonalArea() {
|
||||||
|
return (
|
||||||
|
<section className="mt-4 grid max-w-screen-2xl gap-4 px-4 pb-4">
|
||||||
|
<h1 className="text-2xl font-bold">Personal Area</h1>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
66
app/profile/layout.tsx
Normal file
66
app/profile/layout.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Avatar } from '@nextui-org/react';
|
||||||
|
import LogoutButton from 'components/button/logout';
|
||||||
|
import { Customer } from 'lib/woocomerce/models/customer';
|
||||||
|
import { Shipping } from 'lib/woocomerce/models/shipping';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export default function ProfileLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const [customer, setCustomer] = useState<Customer | undefined>(undefined);
|
||||||
|
const [shippingAddress, setShippingAddress] = useState<Shipping | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCustomer = async () => {
|
||||||
|
const data = (await (
|
||||||
|
await fetch('/api/customer', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).json()) as Customer;
|
||||||
|
setCustomer(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCustomer();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="mx-auto mt-4 flex max-w-screen-2xl flex-row gap-4 px-4 pb-4">
|
||||||
|
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-black md:w-1/3">
|
||||||
|
<h2 className="mb-2 text-2xl font-bold">Profile</h2>
|
||||||
|
{customer && (
|
||||||
|
<div>
|
||||||
|
<Avatar src={customer.avatar_url} alt="avatar" className="h-11 w-11" />
|
||||||
|
<div>
|
||||||
|
<span className="text-lg font-bold">{customer.first_name}</span>
|
||||||
|
<span className="text-lg font-bold">{customer.last_name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-start mt-3 flex">
|
||||||
|
<Link href={`/profile/area`}>
|
||||||
|
<button type="button" className="w-full rounded-md py-3">
|
||||||
|
Personal area
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex-start mt-3 flex">
|
||||||
|
<Link href={`/profile/orders`}>
|
||||||
|
<button type="button" className="w-full rounded-md py-3">
|
||||||
|
Orders
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3">
|
||||||
|
<LogoutButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-black md:w-2/3">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
@ -10,7 +10,7 @@ export default async function OrdersPage() {
|
|||||||
const orders = await woocommerce.get('orders');
|
const orders = await woocommerce.get('orders');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto mt-4 grid max-w-screen-2xl justify-center gap-4 px-4 pb-4">
|
<section className="mt-4 grid max-w-screen-2xl gap-4 px-4 pb-4">
|
||||||
<h1 className="text-2xl font-bold">Orders</h1>
|
<h1 className="text-2xl font-bold">Orders</h1>
|
||||||
{orders.map((order) => (
|
{orders.map((order) => (
|
||||||
<Link href={`/profile/orders/${order.id}`} key={order.id} className="flex flex-col">
|
<Link href={`/profile/orders/${order.id}`} key={order.id} className="flex flex-col">
|
||||||
|
@ -1,106 +1,7 @@
|
|||||||
'use client';
|
export default async function ProfilePage() {
|
||||||
|
|
||||||
import LogoutButton from 'components/button/logout';
|
|
||||||
import ShippingForm from 'components/shipping/form';
|
|
||||||
import { Customer } from 'lib/woocomerce/models/customer';
|
|
||||||
import { Shipping } from 'lib/woocomerce/models/shipping';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export default function ProfilePage() {
|
|
||||||
const [customer, setCustomer] = useState<Customer | undefined>(undefined);
|
|
||||||
const [shippingAddress, setShippingAddress] = useState<Shipping | undefined>(undefined);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchCustomer = async () => {
|
|
||||||
const data = (await (
|
|
||||||
await fetch('/api/customer', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
).json()) as Customer;
|
|
||||||
setCustomer(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchCustomer();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto mt-4 grid max-w-screen-2xl gap-4 px-4 pb-4">
|
<section className="mt-4 grid max-w-screen-2xl gap-4 px-4 pb-4">
|
||||||
<h1 className="text-2xl font-bold">Profile</h1>
|
<h1 className="text-2xl font-bold">Personal Area</h1>
|
||||||
<div className="rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-black">
|
|
||||||
<h2 className="text-2xl font-bold">Info</h2>
|
|
||||||
{customer && (
|
|
||||||
<div>
|
|
||||||
<img src={customer.avatar_url} alt="avatar" className="h-11 w-11" />
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<div className="mt-4">
|
|
||||||
<label
|
|
||||||
htmlFor="name"
|
|
||||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
value={customer.first_name}
|
|
||||||
className="mt-1 block w-full rounded-md border-gray-300 p-3 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-lg"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4">
|
|
||||||
<label
|
|
||||||
htmlFor="lastname"
|
|
||||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
Cognome
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="lastname"
|
|
||||||
value={customer.last_name}
|
|
||||||
className="mt-1 block w-full rounded-md border-gray-300 p-3 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-lg"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4">
|
|
||||||
<label
|
|
||||||
htmlFor="email"
|
|
||||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
value={customer.email}
|
|
||||||
className="mt-1 block w-full rounded-md border-gray-300 p-3 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-lg"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ShippingForm title="Shipping Info" handleChangeAction={setShippingAddress} />
|
|
||||||
|
|
||||||
<div className="mt-4">
|
|
||||||
<Link href={`/profile/orders`}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="w-full rounded-md bg-indigo-500 p-3 text-white"
|
|
||||||
>
|
|
||||||
Orders
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4">
|
|
||||||
<LogoutButton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ export default function LogoutButton() {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-full rounded-md bg-indigo-500 p-3 text-white"
|
className="rounded-md py-3"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signOut({ callbackUrl: '/' });
|
signOut({ callbackUrl: '/' });
|
||||||
}}
|
}}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { NextUIProvider } from '@nextui-org/react';
|
||||||
import { Cart } from 'lib/woocomerce/models/cart';
|
import { Cart } from 'lib/woocomerce/models/cart';
|
||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
@ -30,14 +31,16 @@ export function CartProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CartContext.Provider
|
<NextUIProvider>
|
||||||
value={{
|
<CartContext.Provider
|
||||||
cart,
|
value={{
|
||||||
setNewCart
|
cart,
|
||||||
}}
|
setNewCart
|
||||||
>
|
}}
|
||||||
{children}
|
>
|
||||||
</CartContext.Provider>
|
{children}
|
||||||
|
</CartContext.Provider>
|
||||||
|
</NextUIProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { GridTileImage } from 'components/grid/tile';
|
import { GridTileImage } from 'components/grid/tile';
|
||||||
import { Product } from 'lib/woocomerce/models/product';
|
import { Product } from 'lib/woocomerce/models/product';
|
||||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export function ThreeItemGridItem({
|
export function ThreeItemGridItem({
|
||||||
@ -41,12 +40,7 @@ export function ThreeItemGridItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ThreeItemGrid() {
|
export async function ThreeItemGrid({ products }: { products: Product[] }) {
|
||||||
// Collections that start with `hidden-*` are hidden from the search page.
|
|
||||||
const products: Product[] = await woocommerce.get('products');
|
|
||||||
|
|
||||||
const [firstProduct, secondProduct, thirdProduct] = products;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto grid max-w-screen-2xl gap-4 px-4 pb-4 md:grid-cols-6 md:grid-rows-2">
|
<section className="mx-auto grid max-w-screen-2xl gap-4 px-4 pb-4 md:grid-cols-6 md:grid-rows-2">
|
||||||
{products.map((product, index) => (
|
{products.map((product, index) => (
|
||||||
|
37
components/layout/breadcrumb.tsx
Normal file
37
components/layout/breadcrumb.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use client';
|
||||||
|
import { BreadcrumbItem, Breadcrumbs } from '@nextui-org/react';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
export default function Bread() {
|
||||||
|
const paths: { name: string; href: string }[] = [];
|
||||||
|
const currentPath = usePathname();
|
||||||
|
if (currentPath !== '/') {
|
||||||
|
currentPath.split('/').map((path) => {
|
||||||
|
if (path.length === 0) {
|
||||||
|
paths.push({
|
||||||
|
name: 'Home',
|
||||||
|
href: '/'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const pathName = path.charAt(0).toUpperCase() + path.slice(1);
|
||||||
|
paths.push({
|
||||||
|
name: pathName,
|
||||||
|
href: `/${path}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{paths.length > 0 && (
|
||||||
|
<Breadcrumbs className="p-4">
|
||||||
|
{paths.map((path) => (
|
||||||
|
<BreadcrumbItem key={path.name} href={path.href}>
|
||||||
|
{path.name}
|
||||||
|
</BreadcrumbItem>
|
||||||
|
))}
|
||||||
|
</Breadcrumbs>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,11 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { Avatar, Select, SelectItem } from '@nextui-org/react';
|
import { Avatar, Select, SelectItem } from '@nextui-org/react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { getCountries } from 'lib/utils';
|
||||||
import { Shipping } from 'lib/woocomerce/models/shipping';
|
import { Shipping } from 'lib/woocomerce/models/shipping';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import countriesJson from '../../types/countries.json';
|
|
||||||
|
|
||||||
export const countries = countriesJson as { country: string; flag_base64: string }[];
|
|
||||||
|
|
||||||
export default function ShippingForm({
|
export default function ShippingForm({
|
||||||
className,
|
className,
|
||||||
@ -16,6 +14,7 @@ export default function ShippingForm({
|
|||||||
title?: string;
|
title?: string;
|
||||||
handleChangeAction?: (data: Shipping) => void;
|
handleChangeAction?: (data: Shipping) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const countries = getCountries();
|
||||||
const initialState: Shipping = {
|
const initialState: Shipping = {
|
||||||
first_name: '',
|
first_name: '',
|
||||||
last_name: '',
|
last_name: '',
|
||||||
@ -38,7 +37,7 @@ export default function ShippingForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('flex flex-col gap-4', className)}>
|
<div className={clsx('flex flex-col', className)}>
|
||||||
{title && <h2 className="mt-2 text-2xl font-bold">{title}</h2>}
|
{title && <h2 className="mt-2 text-2xl font-bold">{title}</h2>}
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label
|
<label
|
||||||
@ -123,12 +122,12 @@ export default function ShippingForm({
|
|||||||
>
|
>
|
||||||
{countries.map((item) => (
|
{countries.map((item) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={item.country}
|
key={item.name}
|
||||||
startContent={
|
startContent={
|
||||||
<Avatar alt={item.country + '-img'} className="h-6 w-6" src={item.flag_base64} />
|
<Avatar alt={item.name + '-img'} className="h-6 w-6" src={item.icon} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{item.country}
|
{item.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ReadonlyURLSearchParams } from 'next/navigation';
|
import { ReadonlyURLSearchParams } from 'next/navigation';
|
||||||
|
import countries from '../types/countries.json';
|
||||||
|
|
||||||
export const createUrl = (pathname: string, params: URLSearchParams | ReadonlyURLSearchParams) => {
|
export const createUrl = (pathname: string, params: URLSearchParams | ReadonlyURLSearchParams) => {
|
||||||
const paramsString = params.toString();
|
const paramsString = params.toString();
|
||||||
@ -9,3 +10,9 @@ export const createUrl = (pathname: string, params: URLSearchParams | ReadonlyUR
|
|||||||
|
|
||||||
export const ensureStartsWith = (stringToCheck: string, startsWith: string) =>
|
export const ensureStartsWith = (stringToCheck: string, startsWith: string) =>
|
||||||
stringToCheck.startsWith(startsWith) ? stringToCheck : `${startsWith}${stringToCheck}`;
|
stringToCheck.startsWith(startsWith) ? stringToCheck : `${startsWith}${stringToCheck}`;
|
||||||
|
|
||||||
|
export const getCountries = (): { name: string; icon: string }[] =>
|
||||||
|
(countries as { country: string; flag_base64: string }[]).map(({ country, flag_base64 }) => ({
|
||||||
|
name: country,
|
||||||
|
icon: flag_base64
|
||||||
|
}));
|
||||||
|
@ -14,6 +14,7 @@ export type Category = {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Tag = {
|
export type Tag = {
|
||||||
|
429
lib/wordpress/models/client.ts
Normal file
429
lib/wordpress/models/client.ts
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
import axios, { AxiosRequestConfig, RawAxiosRequestHeaders } from 'axios';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
import OAuth from 'oauth-1.0a';
|
||||||
|
import Url from 'url-parse';
|
||||||
|
import { DELETE, IWCRestApiOptions, WCRestApiEndpoint, WCRestApiMethod } from './clientOptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the axiosConfig property to the axios config object.
|
||||||
|
* Could reveive any axios |... config objects.
|
||||||
|
* @param {AxiosRequestConfig} axiosConfig
|
||||||
|
*/
|
||||||
|
export type WCRestApiOptions = IWCRestApiOptions<AxiosRequestConfig>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all the possible query params for the WCCommerce REST API.
|
||||||
|
*/
|
||||||
|
export type WCRestApiParams = DELETE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the response types for each endpoint.
|
||||||
|
*/
|
||||||
|
type WCCommerceResponse<
|
||||||
|
T extends WCRestApiEndpoint,
|
||||||
|
P extends Partial<WCRestApiParams> = {}
|
||||||
|
> = P['id'] extends number | string ? (T extends 'posts' ? any : any) : any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WCCommerce REST API wrapper
|
||||||
|
*
|
||||||
|
* @param {Object} opt
|
||||||
|
*/
|
||||||
|
export default class WCCommerceRestApi<T extends WCRestApiOptions> {
|
||||||
|
protected _opt: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param {Object} opt
|
||||||
|
*/
|
||||||
|
constructor(opt: T) {
|
||||||
|
this._opt = opt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the class is not instantiated, return a new instance.
|
||||||
|
* This is useful for the static methods.
|
||||||
|
*/
|
||||||
|
if (!(this instanceof WCCommerceRestApi)) {
|
||||||
|
return new WCCommerceRestApi(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the url is defined.
|
||||||
|
*/
|
||||||
|
if (!this._opt.url || this._opt.url === '') {
|
||||||
|
throw new OptionsException('url is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the consumerKey is defined.
|
||||||
|
*/
|
||||||
|
if (!this._opt.consumerKey || this._opt.consumerKey === '') {
|
||||||
|
throw new OptionsException('consumerKey is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the consumerSecret is defined.
|
||||||
|
*/
|
||||||
|
if (!this._opt.consumerSecret || this._opt.consumerSecret === '') {
|
||||||
|
throw new OptionsException('consumerSecret is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default options
|
||||||
|
*/
|
||||||
|
this._setDefaultsOptions(this._opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default options
|
||||||
|
*
|
||||||
|
* @param {Object} opt
|
||||||
|
*/
|
||||||
|
_setDefaultsOptions(opt: T): void {
|
||||||
|
this._opt.wpAPIPrefix = opt.wpAPIPrefix || 'wp-json';
|
||||||
|
this._opt.version = opt.version || 'wp/v2';
|
||||||
|
this._opt.isHttps = /^https/i.test(this._opt.url);
|
||||||
|
this._opt.encoding = opt.encoding || 'utf-8';
|
||||||
|
this._opt.queryStringAuth = opt.queryStringAuth || false;
|
||||||
|
this._opt.classVersion = '0.0.2';
|
||||||
|
}
|
||||||
|
|
||||||
|
login(username: string, password: string): Promise<any> {
|
||||||
|
return this._request('POST', 'token', { username, password }, {}, 'jwt-auth/v1');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse params to object.
|
||||||
|
*
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Object} query
|
||||||
|
* @return {Object} IWCRestApiQuery
|
||||||
|
*/
|
||||||
|
// _parseParamsObject<T>(params: Record<string, T>, query: Record<string, any>): IWCRestApiQuery {
|
||||||
|
// for (const key in params) {
|
||||||
|
// if (typeof params[key] === "object") {
|
||||||
|
// // If the value is an object, loop through it and add it to the query object
|
||||||
|
// for (const subKey in params[key]) {
|
||||||
|
// query[key + "[" + subKey + "]"] = params[key][subKey];
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// query[key] = params[key]; // If the value is not an object, add it to the query object
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return query; // Return the query object
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize query string for oAuth 1.0a
|
||||||
|
* Depends on the _parseParamsObject method
|
||||||
|
*
|
||||||
|
* @param {String} url
|
||||||
|
* @param {Object} params
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
_normalizeQueryString(url: string, params: Partial<Record<string, any>>): string {
|
||||||
|
/**
|
||||||
|
* Exit if url and params are not defined
|
||||||
|
*/
|
||||||
|
if (url.indexOf('?') === -1 && Object.keys(params).length === 0) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
const query = new Url(url, true).query; // Parse the query string returned by the url
|
||||||
|
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
let queryString = '';
|
||||||
|
|
||||||
|
// Include params object into URL.searchParams.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
// const a = this._parseParamsObject(params, query);
|
||||||
|
// console.log("A:", a);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop through the params object and push the key and value into the values array
|
||||||
|
* Example: values = ['key1=value1', 'key2=value2']
|
||||||
|
*/
|
||||||
|
for (const key in query) {
|
||||||
|
values.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
values.sort(); // Sort the values array
|
||||||
|
|
||||||
|
for (const i in values) {
|
||||||
|
/*
|
||||||
|
* If the queryString is not empty, add an ampersand to the end of the string
|
||||||
|
*/
|
||||||
|
if (queryString.length) queryString += '&';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the key and value to the queryString
|
||||||
|
*/
|
||||||
|
queryString +=
|
||||||
|
encodeURIComponent(values[i] || '') +
|
||||||
|
'=' +
|
||||||
|
encodeURIComponent(<string | number | boolean>(query[values[i] as string] ?? ''));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Replace %5B with [ and %5D with ]
|
||||||
|
*/
|
||||||
|
queryString = queryString.replace(/%5B/g, '[').replace(/%5D/g, ']');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the url with the queryString
|
||||||
|
*/
|
||||||
|
const urlObject = url.split('?')[0] + '?' + queryString;
|
||||||
|
|
||||||
|
return urlObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get URL
|
||||||
|
*
|
||||||
|
* @param {String} endpoint
|
||||||
|
* @param {Object} params
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
_getUrl(endpoint: string, params: Partial<Record<string, unknown>>, version?: string): string {
|
||||||
|
const api = this._opt.wpAPIPrefix + '/'; // Add prefix to endpoint
|
||||||
|
|
||||||
|
let url = this._opt.url.slice(-1) === '/' ? this._opt.url : this._opt.url + '/';
|
||||||
|
|
||||||
|
url = url + api + (version ?? this._opt.version) + '/' + endpoint;
|
||||||
|
// Add id param to url
|
||||||
|
if (params.id) {
|
||||||
|
url = url + '/' + params.id;
|
||||||
|
delete params.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryParams: string[] = [];
|
||||||
|
for (const key in params) {
|
||||||
|
queryParams.push(
|
||||||
|
`${encodeURIComponent(key)}=${encodeURIComponent(params[key] as string | number | boolean)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParams.length > 0) {
|
||||||
|
url += '?' + queryParams.join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If port is defined, add it to the url
|
||||||
|
*/
|
||||||
|
if (this._opt.port) {
|
||||||
|
const hostname = new Url(url).hostname;
|
||||||
|
url = url.replace(hostname, hostname + ':' + this._opt.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If isHttps is true, normalize the query string
|
||||||
|
*/
|
||||||
|
// if (this._opt.isHttps) {
|
||||||
|
// url = this._normalizeQueryString(url, params);
|
||||||
|
// return url;
|
||||||
|
// }
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Hmac was deprecated fot this version at 16.11.2022
|
||||||
|
* Get OAuth 1.0a since it is mandatory for WCCommerce REST API
|
||||||
|
* You must use OAuth 1.0a "one-legged" authentication to ensure REST API credentials cannot be intercepted by an attacker.
|
||||||
|
* Reference: https://WCcommerce.github.io/WCcommerce-rest-api-docs/#authentication-over-http
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
_getOAuth(): OAuth {
|
||||||
|
const data = {
|
||||||
|
consumer: {
|
||||||
|
key: this._opt.consumerKey,
|
||||||
|
secret: this._opt.consumerSecret
|
||||||
|
},
|
||||||
|
signature_method: 'HMAC-SHA256',
|
||||||
|
hash_function: (base: any, key: any) => {
|
||||||
|
return crypto.createHmac('sha256', key).update(base).digest('base64');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new OAuth(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Axios request
|
||||||
|
* Mount the options to send to axios and send the request.
|
||||||
|
*
|
||||||
|
* @param {String} method
|
||||||
|
* @param {String} endpoint
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Object} params
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
_request<T extends WCRestApiEndpoint, P extends Partial<WCRestApiParams>>(
|
||||||
|
method: WCRestApiMethod,
|
||||||
|
endpoint: T,
|
||||||
|
data?: Record<string, unknown>,
|
||||||
|
params: P = {} as P,
|
||||||
|
version?: string
|
||||||
|
): Promise<WCCommerceResponse<T, P>> {
|
||||||
|
const url = this._getUrl(endpoint, params, version);
|
||||||
|
const header: RawAxiosRequestHeaders = {
|
||||||
|
Accept: 'application/json'
|
||||||
|
};
|
||||||
|
// only set "User-Agent" in node environment
|
||||||
|
// the checking method is identical to upstream axios
|
||||||
|
if (
|
||||||
|
typeof process !== 'undefined' &&
|
||||||
|
Object.prototype.toString.call(process) === '[object process]'
|
||||||
|
) {
|
||||||
|
header['User-Agent'] = 'WCCommerce REST API - TS Client/' + this._opt.classVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options: AxiosRequestConfig = {
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
responseEncoding: this._opt.encoding,
|
||||||
|
timeout: this._opt.timeout,
|
||||||
|
responseType: 'json',
|
||||||
|
headers: { ...header },
|
||||||
|
params: {},
|
||||||
|
data: data ? JSON.stringify(data) : null
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If isHttps is false, add the query string to the params object
|
||||||
|
*/
|
||||||
|
if (this._opt.isHttps) {
|
||||||
|
if (this._opt.queryStringAuth) {
|
||||||
|
options.params = {
|
||||||
|
consumer_key: this._opt.consumerKey,
|
||||||
|
consumer_secret: this._opt.consumerSecret
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
options.auth = {
|
||||||
|
username: this._opt.consumerKey,
|
||||||
|
password: this._opt.consumerSecret
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
options.params = { ...options.params, ...params };
|
||||||
|
} else {
|
||||||
|
options.params = this._getOAuth().authorize({
|
||||||
|
url,
|
||||||
|
method
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.data) {
|
||||||
|
options.headers = {
|
||||||
|
...header,
|
||||||
|
'Content-Type': `application/json; charset=${this._opt.encoding}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow set and override Axios options.
|
||||||
|
options = { ...options, ...this._opt.axiosConfig };
|
||||||
|
|
||||||
|
return axios(options).then((response) => response.data as WCCommerceResponse<T, P>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET requests
|
||||||
|
*
|
||||||
|
* @param {String} endpoint
|
||||||
|
* @param {Object} params
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
get<T extends WCRestApiEndpoint, P extends Partial<WCRestApiParams>>(
|
||||||
|
endpoint: T,
|
||||||
|
params?: P
|
||||||
|
): Promise<WCCommerceResponse<T, P>> {
|
||||||
|
return this._request('GET', endpoint, undefined, params || ({} as P));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST requests
|
||||||
|
*
|
||||||
|
* @param {String} endpoint
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Object} params
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
post<T extends WCRestApiEndpoint>(
|
||||||
|
endpoint: T,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
params?: Partial<WCRestApiParams>
|
||||||
|
): Promise<WCCommerceResponse<T>> {
|
||||||
|
return this._request('POST', endpoint, data, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT requests
|
||||||
|
*
|
||||||
|
* @param {String} endpoint
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Object} params
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
put<T extends WCRestApiEndpoint>(
|
||||||
|
endpoint: T,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
params?: Partial<WCRestApiParams>
|
||||||
|
): Promise<WCCommerceResponse<T>> {
|
||||||
|
return this._request('PUT', endpoint, data, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE requests
|
||||||
|
*
|
||||||
|
* @param {String} endpoint
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Object} params
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
delete<T extends WCRestApiEndpoint>(
|
||||||
|
endpoint: T,
|
||||||
|
data: Pick<WCRestApiParams, 'force'>,
|
||||||
|
params: Pick<WCRestApiParams, 'id'>
|
||||||
|
): Promise<WCCommerceResponse<T, Pick<WCRestApiParams, 'id'>>> {
|
||||||
|
return this._request('DELETE', endpoint, data, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPTIONS requests
|
||||||
|
*
|
||||||
|
* @param {String} endpoint
|
||||||
|
* @param {Object} params
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
options<T extends WCRestApiEndpoint>(
|
||||||
|
endpoint: T,
|
||||||
|
params?: Partial<WCRestApiParams>
|
||||||
|
): Promise<WCCommerceResponse<T>> {
|
||||||
|
return this._request('OPTIONS', endpoint, {}, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options Exception.
|
||||||
|
*/
|
||||||
|
export class OptionsException {
|
||||||
|
public name: 'Options Error';
|
||||||
|
public message: string;
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param {String} message
|
||||||
|
*/
|
||||||
|
constructor(message: string) {
|
||||||
|
this.name = 'Options Error';
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
59
lib/wordpress/models/clientOptions.ts
Normal file
59
lib/wordpress/models/clientOptions.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
export declare type WCRestApiVersion = 'wp/v2';
|
||||||
|
|
||||||
|
export declare type WCRestApiEncoding = 'utf-8' | 'ascii';
|
||||||
|
export declare type WCRestApiMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS';
|
||||||
|
|
||||||
|
export declare type WCRestApiEndpoint = 'posts' | string;
|
||||||
|
|
||||||
|
export declare type IWCRestApiQuery = Record<string, unknown>;
|
||||||
|
|
||||||
|
export type IWCCredentials = {
|
||||||
|
/* Your API consumer key */
|
||||||
|
consumerKey: string;
|
||||||
|
/* Your API consumer secret */
|
||||||
|
consumerSecret: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WCCommerceRestApiTypeFunctions = {
|
||||||
|
get: <T>(endpoint: string, params?: T) => Promise<any>;
|
||||||
|
post: <T>(endpoint: string, params?: T) => Promise<any>;
|
||||||
|
put: <T>(endpoint: string, params?: T) => Promise<any>;
|
||||||
|
delete: <T>(endpoint: string, params?: T) => Promise<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IWCRestApiOptions<T> extends IWCCredentials {
|
||||||
|
/* Your Store URL, example: http://WC.dev/ */
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
/* Custom WP REST API URL prefix, used to support custom prefixes created with the `rest_url_prefix filter` */
|
||||||
|
wpAPIPrefix?: string;
|
||||||
|
|
||||||
|
/* API version, default is `v3` */
|
||||||
|
version?: WCRestApiVersion;
|
||||||
|
|
||||||
|
/* Encoding, default is 'utf-8' */
|
||||||
|
encoding?: WCRestApiEncoding;
|
||||||
|
|
||||||
|
/* When `true` and using under HTTPS force Basic Authentication as query string, default is `false` */
|
||||||
|
queryStringAuth?: boolean;
|
||||||
|
|
||||||
|
/* Provide support for URLs with ports, eg: `8080` */
|
||||||
|
port?: number;
|
||||||
|
|
||||||
|
/* Provide support for custom timeout, eg: `5000` */
|
||||||
|
timeout?: number;
|
||||||
|
|
||||||
|
/* Define the custom Axios config, also override this library options */
|
||||||
|
axiosConfig?: T;
|
||||||
|
|
||||||
|
/* Version of this library */
|
||||||
|
classVersion?: string;
|
||||||
|
|
||||||
|
/* Https or Http */
|
||||||
|
isHttps?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DELETE {
|
||||||
|
id: number | string;
|
||||||
|
force?: boolean | string;
|
||||||
|
}
|
11
lib/wordpress/wordpress.ts
Normal file
11
lib/wordpress/wordpress.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import WCCommerceRestApi, { WCRestApiOptions } from './models/client';
|
||||||
|
|
||||||
|
const option: WCRestApiOptions = {
|
||||||
|
url: process.env.WOOCOMMERCE_URL ?? 'http://wordpress.localhost',
|
||||||
|
consumerKey: process.env.WOOCOMMERCE_CONSUMER_KEY ?? '',
|
||||||
|
consumerSecret: process.env.WOOCOMMERCE_CONSUMER_SECRET ?? '',
|
||||||
|
isHttps: false,
|
||||||
|
version: 'wp/v2',
|
||||||
|
queryStringAuth: false // Force Basic Authentication as query string true and using under
|
||||||
|
};
|
||||||
|
export const wordpress = new WCCommerceRestApi(option);
|
@ -2,12 +2,10 @@ import { getToken } from 'next-auth/jwt';
|
|||||||
import type { NextRequest } from 'next/server';
|
import type { NextRequest } from 'next/server';
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
// Lista delle pagine protette
|
|
||||||
const protectedRoutes = ['/profile'];
|
const protectedRoutes = ['/profile'];
|
||||||
|
|
||||||
export async function middleware(req: NextRequest) {
|
export async function middleware(req: NextRequest) {
|
||||||
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
|
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
|
||||||
console.log('token', token);
|
|
||||||
const isProtectedRoute = protectedRoutes.some((route) => req.nextUrl.pathname.startsWith(route));
|
const isProtectedRoute = protectedRoutes.some((route) => req.nextUrl.pathname.startsWith(route));
|
||||||
if (isProtectedRoute && !token) {
|
if (isProtectedRoute && !token) {
|
||||||
const loginUrl = new URL('/login', req.url);
|
const loginUrl = new URL('/login', req.url);
|
||||||
@ -19,9 +17,13 @@ export async function middleware(req: NextRequest) {
|
|||||||
return NextResponse.redirect(profileUrl);
|
return NextResponse.redirect(profileUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.nextUrl.pathname.endsWith('/product')) {
|
||||||
|
return NextResponse.redirect(new URL('/', req.url));
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ['/login', '/profile', '/profile/:path*']
|
matcher: ['/login', '/profile', '/profile/:path*', '/product']
|
||||||
};
|
};
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"paths": {
|
||||||
|
"@": ["/"] // enables us to use @components/MyComponent
|
||||||
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "next"
|
"name": "next"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user