refactor: change layout profile and home page

This commit is contained in:
paolosantarsiero 2025-01-03 03:05:01 +01:00
parent 19802716d4
commit 6775f7363b
18 changed files with 716 additions and 138 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) => (

View 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>
)}
</>
);
}

View File

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

View File

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

View File

@ -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 = {

View 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;
}
}

View 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;
}

View 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);

View File

@ -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']
}; };

View File

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