mirror of
https://github.com/vercel/commerce.git
synced 2025-06-28 01:11:24 +00:00
feat: add profile and orders customer
This commit is contained in:
parent
ce96340833
commit
5880b80676
@ -7,7 +7,6 @@ export async function generateMetadata(props: {
|
||||
}): Promise<Metadata> {
|
||||
const params = await props.params;
|
||||
|
||||
|
||||
return {
|
||||
title: '',
|
||||
description: '',
|
||||
|
@ -1,50 +1,50 @@
|
||||
import { woocommerce } from "lib/woocomerce/woocommerce";
|
||||
import { NextAuthOptions, Session, User } from "next-auth";
|
||||
import { JWT } from "next-auth/jwt";
|
||||
import NextAuth from "next-auth/next";
|
||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||
import { NextAuthOptions, Session, User } from 'next-auth';
|
||||
import { JWT } from 'next-auth/jwt';
|
||||
import NextAuth from 'next-auth/next';
|
||||
import CredentialsProvider from 'next-auth/providers/credentials';
|
||||
|
||||
export const authOptions = {
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
session: {
|
||||
strategy: "jwt", // Use JWT for session handling
|
||||
strategy: 'jwt' // Use JWT for session handling
|
||||
},
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: 'woocommerce',
|
||||
credentials: {
|
||||
username: { label: 'Username', type: 'text', placeholder: 'Username' },
|
||||
password: { label: 'Password', type: 'password', placeholder: 'Password' },
|
||||
name: 'woocommerce',
|
||||
credentials: {
|
||||
username: { label: 'Username', type: 'text', placeholder: 'Username' },
|
||||
password: { label: 'Password', type: 'password', placeholder: 'Password' }
|
||||
},
|
||||
async authorize(credentials, req) {
|
||||
if (!credentials?.username || !credentials?.password) {
|
||||
return null;
|
||||
}
|
||||
const user = await woocommerce.login(credentials.username, credentials.password);
|
||||
// If no error and we have user data, return it
|
||||
if (user) {
|
||||
return user
|
||||
}
|
||||
// Return null if user data could not be retrieved
|
||||
return null
|
||||
async authorize(credentials, req) {
|
||||
if (!credentials?.username || !credentials?.password) {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }: { token: JWT, user: User }) {
|
||||
const user = await woocommerce.login(credentials.username, credentials.password);
|
||||
// If no error and we have user data, return it
|
||||
if (user) {
|
||||
console.debug('Set token user', user);
|
||||
token.user = user;
|
||||
return user;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }: {session: Session, token: JWT}) {
|
||||
console.debug('Set session token', token.user);
|
||||
session.user = token.user;
|
||||
return session;
|
||||
},
|
||||
},
|
||||
// Return null if user data could not be retrieved
|
||||
return null;
|
||||
}
|
||||
})
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }: { token: JWT; user: User }) {
|
||||
if (user) {
|
||||
console.debug('Set token user', user);
|
||||
token.user = user;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }: { session: Session; token: JWT }) {
|
||||
console.debug('Set session token', token.user);
|
||||
session.user = token.user;
|
||||
return session;
|
||||
}
|
||||
}
|
||||
} satisfies NextAuthOptions;
|
||||
|
||||
const handler = NextAuth(authOptions)
|
||||
const handler = NextAuth(authOptions);
|
||||
export { handler as GET, handler as POST };
|
||||
|
@ -9,7 +9,7 @@ export async function GET(req: NextRequest) {
|
||||
if (session?.user?.token) {
|
||||
storeApi._setAuthorizationToken(session.user.token);
|
||||
} else {
|
||||
storeApi._setAuthorizationToken('');
|
||||
storeApi._setAuthorizationToken('');
|
||||
}
|
||||
const cart = await storeApi.getCart();
|
||||
return NextResponse.json(cart, { status: 200 });
|
||||
@ -32,11 +32,11 @@ export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const { key, quantity } = await req.json();
|
||||
if (quantity > 0) {
|
||||
const cart = await storeApi.updateItem({ key, quantity });
|
||||
return NextResponse.json(cart, { status: 200 });
|
||||
const cart = await storeApi.updateItem({ key, quantity });
|
||||
return NextResponse.json(cart, { status: 200 });
|
||||
} else {
|
||||
const cart = await storeApi.removeFromCart({ key });
|
||||
return NextResponse.json(cart, { status: 200 });
|
||||
const cart = await storeApi.removeFromCart({ key });
|
||||
return NextResponse.json(cart, { status: 200 });
|
||||
}
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to update cart item' }, { status: 500 });
|
||||
|
@ -1,16 +1,14 @@
|
||||
|
||||
import { ThreeItemGridItem } from 'components/grid/three-items';
|
||||
import { Product } from 'lib/woocomerce/models/product';
|
||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||
|
||||
|
||||
export default async function ProductPage(props: { params: Promise<{ name: string }> }) {
|
||||
const params = await props.params;
|
||||
const products: Product[] = (await (woocommerce.get('products', { category: params.name })));
|
||||
const products: Product[] = await woocommerce.get('products', { category: params.name });
|
||||
|
||||
return (
|
||||
<section className="mx-auto grid max-w-screen-2xl gap-4 px-4 pb-4 md:grid-cols-6 md:grid-rows-2 lg:max-h-[calc(100vh-200px)]">
|
||||
{products.map((product, index) => (
|
||||
{products.map((product, index) => (
|
||||
<ThreeItemGridItem key={product.id} size={index === 0 ? 'full' : 'half'} item={product} />
|
||||
))}
|
||||
</section>
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import { CartProvider } from 'components/cart/cart-context';
|
||||
import { Navbar } from 'components/layout/navbar';
|
||||
import { NextAuthProvider } from 'components/next-session-provider';
|
||||
@ -44,15 +43,15 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
||||
<html lang="en" className={GeistSans.variable}>
|
||||
<body className="bg-neutral-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
|
||||
<NextAuthProvider>
|
||||
<CartProvider value={cart}>
|
||||
<Navbar />
|
||||
<CartProvider value={cart}>
|
||||
<Navbar />
|
||||
<main>
|
||||
{children}
|
||||
<Toaster closeButton />
|
||||
<WelcomeToast />
|
||||
</main>
|
||||
</CartProvider>
|
||||
</NextAuthProvider>
|
||||
</NextAuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
71
app/login/page.tsx
Normal file
71
app/login/page.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
'use client';
|
||||
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function LoginPage() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const { replace } = useRouter();
|
||||
|
||||
const handleLogin = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
await signIn('credentials', { username, password, redirect: false });
|
||||
replace('/');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="mx-auto mt-4 grid max-w-screen-2xl justify-center gap-4 px-4 pb-4">
|
||||
<h1 className="text-2xl font-bold">Login</h1>
|
||||
<div className="flex h-screen justify-center">
|
||||
<form onSubmit={handleLogin}>
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="username"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
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"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
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"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type="submit"
|
||||
className="flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-3 text-lg font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -12,7 +12,7 @@ export const metadata = {
|
||||
export default async function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<ThreeItemGrid/>
|
||||
<ThreeItemGrid />
|
||||
<Carousel />
|
||||
<Footer />
|
||||
</>
|
||||
|
@ -15,7 +15,9 @@ export async function generateMetadata(props: {
|
||||
params: Promise<{ name: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const params = await props.params;
|
||||
const product: Product | undefined = (await (woocommerce.get('products', { slug: params.name })))?.[0];
|
||||
const product: Product | undefined = (
|
||||
await woocommerce.get('products', { slug: params.name })
|
||||
)?.[0];
|
||||
|
||||
if (!product) return notFound();
|
||||
|
||||
@ -31,13 +33,15 @@ export async function generateMetadata(props: {
|
||||
index: indexable,
|
||||
follow: indexable
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ProductPage(props: { params: Promise<{ name: string }> }) {
|
||||
const params = await props.params;
|
||||
const product: Product | undefined = (await (woocommerce.get('products', { slug: params.name })))?.[0];
|
||||
const product: Product | undefined = (
|
||||
await woocommerce.get('products', { slug: params.name })
|
||||
)?.[0];
|
||||
|
||||
if (!product) return notFound();
|
||||
|
||||
@ -49,9 +53,8 @@ export default async function ProductPage(props: { params: Promise<{ name: strin
|
||||
image: product.images?.[0]?.src,
|
||||
offers: {
|
||||
'@type': 'AggregateOffer',
|
||||
availability: product.stock_quantity > 0
|
||||
? 'https://schema.org/InStock'
|
||||
: 'https://schema.org/OutOfStock',
|
||||
availability:
|
||||
product.stock_quantity > 0 ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
|
||||
priceCurrency: product.price,
|
||||
highPrice: product.max_price,
|
||||
lowPrice: product.min_price
|
||||
|
85
app/profile/orders/[id]/page.tsx
Normal file
85
app/profile/orders/[id]/page.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { authOptions } from 'app/api/auth/[...nextauth]/route';
|
||||
import Price from 'components/price';
|
||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default async function OrderPage(props: { params: Promise<{ id: number }> }) {
|
||||
const params = await props.params;
|
||||
const data = await getServerSession(authOptions);
|
||||
try {
|
||||
const order = await woocommerce.get('orders', { id: params.id });
|
||||
console.log(order);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
const order = await woocommerce.get('orders', { id: params.id });
|
||||
|
||||
return (
|
||||
<section className="mx-auto mt-4 grid max-w-screen-2xl justify-center gap-4 px-4 pb-4">
|
||||
<h1 className="text-2xl font-bold">Order</h1>
|
||||
<div className="flex h-screen 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={order.order_key}
|
||||
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>
|
||||
{order.line_items.map((item, i) => (
|
||||
<li
|
||||
key={i}
|
||||
className="flex w-full flex-col border-b border-neutral-300 dark:border-neutral-700"
|
||||
>
|
||||
<div className="relative flex w-full flex-row justify-between px-1 py-4">
|
||||
<div className="flex flex-row">
|
||||
<div className="relative h-16 w-16 overflow-hidden rounded-md border border-neutral-300 bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800">
|
||||
<Image
|
||||
className="h-full w-full object-cover"
|
||||
width={64}
|
||||
height={64}
|
||||
alt={item.name ?? ''}
|
||||
src={item.image?.src || ''}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col text-base">
|
||||
<span className="leading-tight">{item.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-16 flex-col justify-between">
|
||||
<Price
|
||||
className="flex justify-end space-y-2 text-right text-sm"
|
||||
amount={(item.price ?? 0).toString()}
|
||||
currencyCode={order.currency}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="total"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Total
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="total"
|
||||
value={order.total}
|
||||
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>
|
||||
</section>
|
||||
);
|
||||
}
|
85
app/profile/orders/page.tsx
Normal file
85
app/profile/orders/page.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { authOptions } from 'app/api/auth/[...nextauth]/route';
|
||||
import Price from 'components/price';
|
||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default async function OrdersPage() {
|
||||
const data = await getServerSession(authOptions);
|
||||
const orders = await woocommerce.get('orders');
|
||||
|
||||
return (
|
||||
<section className="mx-auto mt-4 grid max-w-screen-2xl justify-center gap-4 px-4 pb-4">
|
||||
<h1 className="text-2xl font-bold">Orders</h1>
|
||||
{orders.map((order) => (
|
||||
<Link
|
||||
href={`/profile/orders/${order.id}`}
|
||||
key={order.id}
|
||||
className="flex h-screen 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={order.order_key}
|
||||
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>
|
||||
{order.line_items.map((item, i) => (
|
||||
<li
|
||||
key={i}
|
||||
className="flex w-full flex-col border-b border-neutral-300 dark:border-neutral-700"
|
||||
>
|
||||
<div className="relative flex w-full flex-row justify-between px-1 py-4">
|
||||
<div className="flex flex-row">
|
||||
<div className="relative h-16 w-16 overflow-hidden rounded-md border border-neutral-300 bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800">
|
||||
<Image
|
||||
className="h-full w-full object-cover"
|
||||
width={64}
|
||||
height={64}
|
||||
alt={item.name ?? ''}
|
||||
src={item.image?.src || ''}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col text-base">
|
||||
<span className="leading-tight">{item.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-16 flex-col justify-between">
|
||||
<Price
|
||||
className="flex justify-end space-y-2 text-right text-sm"
|
||||
amount={(item.price ?? 0).toString()}
|
||||
currencyCode={order.currency}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="total"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Total
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="total"
|
||||
value={order.total}
|
||||
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>
|
||||
</Link>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
156
app/profile/page.tsx
Normal file
156
app/profile/page.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import { authOptions } from 'app/api/auth/[...nextauth]/route';
|
||||
import LogoutButton from 'components/button/logout';
|
||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export default async function LoginPage() {
|
||||
const data = await getServerSession(authOptions);
|
||||
if (!data?.user) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const customer = await woocommerce.get('customers', { id: data.user.store_id });
|
||||
|
||||
return (
|
||||
<section className="mx-auto mt-4 grid max-w-screen-2xl justify-center gap-4 px-4 pb-4">
|
||||
<h1 className="text-2xl font-bold">Profile</h1>
|
||||
<h2 className="text-2xl font-bold">Info</h2>
|
||||
<img src={customer.avatar_url} alt="avatar" className="h-11 w-11" />
|
||||
<div className="flex h-screen 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"
|
||||
>
|
||||
Name
|
||||
</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
|
||||
/>
|
||||
<h2 className="mt-2 text-2xl font-bold">Shipping info</h2>
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="address"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Address
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="address"
|
||||
value={customer.shipping.address_1}
|
||||
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="city"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
City
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="city"
|
||||
value={customer.shipping.city}
|
||||
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="state"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
State
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="state"
|
||||
value={customer.shipping.state}
|
||||
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="postcode"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Postcode
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="postcode"
|
||||
value={customer.shipping.postcode}
|
||||
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="country"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Country
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="country"
|
||||
value={customer.shipping.country}
|
||||
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">
|
||||
<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>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -3,6 +3,5 @@ import OpengraphImage from 'components/opengraph-image';
|
||||
export const runtime = 'edge';
|
||||
|
||||
export default async function Image({ params }: { params: { collection: string } }) {
|
||||
|
||||
return await OpengraphImage({ title: '' });
|
||||
}
|
||||
|
@ -7,8 +7,7 @@ export default function SearchLayout({ children }: { children: React.ReactNode }
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto flex max-w-screen-2xl flex-col gap-8 px-4 pb-4 text-black md:flex-row dark:text-white">
|
||||
<div className="order-first w-full flex-none md:max-w-[125px]">
|
||||
</div>
|
||||
<div className="order-first w-full flex-none md:max-w-[125px]"></div>
|
||||
<div className="order-last min-h-screen w-full md:order-none">
|
||||
<ChildrenWrapper>{children}</ChildrenWrapper>
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@ export default async function SearchPage(props: {
|
||||
const { sort, q: searchValue } = searchParams as { [key: string]: string };
|
||||
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
|
||||
|
||||
const products = (await (woocommerce.get('products', { search: searchValue, orderby: sortKey })));
|
||||
const products = await woocommerce.get('products', { search: searchValue, orderby: sortKey });
|
||||
const resultsText = products.length > 1 ? 'results' : 'result';
|
||||
|
||||
return (
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import { validateEnvironmentVariables } from 'lib/utils';
|
||||
import { MetadataRoute } from 'next';
|
||||
|
||||
|
14
components/button/logout.tsx
Normal file
14
components/button/logout.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
'use client';
|
||||
import { signOut } from 'next-auth/react';
|
||||
|
||||
export default function LogoutButton() {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="w-full rounded-md bg-indigo-500 p-3 text-white"
|
||||
onClick={() => signOut({ callbackUrl: '/' })}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
);
|
||||
}
|
@ -5,7 +5,7 @@ import { GridTileImage } from './grid/tile';
|
||||
|
||||
export async function Carousel() {
|
||||
// Collections that start with `hidden-*` are hidden from the search page.
|
||||
const products: Product[] = (await (woocommerce.get('products')));
|
||||
const products: Product[] = await woocommerce.get('products');
|
||||
|
||||
if (!products?.length) return null;
|
||||
|
||||
@ -20,7 +20,7 @@ export async function Carousel() {
|
||||
key={`${product.id}${i}`}
|
||||
className="relative aspect-square h-[30vh] max-h-[275px] w-2/3 max-w-[475px] flex-none md:w-1/3"
|
||||
>
|
||||
<Link href={`/product/${product.id}`} className="relative h-full w-full">
|
||||
<Link href={`/product/${product.slug}`} className="relative h-full w-full">
|
||||
<GridTileImage
|
||||
alt={product.name}
|
||||
label={{
|
||||
|
@ -5,17 +5,12 @@ import clsx from 'clsx';
|
||||
import { Product } from 'lib/woocomerce/models/product';
|
||||
import { useCart } from './cart-context';
|
||||
|
||||
function SubmitButton({
|
||||
}: {
|
||||
}) {
|
||||
function SubmitButton({}: {}) {
|
||||
const buttonClasses =
|
||||
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white';
|
||||
|
||||
return (
|
||||
<button
|
||||
aria-label="Please select an option"
|
||||
className={clsx(buttonClasses)}
|
||||
>
|
||||
<button aria-label="Please select an option" className={clsx(buttonClasses)}>
|
||||
<div className="absolute left-0 ml-4">
|
||||
<PlusIcon className="h-5" />
|
||||
</div>
|
||||
@ -31,7 +26,12 @@ export function AddToCart({ product }: { product: Product }) {
|
||||
<form
|
||||
action={async () => {
|
||||
try {
|
||||
const cart = await (await fetch('/api/cart', {method: 'POST', body: JSON.stringify({ id: product.id, quantity: 1, variation: [] })},)).json();
|
||||
const cart = await (
|
||||
await fetch('/api/cart', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id: product.id, quantity: 1, variation: [] })
|
||||
})
|
||||
).json();
|
||||
setNewCart(cart);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -5,8 +5,12 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
type UpdateType = 'plus' | 'minus' | 'delete';
|
||||
|
||||
type UpdatePayload = { key: string | number; quantity: number;};
|
||||
type AddPayload = { id: string | number; quantity: number; variation: { attribute: string; value: string }[] };
|
||||
type UpdatePayload = { key: string | number; quantity: number };
|
||||
type AddPayload = {
|
||||
id: string | number;
|
||||
quantity: number;
|
||||
variation: { attribute: string; value: string }[];
|
||||
};
|
||||
type RemovePayload = { key: string | number };
|
||||
|
||||
type CartContextType = {
|
||||
@ -16,17 +20,11 @@ type CartContextType = {
|
||||
|
||||
const CartContext = createContext<CartContextType | undefined>(undefined);
|
||||
|
||||
export function CartProvider({
|
||||
value,
|
||||
children,
|
||||
}: {
|
||||
value: Cart;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
export function CartProvider({ value, children }: { value: Cart; children: React.ReactNode }) {
|
||||
const [cart, setCart] = useState<Cart | undefined>(value);
|
||||
const setNewCart = (cart: Cart) => {
|
||||
setCart(cart);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setCart(value);
|
||||
@ -36,7 +34,7 @@ export function CartProvider({
|
||||
<CartContext.Provider
|
||||
value={{
|
||||
cart,
|
||||
setNewCart,
|
||||
setNewCart
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -4,18 +4,16 @@ import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { CartItem } from 'lib/woocomerce/models/cart';
|
||||
import { useCart } from './cart-context';
|
||||
|
||||
export function DeleteItemButton({
|
||||
item,
|
||||
}: {
|
||||
item: CartItem;
|
||||
}) {
|
||||
const {setNewCart} = useCart();
|
||||
export function DeleteItemButton({ item }: { item: CartItem }) {
|
||||
const { setNewCart } = useCart();
|
||||
|
||||
return (
|
||||
<form
|
||||
action={async () => {
|
||||
try {
|
||||
const cart = await (await fetch('/api/cart', {method: 'DELETE', body: JSON.stringify({ key: item.key })})).json();
|
||||
const cart = await (
|
||||
await fetch('/api/cart', { method: 'DELETE', body: JSON.stringify({ key: item.key }) })
|
||||
).json();
|
||||
setNewCart(cart);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -26,29 +26,24 @@ function SubmitButton({ type }: { type: 'plus' | 'minus' }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function EditItemQuantityButton({
|
||||
item,
|
||||
type,
|
||||
}: {
|
||||
item: CartItem;
|
||||
type: 'plus' | 'minus';
|
||||
}) {
|
||||
const {setNewCart} = useCart();
|
||||
export function EditItemQuantityButton({ item, type }: { item: CartItem; type: 'plus' | 'minus' }) {
|
||||
const { setNewCart } = useCart();
|
||||
const payload = {
|
||||
key: item.key,
|
||||
quantity: type === 'plus' ? item.quantity + 1 : item.quantity - 1,
|
||||
quantity: type === 'plus' ? item.quantity + 1 : item.quantity - 1
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
action={async () => {
|
||||
try {
|
||||
const cart = await (await fetch('/api/cart', {method: 'PUT', body: JSON.stringify(payload)})).json();
|
||||
const cart = await (
|
||||
await fetch('/api/cart', { method: 'PUT', body: JSON.stringify(payload) })
|
||||
).json();
|
||||
setNewCart(cart);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<SubmitButton type={type} />
|
||||
|
@ -15,29 +15,35 @@ import { DeleteItemButton } from './delete-item-button';
|
||||
import { EditItemQuantityButton } from './edit-item-quantity-button';
|
||||
import OpenCart from './open-cart';
|
||||
|
||||
|
||||
export default function CartModal() {
|
||||
const {cart, setNewCart} = useCart();
|
||||
const { cart, setNewCart } = useCart();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [userIsLoggedIn, setUserIsLoggedIn] = useState(false);
|
||||
const openCart = () => setIsOpen(true);
|
||||
const closeCart = () => setIsOpen(false);
|
||||
const {data} = useSession();
|
||||
const { data } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.user.token) {
|
||||
const fetchCart = async () => {
|
||||
const cart = await (await fetch('/api/cart')).json();
|
||||
setNewCart(cart);
|
||||
};
|
||||
fetchCart();
|
||||
}
|
||||
if (data?.user.token) {
|
||||
const fetchCart = async () => {
|
||||
const cart = await (await fetch('/api/cart')).json();
|
||||
setNewCart(cart);
|
||||
};
|
||||
fetchCart();
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button aria-label="Open cart" onClick={openCart}>
|
||||
<OpenCart quantity={cart?.items?.map((item) => item.quantity).reduce((a, b) => a + b, 0).toString() ?? '0'} />
|
||||
<OpenCart
|
||||
quantity={
|
||||
cart?.items
|
||||
?.map((item) => item.quantity)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
.toString() ?? '0'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
<Transition show={isOpen}>
|
||||
<Dialog onClose={closeCart} className="relative z-50">
|
||||
@ -77,69 +83,58 @@ export default function CartModal() {
|
||||
) : (
|
||||
<div className="flex h-full flex-col justify-between overflow-hidden p-1">
|
||||
<ul className="flex-grow overflow-auto py-4">
|
||||
{cart.items?.length && cart.items
|
||||
.sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
)
|
||||
.map((item, i) => {
|
||||
return (
|
||||
<li
|
||||
key={i}
|
||||
className="flex w-full flex-col border-b border-neutral-300 dark:border-neutral-700"
|
||||
>
|
||||
<div className="relative flex w-full flex-row justify-between px-1 py-4">
|
||||
<div className="absolute z-40 -ml-1 -mt-2">
|
||||
<DeleteItemButton item={item} />
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="relative h-16 w-16 overflow-hidden rounded-md border border-neutral-300 bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800">
|
||||
<Image
|
||||
className="h-full w-full object-cover"
|
||||
width={64}
|
||||
height={64}
|
||||
alt={
|
||||
item.name
|
||||
}
|
||||
src={item.images?.[0]?.src || ''}
|
||||
/>
|
||||
{cart.items?.length &&
|
||||
cart.items
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((item, i) => {
|
||||
return (
|
||||
<li
|
||||
key={i}
|
||||
className="flex w-full flex-col border-b border-neutral-300 dark:border-neutral-700"
|
||||
>
|
||||
<div className="relative flex w-full flex-row justify-between px-1 py-4">
|
||||
<div className="absolute z-40 -ml-1 -mt-2">
|
||||
<DeleteItemButton item={item} />
|
||||
</div>
|
||||
<Link
|
||||
href={''}
|
||||
onClick={closeCart}
|
||||
className="z-30 ml-2 flex flex-row space-x-4"
|
||||
>
|
||||
<div className="flex flex-1 flex-col text-base">
|
||||
<span className="leading-tight">
|
||||
{item.name}
|
||||
</span>
|
||||
<div className="flex flex-row">
|
||||
<div className="relative h-16 w-16 overflow-hidden rounded-md border border-neutral-300 bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800">
|
||||
<Image
|
||||
className="h-full w-full object-cover"
|
||||
width={64}
|
||||
height={64}
|
||||
alt={item.name}
|
||||
src={item.images?.[0]?.src || ''}
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex h-16 flex-col justify-between">
|
||||
<Price
|
||||
className="flex justify-end space-y-2 text-right text-sm"
|
||||
amount={item.prices?.price}
|
||||
needSplit
|
||||
currencyCode={item.prices.currency_code}
|
||||
/>
|
||||
<div className="ml-auto flex h-9 flex-row items-center rounded-full border border-neutral-200 dark:border-neutral-700">
|
||||
<EditItemQuantityButton
|
||||
item={item}
|
||||
type="minus"
|
||||
/>
|
||||
<p className="w-6 text-center">
|
||||
<span className="w-full text-sm">{item.quantity}</span>
|
||||
</p>
|
||||
<EditItemQuantityButton
|
||||
item={item}
|
||||
type="plus"
|
||||
<Link
|
||||
href={''}
|
||||
onClick={closeCart}
|
||||
className="z-30 ml-2 flex flex-row space-x-4"
|
||||
>
|
||||
<div className="flex flex-1 flex-col text-base">
|
||||
<span className="leading-tight">{item.name}</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex h-16 flex-col justify-between">
|
||||
<Price
|
||||
className="flex justify-end space-y-2 text-right text-sm"
|
||||
amount={item.prices?.price}
|
||||
needSplit
|
||||
currencyCode={item.prices.currency_code}
|
||||
/>
|
||||
<div className="ml-auto flex h-9 flex-row items-center rounded-full border border-neutral-200 dark:border-neutral-700">
|
||||
<EditItemQuantityButton item={item} type="minus" />
|
||||
<p className="w-6 text-center">
|
||||
<span className="w-full text-sm">{item.quantity}</span>
|
||||
</p>
|
||||
<EditItemQuantityButton item={item} type="plus" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<div className="py-4 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 dark:border-neutral-700">
|
||||
|
@ -43,8 +43,7 @@ export function ThreeItemGridItem({
|
||||
|
||||
export async function ThreeItemGrid() {
|
||||
// Collections that start with `hidden-*` are hidden from the search page.
|
||||
const products: Product[] = (await woocommerce.get('products'));
|
||||
|
||||
const products: Product[] = await woocommerce.get('products');
|
||||
|
||||
const [firstProduct, secondProduct, thirdProduct] = products;
|
||||
|
||||
|
16
components/icons/UserIcon.tsx
Normal file
16
components/icons/UserIcon.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { UserCircleIcon } from '@heroicons/react/24/outline';
|
||||
import { authOptions } from 'app/api/auth/[...nextauth]/route';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default async function UserIcon() {
|
||||
const isAuthenticated = (await getServerSession(authOptions))?.user?.token;
|
||||
|
||||
return (
|
||||
<Link href={!isAuthenticated ? '/login' : '/profile'} className="ms-2" aria-label="login">
|
||||
<div className="relative flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors dark:border-neutral-700 dark:text-white">
|
||||
<UserCircleIcon className="h-4 transition-all ease-in-out hover:scale-110" />
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import FooterMenu from 'components/layout/footer-menu';
|
||||
import LogoSquare from 'components/logo-square';
|
||||
import Link from 'next/link';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
const { COMPANY_NAME, SITE_NAME } = process.env;
|
||||
@ -20,7 +19,7 @@ export default async function Footer() {
|
||||
{
|
||||
title: 'Shop',
|
||||
path: '/shop'
|
||||
},
|
||||
}
|
||||
] as Menu[];
|
||||
const currentYear = new Date().getFullYear();
|
||||
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
|
||||
|
@ -1,6 +1,5 @@
|
||||
|
||||
import CartModal from 'components/cart/modal';
|
||||
import LoginModal from 'components/login/modal';
|
||||
import UserIcon from 'components/icons/UserIcon';
|
||||
import LogoSquare from 'components/logo-square';
|
||||
import { Category } from 'lib/woocomerce/models/base';
|
||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||
@ -18,7 +17,7 @@ type Menu = {
|
||||
};
|
||||
|
||||
export async function Navbar() {
|
||||
const categories: Category[] = (await (woocommerce.get('products/categories')));
|
||||
const categories: Category[] = await woocommerce.get('products/categories');
|
||||
const menu = [
|
||||
{
|
||||
title: 'Home',
|
||||
@ -71,8 +70,8 @@ export async function Navbar() {
|
||||
</Suspense>
|
||||
</div>
|
||||
<div className="flex justify-end md:w-1/3">
|
||||
<LoginModal />
|
||||
<CartModal />
|
||||
<UserIcon />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -10,7 +10,7 @@ export default function ProductGridItems({ products }: { products: Product[] })
|
||||
<Grid.Item key={product.id} className="animate-fadeIn">
|
||||
<Link
|
||||
className="relative inline-block h-full w-full"
|
||||
href={`/product/${product.id}`}
|
||||
href={`/product/${product.slug}`}
|
||||
prefetch={true}
|
||||
>
|
||||
<GridTileImage
|
||||
|
@ -1,11 +1,9 @@
|
||||
import clsx from 'clsx';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { getCollections } from 'lib/shopify';
|
||||
import FilterList from './filter';
|
||||
import FilterList, { ListItem } from './filter';
|
||||
|
||||
async function CollectionList() {
|
||||
const collections = await getCollections();
|
||||
const collections: ListItem[] = [];
|
||||
return <FilterList list={collections} title="Collections" />;
|
||||
}
|
||||
|
||||
|
@ -1,133 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { UserCircleIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { useCart } from 'components/cart/cart-context';
|
||||
import { signIn, signOut, useSession } from 'next-auth/react';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
|
||||
export default function LoginModal() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLogged, setIsLogged] = useState(false);
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const openLogin = () => setIsOpen(true);
|
||||
const closeLogin = () => setIsOpen(false);
|
||||
|
||||
const {setNewCart} = useCart();
|
||||
const {data} = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.user.token) {
|
||||
setIsLogged(true);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const handleLogin = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const res = await signIn('credentials', {username, password, redirect: false});
|
||||
const cart = await (await fetch('/api/cart')).json();
|
||||
setNewCart(cart);
|
||||
closeLogin();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className="me-2" aria-label="Open cart" onClick={openLogin}>
|
||||
<div className="relative flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors dark:border-neutral-700 dark:text-white">
|
||||
<UserCircleIcon className="h-4 transition-all ease-in-out hover:scale-110" />
|
||||
</div>
|
||||
</button>
|
||||
<Transition show={isOpen}>
|
||||
<Dialog onClose={closeLogin} className="relative z-50">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="transition-all ease-in-out duration-300"
|
||||
enterFrom="opacity-0 backdrop-blur-none"
|
||||
enterTo="opacity-100 backdrop-blur-[.5px]"
|
||||
leave="transition-all ease-in-out duration-200"
|
||||
leaveFrom="opacity-100 backdrop-blur-[.5px]"
|
||||
leaveTo="opacity-0 backdrop-blur-none"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
||||
</Transition.Child>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="transition-all ease-in-out duration-300"
|
||||
enterFrom="translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="transition-all ease-in-out duration-200"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="translate-x-full"
|
||||
>
|
||||
<Dialog.Panel className="fixed bottom-0 right-0 top-0 flex h-full w-full flex-col border-l border-neutral-200 bg-white/80 p-6 text-black backdrop-blur-xl md:w-[390px] dark:border-neutral-700 dark:bg-black/80 dark:text-white">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-lg font-semibold">Login</p>
|
||||
<button aria-label="Close cart" onClick={closeLogin}>
|
||||
<div className="relative flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors dark:border-neutral-700 dark:text-white">
|
||||
<XMarkIcon className="h-6 transition-all ease-in-out hover:scale-110" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{!isLogged ? ( <form onSubmit={handleLogin}>
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="username"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
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"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
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"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type="submit"
|
||||
className="flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-3 text-lg font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</form>) : (
|
||||
<div className="mt-20 flex w-full flex-col items-center justify-center overflow-hidden">
|
||||
<p className="mt-6 text-center text-2xl font-bold">You are logged in.</p>
|
||||
<button className="mt-6 flex" onClick={() => signOut()}>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { SessionProvider } from 'next-auth/react';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
|
@ -5,7 +5,7 @@ import { GridTileImage } from 'components/grid/tile';
|
||||
import { useProduct, useUpdateURL } from 'components/product/product-context';
|
||||
import Image from 'next/image';
|
||||
|
||||
export function Gallery({ images }: { images: { id: number, src: string; altText: string }[] }) {
|
||||
export function Gallery({ images }: { images: { id: number; src: string; altText: string }[] }) {
|
||||
const { state, updateImage } = useProduct();
|
||||
const updateURL = useUpdateURL();
|
||||
const imageIndex = state.image ? parseInt(state.image) : 0;
|
||||
@ -60,7 +60,7 @@ export function Gallery({ images }: { images: { id: number, src: string; altText
|
||||
</div>
|
||||
|
||||
{images.length > 1 ? (
|
||||
<ul className="my-12 flex items-center flex-wrap justify-center gap-2 overflow-auto py-1 lg:mb-0">
|
||||
<ul className="my-12 flex flex-wrap items-center justify-center gap-2 overflow-auto py-1 lg:mb-0">
|
||||
{images.map((image, index) => {
|
||||
const isActive = index === imageIndex;
|
||||
|
||||
|
@ -9,10 +9,7 @@ export function ProductDescription({ product }: { product: Product }) {
|
||||
<div className="mb-6 flex flex-col border-b pb-6 dark:border-neutral-700">
|
||||
<h1 className="mb-2 text-5xl font-medium">{product.name}</h1>
|
||||
<div className="mr-auto w-auto rounded-full bg-blue-600 p-2 text-sm text-white">
|
||||
<Price
|
||||
amount={product.price}
|
||||
currencyCode='EUR'
|
||||
/>
|
||||
<Price amount={product.price} currencyCode="EUR" />
|
||||
</div>
|
||||
</div>
|
||||
{product.description ? (
|
||||
|
@ -1,13 +1,13 @@
|
||||
export type Billing = {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
company: string;
|
||||
address_1: string;
|
||||
address_2: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postcode: string;
|
||||
country: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
};
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
company: string;
|
||||
address_1: string;
|
||||
address_2: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postcode: string;
|
||||
country: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
};
|
||||
|
@ -1,262 +1,259 @@
|
||||
export interface Cart {
|
||||
items: CartItem[];
|
||||
coupons: Coupon[];
|
||||
fees: any[];
|
||||
totals: Totals;
|
||||
shipping_address: IngAddress;
|
||||
billing_address: IngAddress;
|
||||
needs_payment: boolean;
|
||||
needs_shipping: boolean;
|
||||
payment_requirements: string[];
|
||||
has_calculated_shipping: boolean;
|
||||
shipping_rates: ShippingRate[];
|
||||
items_count: number;
|
||||
items_weight: number;
|
||||
cross_sells: any[];
|
||||
errors: any[];
|
||||
payment_methods: string[];
|
||||
extensions: Extensions;
|
||||
items: CartItem[];
|
||||
coupons: Coupon[];
|
||||
fees: any[];
|
||||
totals: Totals;
|
||||
shipping_address: IngAddress;
|
||||
billing_address: IngAddress;
|
||||
needs_payment: boolean;
|
||||
needs_shipping: boolean;
|
||||
payment_requirements: string[];
|
||||
has_calculated_shipping: boolean;
|
||||
shipping_rates: ShippingRate[];
|
||||
items_count: number;
|
||||
items_weight: number;
|
||||
cross_sells: any[];
|
||||
errors: any[];
|
||||
payment_methods: string[];
|
||||
extensions: Extensions;
|
||||
}
|
||||
|
||||
export interface CartItem {
|
||||
key: string;
|
||||
id: number;
|
||||
quantity: number;
|
||||
quantity_limits: QuantityLimits;
|
||||
name: string;
|
||||
short_description: string;
|
||||
description: string;
|
||||
sku: string;
|
||||
low_stock_remaining: null;
|
||||
backorders_allowed: boolean;
|
||||
show_backorder_badge: boolean;
|
||||
sold_individually: boolean;
|
||||
permalink: string;
|
||||
images: Image[];
|
||||
variation: any[];
|
||||
item_data: any[];
|
||||
prices: Prices;
|
||||
totals: Totals;
|
||||
catalog_visibility: string;
|
||||
extensions: Extensions;
|
||||
key: string;
|
||||
id: number;
|
||||
quantity: number;
|
||||
quantity_limits: QuantityLimits;
|
||||
name: string;
|
||||
short_description: string;
|
||||
description: string;
|
||||
sku: string;
|
||||
low_stock_remaining: null;
|
||||
backorders_allowed: boolean;
|
||||
show_backorder_badge: boolean;
|
||||
sold_individually: boolean;
|
||||
permalink: string;
|
||||
images: Image[];
|
||||
variation: any[];
|
||||
item_data: any[];
|
||||
prices: Prices;
|
||||
totals: Totals;
|
||||
catalog_visibility: string;
|
||||
extensions: Extensions;
|
||||
}
|
||||
|
||||
|
||||
export interface IngAddress {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
company: string;
|
||||
address_1: string;
|
||||
address_2: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postcode: string;
|
||||
country: string;
|
||||
email?: string;
|
||||
phone: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
company: string;
|
||||
address_1: string;
|
||||
address_2: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postcode: string;
|
||||
country: string;
|
||||
email?: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export interface Coupon {
|
||||
code: string;
|
||||
discount_type: string;
|
||||
totals: CouponTotals;
|
||||
code: string;
|
||||
discount_type: string;
|
||||
totals: CouponTotals;
|
||||
}
|
||||
|
||||
export interface CouponTotals {
|
||||
total_discount: string;
|
||||
total_discount_tax: string;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
total_discount: string;
|
||||
total_discount_tax: string;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
}
|
||||
|
||||
export interface Extensions {
|
||||
}
|
||||
export interface Extensions {}
|
||||
|
||||
export interface Image {
|
||||
id: number;
|
||||
src: string;
|
||||
thumbnail: string;
|
||||
srcset: string;
|
||||
sizes: string;
|
||||
name: string;
|
||||
alt: string;
|
||||
id: number;
|
||||
src: string;
|
||||
thumbnail: string;
|
||||
srcset: string;
|
||||
sizes: string;
|
||||
name: string;
|
||||
alt: string;
|
||||
}
|
||||
|
||||
export interface Prices {
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
price_range: null;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
raw_prices: RawPrices;
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
price_range: null;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
raw_prices: RawPrices;
|
||||
}
|
||||
|
||||
export interface RawPrices {
|
||||
precision: number;
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
precision: number;
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
}
|
||||
|
||||
export interface QuantityLimits {
|
||||
minimum: number;
|
||||
maximum: number;
|
||||
multiple_of: number;
|
||||
editable: boolean;
|
||||
minimum: number;
|
||||
maximum: number;
|
||||
multiple_of: number;
|
||||
editable: boolean;
|
||||
}
|
||||
|
||||
export interface ItemTotals {
|
||||
line_subtotal: string;
|
||||
line_subtotal_tax: string;
|
||||
line_total: string;
|
||||
line_total_tax: string;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
line_subtotal: string;
|
||||
line_subtotal_tax: string;
|
||||
line_total: string;
|
||||
line_total_tax: string;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
}
|
||||
|
||||
export interface ShippingRate {
|
||||
package_id: number;
|
||||
name: string;
|
||||
destination: Destination;
|
||||
items: ShippingRateItem[];
|
||||
shipping_rates: ShippingRateShippingRate[];
|
||||
package_id: number;
|
||||
name: string;
|
||||
destination: Destination;
|
||||
items: ShippingRateItem[];
|
||||
shipping_rates: ShippingRateShippingRate[];
|
||||
}
|
||||
|
||||
export interface Destination {
|
||||
address_1: string;
|
||||
address_2: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postcode: string;
|
||||
country: string;
|
||||
address_1: string;
|
||||
address_2: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postcode: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
export interface ShippingRateItem {
|
||||
key: string;
|
||||
name: string;
|
||||
quantity: number;
|
||||
key: string;
|
||||
name: string;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface ShippingRateShippingRate {
|
||||
rate_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
delivery_time: string;
|
||||
price: string;
|
||||
taxes: string;
|
||||
instance_id: number;
|
||||
method_id: string;
|
||||
meta_data: MetaDatum[];
|
||||
selected: boolean;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
rate_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
delivery_time: string;
|
||||
price: string;
|
||||
taxes: string;
|
||||
instance_id: number;
|
||||
method_id: string;
|
||||
meta_data: MetaDatum[];
|
||||
selected: boolean;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
}
|
||||
|
||||
export interface MetaDatum {
|
||||
key: string;
|
||||
value: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Totals {
|
||||
total_items: string;
|
||||
total_items_tax: string;
|
||||
total_fees: string;
|
||||
total_fees_tax: string;
|
||||
total_discount: string;
|
||||
total_discount_tax: string;
|
||||
total_shipping: string;
|
||||
total_shipping_tax: string;
|
||||
total_price: string;
|
||||
total_tax: string;
|
||||
tax_lines: TaxLine[];
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
total_items: string;
|
||||
total_items_tax: string;
|
||||
total_fees: string;
|
||||
total_fees_tax: string;
|
||||
total_discount: string;
|
||||
total_discount_tax: string;
|
||||
total_shipping: string;
|
||||
total_shipping_tax: string;
|
||||
total_price: string;
|
||||
total_tax: string;
|
||||
tax_lines: TaxLine[];
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
}
|
||||
|
||||
export interface TaxLine {
|
||||
name: string;
|
||||
price: string;
|
||||
rate: string;
|
||||
name: string;
|
||||
price: string;
|
||||
rate: string;
|
||||
}
|
||||
|
||||
export interface Extensions {
|
||||
}
|
||||
export interface Extensions {}
|
||||
|
||||
export interface Image {
|
||||
id: number;
|
||||
src: string;
|
||||
thumbnail: string;
|
||||
srcset: string;
|
||||
sizes: string;
|
||||
name: string;
|
||||
alt: string;
|
||||
id: number;
|
||||
src: string;
|
||||
thumbnail: string;
|
||||
srcset: string;
|
||||
sizes: string;
|
||||
name: string;
|
||||
alt: string;
|
||||
}
|
||||
|
||||
export interface Prices {
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
price_range: null;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
raw_prices: RawPrices;
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
price_range: null;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
raw_prices: RawPrices;
|
||||
}
|
||||
|
||||
export interface RawPrices {
|
||||
precision: number;
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
precision: number;
|
||||
price: string;
|
||||
regular_price: string;
|
||||
sale_price: string;
|
||||
}
|
||||
|
||||
export interface QuantityLimits {
|
||||
minimum: number;
|
||||
maximum: number;
|
||||
multiple_of: number;
|
||||
editable: boolean;
|
||||
minimum: number;
|
||||
maximum: number;
|
||||
multiple_of: number;
|
||||
editable: boolean;
|
||||
}
|
||||
|
||||
export interface Totals {
|
||||
line_subtotal: string;
|
||||
line_subtotal_tax: string;
|
||||
line_total: string;
|
||||
line_total_tax: string;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
line_subtotal: string;
|
||||
line_subtotal_tax: string;
|
||||
line_total: string;
|
||||
line_total_tax: string;
|
||||
currency_code: string;
|
||||
currency_symbol: string;
|
||||
currency_minor_unit: number;
|
||||
currency_decimal_separator: string;
|
||||
currency_thousand_separator: string;
|
||||
currency_prefix: string;
|
||||
currency_suffix: string;
|
||||
}
|
@ -3,8 +3,8 @@ import crypto from 'node:crypto';
|
||||
import OAuth from 'oauth-1.0a';
|
||||
import Url from 'url-parse';
|
||||
import { DELETE, IWooRestApiOptions, WooRestApiEndpoint, WooRestApiMethod } from './clientOptions';
|
||||
import { CouponsParams } from './coupon';
|
||||
import { CustomersParams } from './customer';
|
||||
import { Coupon, CouponsParams } from './coupon';
|
||||
import { Customer, CustomersParams } from './customer';
|
||||
import { Order, OrdersMainParams } from './orders';
|
||||
import { Product, ProductMainParams } from './product';
|
||||
|
||||
@ -27,10 +27,28 @@ export type WooRestApiParams = CouponsParams &
|
||||
/**
|
||||
* Define the response types for each endpoint.
|
||||
*/
|
||||
type WooCommerceResponse<T extends WooRestApiEndpoint> =
|
||||
T extends 'products' ? Product[] :
|
||||
T extends 'orders' ? Order[] :
|
||||
any;
|
||||
type WooCommerceResponse<
|
||||
T extends WooRestApiEndpoint,
|
||||
P extends Partial<WooRestApiParams> = {}
|
||||
> = P['id'] extends number | string // Verifica se `id` è definito e di tipo string
|
||||
? T extends 'products'
|
||||
? Product
|
||||
: T extends 'customers'
|
||||
? Customer
|
||||
: T extends 'orders'
|
||||
? Order
|
||||
: T extends 'coupons'
|
||||
? Coupon
|
||||
: any
|
||||
: T extends 'products'
|
||||
? Product[]
|
||||
: T extends 'customers'
|
||||
? Customer[]
|
||||
: T extends 'orders'
|
||||
? Order[]
|
||||
: T extends 'coupons'
|
||||
? Coupon[]
|
||||
: any;
|
||||
|
||||
/**
|
||||
* WooCommerce REST API wrapper
|
||||
@ -265,13 +283,13 @@ export default class WooCommerceRestApi<T extends WooRestApiOptions> {
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
_request<T extends WooRestApiEndpoint>(
|
||||
_request<T extends WooRestApiEndpoint, P extends Partial<WooRestApiParams>>(
|
||||
method: WooRestApiMethod,
|
||||
endpoint: T,
|
||||
data?: Record<string, unknown>,
|
||||
params: Record<string, unknown> = {},
|
||||
version?: string,
|
||||
): Promise<WooCommerceResponse<T>> {
|
||||
params: P = {} as P,
|
||||
version?: string
|
||||
): Promise<WooCommerceResponse<T, P>> {
|
||||
const url = this._getUrl(endpoint, params, version);
|
||||
const header: RawAxiosRequestHeaders = {
|
||||
Accept: 'application/json'
|
||||
@ -330,7 +348,7 @@ export default class WooCommerceRestApi<T extends WooRestApiOptions> {
|
||||
// Allow set and override Axios options.
|
||||
options = { ...options, ...this._opt.axiosConfig };
|
||||
|
||||
return axios(options).then((response) => response.data as WooCommerceResponse<T>);
|
||||
return axios(options).then((response) => response.data as WooCommerceResponse<T, P>);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -341,8 +359,11 @@ export default class WooCommerceRestApi<T extends WooRestApiOptions> {
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get<T extends WooRestApiEndpoint>(endpoint: T, params?: Partial<WooRestApiParams>): Promise<WooCommerceResponse<T>> {
|
||||
return this._request('GET', endpoint, undefined, params);
|
||||
get<T extends WooRestApiEndpoint, P extends Partial<WooRestApiParams>>(
|
||||
endpoint: T,
|
||||
params?: P
|
||||
): Promise<WooCommerceResponse<T, P>> {
|
||||
return this._request('GET', endpoint, undefined, params || ({} as P));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -392,7 +413,7 @@ export default class WooCommerceRestApi<T extends WooRestApiOptions> {
|
||||
endpoint: T,
|
||||
data: Pick<WooRestApiParams, 'force'>,
|
||||
params: Pick<WooRestApiParams, 'id'>
|
||||
): Promise<WooCommerceResponse<T>> {
|
||||
): Promise<WooCommerceResponse<T, Pick<WooRestApiParams, 'id'>>> {
|
||||
return this._request('DELETE', endpoint, data, params);
|
||||
}
|
||||
|
||||
|
@ -1,35 +1,30 @@
|
||||
export declare type WooRestApiVersion = "wc/v3";
|
||||
export declare type WooRestApiVersion = 'wc/v3';
|
||||
// | "wc/v2"
|
||||
// | "wc/v1"
|
||||
// | "wc-api/v3"
|
||||
// | "wc-api/v2"
|
||||
// | "wc-api/v1";
|
||||
export declare type WooRestApiEncoding = "utf-8" | "ascii";
|
||||
export declare type WooRestApiMethod =
|
||||
| "GET"
|
||||
| "POST"
|
||||
| "PUT"
|
||||
| "DELETE"
|
||||
| "OPTIONS";
|
||||
export declare type WooRestApiEncoding = 'utf-8' | 'ascii';
|
||||
export declare type WooRestApiMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS';
|
||||
|
||||
export declare type WooRestApiEndpoint =
|
||||
| "coupons"
|
||||
| "customers"
|
||||
| "orders"
|
||||
| "products"
|
||||
| "products/attributes"
|
||||
| "products/categories"
|
||||
| "products/shipping_classes"
|
||||
| "products/tags"
|
||||
| "products/reviews"
|
||||
| "system_status"
|
||||
| "reports" // TODO: add support for reports
|
||||
| "settings" // TODO: add support for settings
|
||||
| "webhooks" // TODO: add support for webhooks
|
||||
| "shipping" // TODO: add support for shipping
|
||||
| "shipping_methods" // TODO: add support for shipping_methods
|
||||
| "taxes" // TODO: add support for taxes
|
||||
| "payment_gateways" // TODO: add support for payment_gateways
|
||||
| 'coupons'
|
||||
| 'customers'
|
||||
| 'orders'
|
||||
| 'products'
|
||||
| 'products/attributes'
|
||||
| 'products/categories'
|
||||
| 'products/shipping_classes'
|
||||
| 'products/tags'
|
||||
| 'products/reviews'
|
||||
| 'system_status'
|
||||
| 'reports' // TODO: add support for reports
|
||||
| 'settings' // TODO: add support for settings
|
||||
| 'webhooks' // TODO: add support for webhooks
|
||||
| 'shipping' // TODO: add support for shipping
|
||||
| 'shipping_methods' // TODO: add support for shipping_methods
|
||||
| 'taxes' // TODO: add support for taxes
|
||||
| 'payment_gateways' // TODO: add support for payment_gateways
|
||||
| string; // I need to have next endpoint: "orders/<id>/notes"
|
||||
|
||||
export declare type IWooRestApiQuery = Record<string, unknown>;
|
||||
@ -80,7 +75,6 @@ export interface IWooRestApiOptions<T> extends IWooCredentials {
|
||||
isHttps?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface DELETE {
|
||||
id: number | string;
|
||||
force?: boolean | string;
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { Meta_Data } from "./base";
|
||||
import { Tax } from "./taxes";
|
||||
import { Meta_Data } from './base';
|
||||
import { Tax } from './taxes';
|
||||
|
||||
export type Line_Item = {
|
||||
id: number;
|
||||
name: string;
|
||||
product_id: number;
|
||||
variation_id: number;
|
||||
quantity: number;
|
||||
tax_class: string;
|
||||
subtotal: string;
|
||||
subtotal_tax: string;
|
||||
total: string;
|
||||
total_tax: string;
|
||||
taxes: Tax[];
|
||||
meta_data: Meta_Data;
|
||||
sku: string;
|
||||
price: number;
|
||||
};
|
||||
id: number;
|
||||
name: string;
|
||||
product_id: number;
|
||||
variation_id: number;
|
||||
quantity: number;
|
||||
tax_class: string;
|
||||
subtotal: string;
|
||||
subtotal_tax: string;
|
||||
total: string;
|
||||
total_tax: string;
|
||||
taxes: Tax[];
|
||||
meta_data: Meta_Data;
|
||||
sku: string;
|
||||
price: number;
|
||||
};
|
||||
|
@ -1,11 +1,11 @@
|
||||
export type Link = {
|
||||
self: Array<{
|
||||
href: string;
|
||||
}>;
|
||||
collection: Array<{
|
||||
href: string;
|
||||
}>;
|
||||
up: Array<{
|
||||
href: string;
|
||||
}>;
|
||||
};
|
||||
self: Array<{
|
||||
href: string;
|
||||
}>;
|
||||
collection: Array<{
|
||||
href: string;
|
||||
}>;
|
||||
up: Array<{
|
||||
href: string;
|
||||
}>;
|
||||
};
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Meta_Data } from './base';
|
||||
import { Image, Meta_Data } from './base';
|
||||
import { Billing } from './billing';
|
||||
import { Coupon_Lines } from './coupon';
|
||||
import { Fee_Lines } from './fee';
|
||||
import { Line_Item } from './item';
|
||||
import { Order_Refund_Line_Item, Refund } from './refund';
|
||||
import { Shipping, Shipping_Line } from './shipping';
|
||||
import { Tax_Line } from './taxes';
|
||||
@ -43,7 +42,7 @@ export interface Order {
|
||||
date_completed_gmt: string;
|
||||
cart_hash: string;
|
||||
meta_data: Partial<Meta_Data>[];
|
||||
line_items: Partial<Line_Item>[];
|
||||
line_items: Partial<OrderItem>[];
|
||||
tax_lines: Partial<Tax_Line>[];
|
||||
shipping_lines: Partial<Shipping_Line>[];
|
||||
fee_lines: Partial<Fee_Lines>[];
|
||||
@ -51,6 +50,25 @@ export interface Order {
|
||||
refunds: Partial<Refund>[];
|
||||
set_paid: boolean;
|
||||
}
|
||||
|
||||
export type OrderItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
product_id: number;
|
||||
variation_id: number;
|
||||
quantity: number;
|
||||
tax_class: string;
|
||||
subtotal: string;
|
||||
subtotal_tax: string;
|
||||
total: string;
|
||||
total_tax: string;
|
||||
taxes: any[];
|
||||
meta_data: Meta_Data[];
|
||||
sku: string;
|
||||
price: number;
|
||||
image: Image;
|
||||
};
|
||||
|
||||
export interface OrderNotes {
|
||||
id: number;
|
||||
author: string;
|
||||
|
@ -1,12 +1,4 @@
|
||||
import {
|
||||
Attribute,
|
||||
Category,
|
||||
Default_Attribute,
|
||||
Dimension,
|
||||
Image,
|
||||
Meta_Data,
|
||||
Tag
|
||||
} from './base';
|
||||
import { Attribute, Category, Default_Attribute, Dimension, Image, Meta_Data, Tag } from './base';
|
||||
|
||||
export interface Product {
|
||||
id: number;
|
||||
@ -211,7 +203,6 @@ type ProductShippingClassesParams = Partial<ProductShippingClass>;
|
||||
type ProductTagsParams = Partial<ProductTags>;
|
||||
type ProductReviewsParams = Partial<ProductReviews>;
|
||||
|
||||
|
||||
export type ProductMainParams =
|
||||
| (ProductParams & ProductVariationsParams & ProductAttributesParams)
|
||||
| ProductAttributesTermsParams
|
||||
|
@ -1,32 +1,32 @@
|
||||
import { Meta_Data } from "./base";
|
||||
import { Meta_Data } from './base';
|
||||
|
||||
export type Refund = {
|
||||
id: number;
|
||||
reason: string;
|
||||
total: string;
|
||||
};
|
||||
id: number;
|
||||
reason: string;
|
||||
total: string;
|
||||
};
|
||||
|
||||
export type Order_Refund_Line = {
|
||||
id: number;
|
||||
total: string;
|
||||
subtotal: string;
|
||||
refund_total: number;
|
||||
};
|
||||
export type Order_Refund_Line = {
|
||||
id: number;
|
||||
total: string;
|
||||
subtotal: string;
|
||||
refund_total: number;
|
||||
};
|
||||
|
||||
export type Order_Refund_Line_Item = {
|
||||
id: number;
|
||||
name: string;
|
||||
product_id: number;
|
||||
variation_id: number;
|
||||
quantity: number;
|
||||
tax_class: string;
|
||||
subtotal: string;
|
||||
subtotal_tax: string;
|
||||
total: string;
|
||||
total_tax: string;
|
||||
taxes: Partial<Order_Refund_Line>[];
|
||||
meta_data: Partial<Meta_Data>[];
|
||||
sku: string;
|
||||
price: string;
|
||||
refund_total: number;
|
||||
};
|
||||
export type Order_Refund_Line_Item = {
|
||||
id: number;
|
||||
name: string;
|
||||
product_id: number;
|
||||
variation_id: number;
|
||||
quantity: number;
|
||||
tax_class: string;
|
||||
subtotal: string;
|
||||
subtotal_tax: string;
|
||||
total: string;
|
||||
total_tax: string;
|
||||
taxes: Partial<Order_Refund_Line>[];
|
||||
meta_data: Partial<Meta_Data>[];
|
||||
sku: string;
|
||||
price: string;
|
||||
refund_total: number;
|
||||
};
|
||||
|
@ -11,12 +11,12 @@ class WooCommerceStoreApiClient {
|
||||
constructor(baseURL: string) {
|
||||
const headers: RawAxiosRequestHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': '*/*',
|
||||
Accept: '*/*'
|
||||
};
|
||||
|
||||
this.client = axios.create({
|
||||
baseURL,
|
||||
headers,
|
||||
headers
|
||||
});
|
||||
|
||||
this.client.interceptors.response.use((response) => {
|
||||
@ -37,16 +37,22 @@ class WooCommerceStoreApiClient {
|
||||
return this.client.get<Cart>('/cart', { params }).then((response) => response.data);
|
||||
}
|
||||
|
||||
async addToCart(payload: { id: string | number; quantity: number; variation: { attribute: string; value: string }[] }): Promise<Cart> {
|
||||
async addToCart(payload: {
|
||||
id: string | number;
|
||||
quantity: number;
|
||||
variation: { attribute: string; value: string }[];
|
||||
}): Promise<Cart> {
|
||||
return this.client.post<Cart>('/cart/add-item', payload).then((response) => response.data);
|
||||
}
|
||||
|
||||
async updateItem(payload: { key: string | number; quantity: number; }): Promise<Cart> {
|
||||
async updateItem(payload: { key: string | number; quantity: number }): Promise<Cart> {
|
||||
return this.client.post<Cart>('/cart/update-item', payload).then((response) => response.data);
|
||||
}
|
||||
|
||||
async removeFromCart(payload: { key: string | number }): Promise<Cart> {
|
||||
return this.client.post<Cart>(`/cart/remove-item?key=${payload.key}`).then((response) => response.data);
|
||||
return this.client
|
||||
.post<Cart>(`/cart/remove-item?key=${payload.key}`)
|
||||
.then((response) => response.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
import WooCommerceRestApi, { WooRestApiOptions } from "./models/client";
|
||||
import WooCommerceRestApi, { WooRestApiOptions } from './models/client';
|
||||
|
||||
const option :WooRestApiOptions = {
|
||||
url: process.env.WOOCOMMERCE_URL ?? "http://wordpress.localhost",
|
||||
consumerKey: process.env.WOOCOMMERCE_CONSUMER_KEY ?? "ck_1fb0a3c9b50ae813c31c7effc086a809d8416d90",
|
||||
consumerSecret: process.env.WOOCOMMERCE_CONSUMER_SECRET ?? "cs_ee4f1c9e061d07a7cb6025b69d414189a9157e20",
|
||||
isHttps: false,
|
||||
version: "wc/v3",
|
||||
queryStringAuth: false // Force Basic Authentication as query string true and using under
|
||||
}
|
||||
const option: WooRestApiOptions = {
|
||||
url: process.env.WOOCOMMERCE_URL ?? 'http://wordpress.localhost',
|
||||
consumerKey:
|
||||
process.env.WOOCOMMERCE_CONSUMER_KEY ?? 'ck_1fb0a3c9b50ae813c31c7effc086a809d8416d90',
|
||||
consumerSecret:
|
||||
process.env.WOOCOMMERCE_CONSUMER_SECRET ?? 'cs_ee4f1c9e061d07a7cb6025b69d414189a9157e20',
|
||||
isHttps: false,
|
||||
version: 'wc/v3',
|
||||
queryStringAuth: false // Force Basic Authentication as query string true and using under
|
||||
};
|
||||
export const woocommerce = new WooCommerceRestApi(option);
|
@ -3,8 +3,8 @@ export default {
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "http",
|
||||
hostname: "**",
|
||||
protocol: 'http',
|
||||
hostname: '**'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
1388
pnpm-lock.yaml
generated
1388
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "types/**/*.d.ts"],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"types/**/*.d.ts",
|
||||
"app/profile/orders/[id]"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
7
types/next-auth.d.ts
vendored
7
types/next-auth.d.ts
vendored
@ -4,14 +4,16 @@ import 'next-auth/jwt';
|
||||
declare module 'next-auth' {
|
||||
interface Session {
|
||||
user: {
|
||||
store_id: number;
|
||||
token: string;
|
||||
user_email: string;
|
||||
user_nicename: string;
|
||||
user_display_name: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface User {
|
||||
store_id: number;
|
||||
token: string;
|
||||
user_email: string;
|
||||
user_nicename: string;
|
||||
@ -22,10 +24,11 @@ declare module 'next-auth' {
|
||||
declare module 'next-auth/jwt' {
|
||||
interface JWT {
|
||||
user: {
|
||||
store_id: number;
|
||||
token: string;
|
||||
user_email: string;
|
||||
user_nicename: string;
|
||||
user_display_name: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user