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> {
|
}): Promise<Metadata> {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
@ -21,7 +20,7 @@ export async function generateMetadata(props: {
|
|||||||
|
|
||||||
export default async function Page(props: { params: Promise<{ page: string }> }) {
|
export default async function Page(props: { params: Promise<{ page: string }> }) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="mb-8 text-5xl font-bold">{''}</h1>
|
<h1 className="mb-8 text-5xl font-bold">{''}</h1>
|
||||||
|
@ -1,50 +1,50 @@
|
|||||||
import { woocommerce } from "lib/woocomerce/woocommerce";
|
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||||
import { NextAuthOptions, Session, User } from "next-auth";
|
import { NextAuthOptions, Session, User } from 'next-auth';
|
||||||
import { JWT } from "next-auth/jwt";
|
import { JWT } from 'next-auth/jwt';
|
||||||
import NextAuth from "next-auth/next";
|
import NextAuth from 'next-auth/next';
|
||||||
import CredentialsProvider from 'next-auth/providers/credentials';
|
import CredentialsProvider from 'next-auth/providers/credentials';
|
||||||
|
|
||||||
export const authOptions = {
|
export const authOptions = {
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
session: {
|
session: {
|
||||||
strategy: "jwt", // Use JWT for session handling
|
strategy: 'jwt' // Use JWT for session handling
|
||||||
},
|
},
|
||||||
providers: [
|
providers: [
|
||||||
CredentialsProvider({
|
CredentialsProvider({
|
||||||
name: 'woocommerce',
|
name: 'woocommerce',
|
||||||
credentials: {
|
credentials: {
|
||||||
username: { label: 'Username', type: 'text', placeholder: 'Username' },
|
username: { label: 'Username', type: 'text', placeholder: 'Username' },
|
||||||
password: { label: 'Password', type: 'password', placeholder: 'Password' },
|
password: { label: 'Password', type: 'password', placeholder: 'Password' }
|
||||||
},
|
},
|
||||||
async authorize(credentials, req) {
|
async authorize(credentials, req) {
|
||||||
if (!credentials?.username || !credentials?.password) {
|
if (!credentials?.username || !credentials?.password) {
|
||||||
return null;
|
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
|
|
||||||
}
|
}
|
||||||
}),
|
const user = await woocommerce.login(credentials.username, credentials.password);
|
||||||
],
|
// If no error and we have user data, return it
|
||||||
callbacks: {
|
|
||||||
async jwt({ token, user }: { token: JWT, user: User }) {
|
|
||||||
if (user) {
|
if (user) {
|
||||||
console.debug('Set token user', user);
|
return user;
|
||||||
token.user = user;
|
|
||||||
}
|
}
|
||||||
return token;
|
// Return null if user data could not be retrieved
|
||||||
},
|
return null;
|
||||||
async session({ session, token }: {session: Session, token: JWT}) {
|
}
|
||||||
console.debug('Set session token', token.user);
|
})
|
||||||
session.user = token.user;
|
],
|
||||||
return session;
|
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;
|
} satisfies NextAuthOptions;
|
||||||
|
|
||||||
const handler = NextAuth(authOptions)
|
const handler = NextAuth(authOptions);
|
||||||
export { handler as GET, handler as POST };
|
export { handler as GET, handler as POST };
|
||||||
|
@ -9,7 +9,7 @@ export async function GET(req: NextRequest) {
|
|||||||
if (session?.user?.token) {
|
if (session?.user?.token) {
|
||||||
storeApi._setAuthorizationToken(session.user.token);
|
storeApi._setAuthorizationToken(session.user.token);
|
||||||
} else {
|
} else {
|
||||||
storeApi._setAuthorizationToken('');
|
storeApi._setAuthorizationToken('');
|
||||||
}
|
}
|
||||||
const cart = await storeApi.getCart();
|
const cart = await storeApi.getCart();
|
||||||
return NextResponse.json(cart, { status: 200 });
|
return NextResponse.json(cart, { status: 200 });
|
||||||
@ -32,11 +32,11 @@ export async function PUT(req: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const { key, quantity } = await req.json();
|
const { key, quantity } = await req.json();
|
||||||
if (quantity > 0) {
|
if (quantity > 0) {
|
||||||
const cart = await storeApi.updateItem({ key, quantity });
|
const cart = await storeApi.updateItem({ key, quantity });
|
||||||
return NextResponse.json(cart, { status: 200 });
|
return NextResponse.json(cart, { status: 200 });
|
||||||
} else {
|
} else {
|
||||||
const cart = await storeApi.removeFromCart({ key });
|
const cart = await storeApi.removeFromCart({ key });
|
||||||
return NextResponse.json(cart, { status: 200 });
|
return NextResponse.json(cart, { status: 200 });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return NextResponse.json({ error: 'Failed to update cart item' }, { status: 500 });
|
return NextResponse.json({ error: 'Failed to update cart item' }, { status: 500 });
|
||||||
@ -51,4 +51,4 @@ export async function DELETE(req: NextRequest) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return NextResponse.json({ error: 'Failed to remove item from cart' }, { status: 500 });
|
return NextResponse.json({ error: 'Failed to remove item from cart' }, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,4 +6,4 @@ export default async function CheckoutPage(props: { params: Promise<{ id: number
|
|||||||
<h1>Checkout</h1>
|
<h1>Checkout</h1>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
|
|
||||||
import { ThreeItemGridItem } from 'components/grid/three-items';
|
import { ThreeItemGridItem } from 'components/grid/three-items';
|
||||||
import { Product } from 'lib/woocomerce/models/product';
|
import { Product } from 'lib/woocomerce/models/product';
|
||||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||||
|
|
||||||
|
|
||||||
export default async function ProductPage(props: { params: Promise<{ name: string }> }) {
|
export default async function ProductPage(props: { params: Promise<{ name: string }> }) {
|
||||||
const params = await props.params;
|
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 (
|
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)]">
|
<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} />
|
<ThreeItemGridItem key={product.id} size={index === 0 ? 'full' : 'half'} item={product} />
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { CartProvider } from 'components/cart/cart-context';
|
import { CartProvider } from 'components/cart/cart-context';
|
||||||
import { Navbar } from 'components/layout/navbar';
|
import { Navbar } from 'components/layout/navbar';
|
||||||
import { NextAuthProvider } from 'components/next-session-provider';
|
import { NextAuthProvider } from 'components/next-session-provider';
|
||||||
@ -39,20 +38,20 @@ export const metadata = {
|
|||||||
|
|
||||||
export default async function RootLayout({ children }: { children: ReactNode }) {
|
export default async function RootLayout({ children }: { children: ReactNode }) {
|
||||||
const cart = await storeApi.getCart();
|
const cart = await storeApi.getCart();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={GeistSans.variable}>
|
<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">
|
<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>
|
<NextAuthProvider>
|
||||||
<CartProvider value={cart}>
|
<CartProvider value={cart}>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main>
|
<main>
|
||||||
{children}
|
{children}
|
||||||
<Toaster closeButton />
|
<Toaster closeButton />
|
||||||
<WelcomeToast />
|
<WelcomeToast />
|
||||||
</main>
|
</main>
|
||||||
</CartProvider>
|
</CartProvider>
|
||||||
</NextAuthProvider>
|
</NextAuthProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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>
|
||||||
|
);
|
||||||
|
}
|
@ -9,10 +9,10 @@ export const metadata = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ThreeItemGrid/>
|
<ThreeItemGrid />
|
||||||
<Carousel />
|
<Carousel />
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
|
@ -15,8 +15,10 @@ export async function generateMetadata(props: {
|
|||||||
params: Promise<{ name: string }>;
|
params: Promise<{ name: string }>;
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const params = await props.params;
|
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();
|
if (!product) return notFound();
|
||||||
|
|
||||||
const indexable = !product.tags.find((tag) => tag.name?.includes(HIDDEN_PRODUCT_TAG));
|
const indexable = !product.tags.find((tag) => tag.name?.includes(HIDDEN_PRODUCT_TAG));
|
||||||
@ -31,13 +33,15 @@ export async function generateMetadata(props: {
|
|||||||
index: indexable,
|
index: indexable,
|
||||||
follow: indexable
|
follow: indexable
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function ProductPage(props: { params: Promise<{ name: string }> }) {
|
export default async function ProductPage(props: { params: Promise<{ name: string }> }) {
|
||||||
const params = await props.params;
|
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();
|
if (!product) return notFound();
|
||||||
|
|
||||||
@ -49,9 +53,8 @@ export default async function ProductPage(props: { params: Promise<{ name: strin
|
|||||||
image: product.images?.[0]?.src,
|
image: product.images?.[0]?.src,
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'AggregateOffer',
|
'@type': 'AggregateOffer',
|
||||||
availability: product.stock_quantity > 0
|
availability:
|
||||||
? 'https://schema.org/InStock'
|
product.stock_quantity > 0 ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
|
||||||
: 'https://schema.org/OutOfStock',
|
|
||||||
priceCurrency: product.price,
|
priceCurrency: product.price,
|
||||||
highPrice: product.max_price,
|
highPrice: product.max_price,
|
||||||
lowPrice: product.min_price
|
lowPrice: product.min_price
|
||||||
@ -94,4 +97,4 @@ export default async function ProductPage(props: { params: Promise<{ name: strin
|
|||||||
<Footer />
|
<Footer />
|
||||||
</ProductProvider>
|
</ProductProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
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 const runtime = 'edge';
|
||||||
|
|
||||||
export default async function Image({ params }: { params: { collection: string } }) {
|
export default async function Image({ params }: { params: { collection: string } }) {
|
||||||
|
|
||||||
return await OpengraphImage({ title: '' });
|
return await OpengraphImage({ title: '' });
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,7 @@ export default function SearchLayout({ children }: { children: React.ReactNode }
|
|||||||
return (
|
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="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 className="order-first w-full flex-none md:max-w-[125px]"></div>
|
||||||
</div>
|
|
||||||
<div className="order-last min-h-screen w-full md:order-none">
|
<div className="order-last min-h-screen w-full md:order-none">
|
||||||
<ChildrenWrapper>{children}</ChildrenWrapper>
|
<ChildrenWrapper>{children}</ChildrenWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,7 +15,7 @@ export default async function SearchPage(props: {
|
|||||||
const { sort, q: searchValue } = searchParams as { [key: string]: string };
|
const { sort, q: searchValue } = searchParams as { [key: string]: string };
|
||||||
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
|
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';
|
const resultsText = products.length > 1 ? 'results' : 'result';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { validateEnvironmentVariables } from 'lib/utils';
|
import { validateEnvironmentVariables } from 'lib/utils';
|
||||||
import { MetadataRoute } from 'next';
|
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() {
|
export async function Carousel() {
|
||||||
// Collections that start with `hidden-*` are hidden from the search page.
|
// 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;
|
if (!products?.length) return null;
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ export async function Carousel() {
|
|||||||
key={`${product.id}${i}`}
|
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"
|
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
|
<GridTileImage
|
||||||
alt={product.name}
|
alt={product.name}
|
||||||
label={{
|
label={{
|
||||||
|
@ -5,17 +5,12 @@ import clsx from 'clsx';
|
|||||||
import { Product } from 'lib/woocomerce/models/product';
|
import { Product } from 'lib/woocomerce/models/product';
|
||||||
import { useCart } from './cart-context';
|
import { useCart } from './cart-context';
|
||||||
|
|
||||||
function SubmitButton({
|
function SubmitButton({}: {}) {
|
||||||
}: {
|
|
||||||
}) {
|
|
||||||
const buttonClasses =
|
const buttonClasses =
|
||||||
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white';
|
'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button aria-label="Please select an option" className={clsx(buttonClasses)}>
|
||||||
aria-label="Please select an option"
|
|
||||||
className={clsx(buttonClasses)}
|
|
||||||
>
|
|
||||||
<div className="absolute left-0 ml-4">
|
<div className="absolute left-0 ml-4">
|
||||||
<PlusIcon className="h-5" />
|
<PlusIcon className="h-5" />
|
||||||
</div>
|
</div>
|
||||||
@ -31,7 +26,12 @@ export function AddToCart({ product }: { product: Product }) {
|
|||||||
<form
|
<form
|
||||||
action={async () => {
|
action={async () => {
|
||||||
try {
|
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);
|
setNewCart(cart);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -5,8 +5,12 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
type UpdateType = 'plus' | 'minus' | 'delete';
|
type UpdateType = 'plus' | 'minus' | 'delete';
|
||||||
|
|
||||||
type UpdatePayload = { key: string | number; quantity: number;};
|
type UpdatePayload = { key: string | number; quantity: number };
|
||||||
type AddPayload = { id: string | number; quantity: number; variation: { attribute: string; value: string }[] };
|
type AddPayload = {
|
||||||
|
id: string | number;
|
||||||
|
quantity: number;
|
||||||
|
variation: { attribute: string; value: string }[];
|
||||||
|
};
|
||||||
type RemovePayload = { key: string | number };
|
type RemovePayload = { key: string | number };
|
||||||
|
|
||||||
type CartContextType = {
|
type CartContextType = {
|
||||||
@ -16,17 +20,11 @@ type CartContextType = {
|
|||||||
|
|
||||||
const CartContext = createContext<CartContextType | undefined>(undefined);
|
const CartContext = createContext<CartContextType | undefined>(undefined);
|
||||||
|
|
||||||
export function CartProvider({
|
export function CartProvider({ value, children }: { value: Cart; children: React.ReactNode }) {
|
||||||
value,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
value: Cart;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const [cart, setCart] = useState<Cart | undefined>(value);
|
const [cart, setCart] = useState<Cart | undefined>(value);
|
||||||
const setNewCart = (cart: Cart) => {
|
const setNewCart = (cart: Cart) => {
|
||||||
setCart(cart);
|
setCart(cart);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCart(value);
|
setCart(value);
|
||||||
@ -36,7 +34,7 @@ export function CartProvider({
|
|||||||
<CartContext.Provider
|
<CartContext.Provider
|
||||||
value={{
|
value={{
|
||||||
cart,
|
cart,
|
||||||
setNewCart,
|
setNewCart
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -4,18 +4,16 @@ import { XMarkIcon } from '@heroicons/react/24/outline';
|
|||||||
import { CartItem } from 'lib/woocomerce/models/cart';
|
import { CartItem } from 'lib/woocomerce/models/cart';
|
||||||
import { useCart } from './cart-context';
|
import { useCart } from './cart-context';
|
||||||
|
|
||||||
export function DeleteItemButton({
|
export function DeleteItemButton({ item }: { item: CartItem }) {
|
||||||
item,
|
const { setNewCart } = useCart();
|
||||||
}: {
|
|
||||||
item: CartItem;
|
|
||||||
}) {
|
|
||||||
const {setNewCart} = useCart();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
action={async () => {
|
action={async () => {
|
||||||
try {
|
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);
|
setNewCart(cart);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -26,29 +26,24 @@ function SubmitButton({ type }: { type: 'plus' | 'minus' }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EditItemQuantityButton({
|
export function EditItemQuantityButton({ item, type }: { item: CartItem; type: 'plus' | 'minus' }) {
|
||||||
item,
|
const { setNewCart } = useCart();
|
||||||
type,
|
|
||||||
}: {
|
|
||||||
item: CartItem;
|
|
||||||
type: 'plus' | 'minus';
|
|
||||||
}) {
|
|
||||||
const {setNewCart} = useCart();
|
|
||||||
const payload = {
|
const payload = {
|
||||||
key: item.key,
|
key: item.key,
|
||||||
quantity: type === 'plus' ? item.quantity + 1 : item.quantity - 1,
|
quantity: type === 'plus' ? item.quantity + 1 : item.quantity - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
action={async () => {
|
action={async () => {
|
||||||
try {
|
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);
|
setNewCart(cart);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SubmitButton type={type} />
|
<SubmitButton type={type} />
|
||||||
|
@ -15,29 +15,35 @@ import { DeleteItemButton } from './delete-item-button';
|
|||||||
import { EditItemQuantityButton } from './edit-item-quantity-button';
|
import { EditItemQuantityButton } from './edit-item-quantity-button';
|
||||||
import OpenCart from './open-cart';
|
import OpenCart from './open-cart';
|
||||||
|
|
||||||
|
|
||||||
export default function CartModal() {
|
export default function CartModal() {
|
||||||
const {cart, setNewCart} = useCart();
|
const { cart, setNewCart } = useCart();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [userIsLoggedIn, setUserIsLoggedIn] = useState(false);
|
const [userIsLoggedIn, setUserIsLoggedIn] = useState(false);
|
||||||
const openCart = () => setIsOpen(true);
|
const openCart = () => setIsOpen(true);
|
||||||
const closeCart = () => setIsOpen(false);
|
const closeCart = () => setIsOpen(false);
|
||||||
const {data} = useSession();
|
const { data } = useSession();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.user.token) {
|
if (data?.user.token) {
|
||||||
const fetchCart = async () => {
|
const fetchCart = async () => {
|
||||||
const cart = await (await fetch('/api/cart')).json();
|
const cart = await (await fetch('/api/cart')).json();
|
||||||
setNewCart(cart);
|
setNewCart(cart);
|
||||||
};
|
};
|
||||||
fetchCart();
|
fetchCart();
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button aria-label="Open cart" onClick={openCart}>
|
<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>
|
</button>
|
||||||
<Transition show={isOpen}>
|
<Transition show={isOpen}>
|
||||||
<Dialog onClose={closeCart} className="relative z-50">
|
<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">
|
<div className="flex h-full flex-col justify-between overflow-hidden p-1">
|
||||||
<ul className="flex-grow overflow-auto py-4">
|
<ul className="flex-grow overflow-auto py-4">
|
||||||
{cart.items?.length && cart.items
|
{cart.items?.length &&
|
||||||
.sort((a, b) =>
|
cart.items
|
||||||
a.name.localeCompare(b.name)
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
)
|
.map((item, i) => {
|
||||||
.map((item, i) => {
|
return (
|
||||||
return (
|
<li
|
||||||
<li
|
key={i}
|
||||||
key={i}
|
className="flex w-full flex-col border-b border-neutral-300 dark:border-neutral-700"
|
||||||
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="relative flex w-full flex-row justify-between px-1 py-4">
|
<div className="absolute z-40 -ml-1 -mt-2">
|
||||||
<div className="absolute z-40 -ml-1 -mt-2">
|
<DeleteItemButton item={item} />
|
||||||
<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 || ''}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<div className="flex flex-row">
|
||||||
href={''}
|
<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">
|
||||||
onClick={closeCart}
|
<Image
|
||||||
className="z-30 ml-2 flex flex-row space-x-4"
|
className="h-full w-full object-cover"
|
||||||
>
|
width={64}
|
||||||
<div className="flex flex-1 flex-col text-base">
|
height={64}
|
||||||
<span className="leading-tight">
|
alt={item.name}
|
||||||
{item.name}
|
src={item.images?.[0]?.src || ''}
|
||||||
</span>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
<Link
|
||||||
</div>
|
href={''}
|
||||||
<div className="flex h-16 flex-col justify-between">
|
onClick={closeCart}
|
||||||
<Price
|
className="z-30 ml-2 flex flex-row space-x-4"
|
||||||
className="flex justify-end space-y-2 text-right text-sm"
|
>
|
||||||
amount={item.prices?.price}
|
<div className="flex flex-1 flex-col text-base">
|
||||||
needSplit
|
<span className="leading-tight">{item.name}</span>
|
||||||
currencyCode={item.prices.currency_code}
|
</div>
|
||||||
/>
|
</Link>
|
||||||
<div className="ml-auto flex h-9 flex-row items-center rounded-full border border-neutral-200 dark:border-neutral-700">
|
</div>
|
||||||
<EditItemQuantityButton
|
<div className="flex h-16 flex-col justify-between">
|
||||||
item={item}
|
<Price
|
||||||
type="minus"
|
className="flex justify-end space-y-2 text-right text-sm"
|
||||||
/>
|
amount={item.prices?.price}
|
||||||
<p className="w-6 text-center">
|
needSplit
|
||||||
<span className="w-full text-sm">{item.quantity}</span>
|
currencyCode={item.prices.currency_code}
|
||||||
</p>
|
|
||||||
<EditItemQuantityButton
|
|
||||||
item={item}
|
|
||||||
type="plus"
|
|
||||||
/>
|
/>
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
|
||||||
</ul>
|
</ul>
|
||||||
<div className="py-4 text-sm text-neutral-500 dark:text-neutral-400">
|
<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">
|
<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() {
|
export async function ThreeItemGrid() {
|
||||||
// Collections that start with `hidden-*` are hidden from the search page.
|
// 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;
|
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 FooterMenu from 'components/layout/footer-menu';
|
||||||
import LogoSquare from 'components/logo-square';
|
import LogoSquare from 'components/logo-square';
|
||||||
|
import Link from 'next/link';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
const { COMPANY_NAME, SITE_NAME } = process.env;
|
const { COMPANY_NAME, SITE_NAME } = process.env;
|
||||||
@ -20,7 +19,7 @@ export default async function Footer() {
|
|||||||
{
|
{
|
||||||
title: 'Shop',
|
title: 'Shop',
|
||||||
path: '/shop'
|
path: '/shop'
|
||||||
},
|
}
|
||||||
] as Menu[];
|
] as Menu[];
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
|
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
import CartModal from 'components/cart/modal';
|
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 LogoSquare from 'components/logo-square';
|
||||||
import { Category } from 'lib/woocomerce/models/base';
|
import { Category } from 'lib/woocomerce/models/base';
|
||||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||||
@ -18,7 +17,7 @@ type Menu = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function Navbar() {
|
export async function Navbar() {
|
||||||
const categories: Category[] = (await (woocommerce.get('products/categories')));
|
const categories: Category[] = await woocommerce.get('products/categories');
|
||||||
const menu = [
|
const menu = [
|
||||||
{
|
{
|
||||||
title: 'Home',
|
title: 'Home',
|
||||||
@ -71,8 +70,8 @@ export async function Navbar() {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end md:w-1/3">
|
<div className="flex justify-end md:w-1/3">
|
||||||
<LoginModal />
|
|
||||||
<CartModal />
|
<CartModal />
|
||||||
|
<UserIcon />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -10,7 +10,7 @@ export default function ProductGridItems({ products }: { products: Product[] })
|
|||||||
<Grid.Item key={product.id} className="animate-fadeIn">
|
<Grid.Item key={product.id} className="animate-fadeIn">
|
||||||
<Link
|
<Link
|
||||||
className="relative inline-block h-full w-full"
|
className="relative inline-block h-full w-full"
|
||||||
href={`/product/${product.id}`}
|
href={`/product/${product.slug}`}
|
||||||
prefetch={true}
|
prefetch={true}
|
||||||
>
|
>
|
||||||
<GridTileImage
|
<GridTileImage
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
import FilterList, { ListItem } from './filter';
|
||||||
import { getCollections } from 'lib/shopify';
|
|
||||||
import FilterList from './filter';
|
|
||||||
|
|
||||||
async function CollectionList() {
|
async function CollectionList() {
|
||||||
const collections = await getCollections();
|
const collections: ListItem[] = [];
|
||||||
return <FilterList list={collections} title="Collections" />;
|
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 = {
|
type Props = {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -8,4 +8,4 @@ type Props = {
|
|||||||
|
|
||||||
export const NextAuthProvider = ({ children }: Props) => {
|
export const NextAuthProvider = ({ children }: Props) => {
|
||||||
return <SessionProvider>{children}</SessionProvider>;
|
return <SessionProvider>{children}</SessionProvider>;
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@ const Price = ({
|
|||||||
<p suppressHydrationWarning={true} className={className}>
|
<p suppressHydrationWarning={true} className={className}>
|
||||||
{`${new Intl.NumberFormat(undefined, {
|
{`${new Intl.NumberFormat(undefined, {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
|
|
||||||
currency: currencyCode,
|
currency: currencyCode,
|
||||||
currencyDisplay: 'narrowSymbol'
|
currencyDisplay: 'narrowSymbol'
|
||||||
}).format(parseFloat(amount) / (needSplit ? 100 : 1))}`}
|
}).format(parseFloat(amount) / (needSplit ? 100 : 1))}`}
|
||||||
|
@ -5,7 +5,7 @@ import { GridTileImage } from 'components/grid/tile';
|
|||||||
import { useProduct, useUpdateURL } from 'components/product/product-context';
|
import { useProduct, useUpdateURL } from 'components/product/product-context';
|
||||||
import Image from 'next/image';
|
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 { state, updateImage } = useProduct();
|
||||||
const updateURL = useUpdateURL();
|
const updateURL = useUpdateURL();
|
||||||
const imageIndex = state.image ? parseInt(state.image) : 0;
|
const imageIndex = state.image ? parseInt(state.image) : 0;
|
||||||
@ -60,7 +60,7 @@ export function Gallery({ images }: { images: { id: number, src: string; altText
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{images.length > 1 ? (
|
{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) => {
|
{images.map((image, index) => {
|
||||||
const isActive = index === imageIndex;
|
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">
|
<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>
|
<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">
|
<div className="mr-auto w-auto rounded-full bg-blue-600 p-2 text-sm text-white">
|
||||||
<Price
|
<Price amount={product.price} currencyCode="EUR" />
|
||||||
amount={product.price}
|
|
||||||
currencyCode='EUR'
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{product.description ? (
|
{product.description ? (
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
export type Billing = {
|
export type Billing = {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
company: string;
|
company: string;
|
||||||
address_1: string;
|
address_1: string;
|
||||||
address_2: string;
|
address_2: string;
|
||||||
city: string;
|
city: string;
|
||||||
state: string;
|
state: string;
|
||||||
postcode: string;
|
postcode: string;
|
||||||
country: string;
|
country: string;
|
||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
};
|
};
|
||||||
|
@ -1,262 +1,259 @@
|
|||||||
export interface Cart {
|
export interface Cart {
|
||||||
items: CartItem[];
|
items: CartItem[];
|
||||||
coupons: Coupon[];
|
coupons: Coupon[];
|
||||||
fees: any[];
|
fees: any[];
|
||||||
totals: Totals;
|
totals: Totals;
|
||||||
shipping_address: IngAddress;
|
shipping_address: IngAddress;
|
||||||
billing_address: IngAddress;
|
billing_address: IngAddress;
|
||||||
needs_payment: boolean;
|
needs_payment: boolean;
|
||||||
needs_shipping: boolean;
|
needs_shipping: boolean;
|
||||||
payment_requirements: string[];
|
payment_requirements: string[];
|
||||||
has_calculated_shipping: boolean;
|
has_calculated_shipping: boolean;
|
||||||
shipping_rates: ShippingRate[];
|
shipping_rates: ShippingRate[];
|
||||||
items_count: number;
|
items_count: number;
|
||||||
items_weight: number;
|
items_weight: number;
|
||||||
cross_sells: any[];
|
cross_sells: any[];
|
||||||
errors: any[];
|
errors: any[];
|
||||||
payment_methods: string[];
|
payment_methods: string[];
|
||||||
extensions: Extensions;
|
extensions: Extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CartItem {
|
export interface CartItem {
|
||||||
key: string;
|
key: string;
|
||||||
id: number;
|
id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
quantity_limits: QuantityLimits;
|
quantity_limits: QuantityLimits;
|
||||||
name: string;
|
name: string;
|
||||||
short_description: string;
|
short_description: string;
|
||||||
description: string;
|
description: string;
|
||||||
sku: string;
|
sku: string;
|
||||||
low_stock_remaining: null;
|
low_stock_remaining: null;
|
||||||
backorders_allowed: boolean;
|
backorders_allowed: boolean;
|
||||||
show_backorder_badge: boolean;
|
show_backorder_badge: boolean;
|
||||||
sold_individually: boolean;
|
sold_individually: boolean;
|
||||||
permalink: string;
|
permalink: string;
|
||||||
images: Image[];
|
images: Image[];
|
||||||
variation: any[];
|
variation: any[];
|
||||||
item_data: any[];
|
item_data: any[];
|
||||||
prices: Prices;
|
prices: Prices;
|
||||||
totals: Totals;
|
totals: Totals;
|
||||||
catalog_visibility: string;
|
catalog_visibility: string;
|
||||||
extensions: Extensions;
|
extensions: Extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IngAddress {
|
export interface IngAddress {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
company: string;
|
company: string;
|
||||||
address_1: string;
|
address_1: string;
|
||||||
address_2: string;
|
address_2: string;
|
||||||
city: string;
|
city: string;
|
||||||
state: string;
|
state: string;
|
||||||
postcode: string;
|
postcode: string;
|
||||||
country: string;
|
country: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Coupon {
|
export interface Coupon {
|
||||||
code: string;
|
code: string;
|
||||||
discount_type: string;
|
discount_type: string;
|
||||||
totals: CouponTotals;
|
totals: CouponTotals;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CouponTotals {
|
export interface CouponTotals {
|
||||||
total_discount: string;
|
total_discount: string;
|
||||||
total_discount_tax: string;
|
total_discount_tax: string;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
currency_symbol: string;
|
currency_symbol: string;
|
||||||
currency_minor_unit: number;
|
currency_minor_unit: number;
|
||||||
currency_decimal_separator: string;
|
currency_decimal_separator: string;
|
||||||
currency_thousand_separator: string;
|
currency_thousand_separator: string;
|
||||||
currency_prefix: string;
|
currency_prefix: string;
|
||||||
currency_suffix: string;
|
currency_suffix: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Extensions {
|
export interface Extensions {}
|
||||||
}
|
|
||||||
|
|
||||||
export interface Image {
|
export interface Image {
|
||||||
id: number;
|
id: number;
|
||||||
src: string;
|
src: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
srcset: string;
|
srcset: string;
|
||||||
sizes: string;
|
sizes: string;
|
||||||
name: string;
|
name: string;
|
||||||
alt: string;
|
alt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Prices {
|
export interface Prices {
|
||||||
price: string;
|
price: string;
|
||||||
regular_price: string;
|
regular_price: string;
|
||||||
sale_price: string;
|
sale_price: string;
|
||||||
price_range: null;
|
price_range: null;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
currency_symbol: string;
|
currency_symbol: string;
|
||||||
currency_minor_unit: number;
|
currency_minor_unit: number;
|
||||||
currency_decimal_separator: string;
|
currency_decimal_separator: string;
|
||||||
currency_thousand_separator: string;
|
currency_thousand_separator: string;
|
||||||
currency_prefix: string;
|
currency_prefix: string;
|
||||||
currency_suffix: string;
|
currency_suffix: string;
|
||||||
raw_prices: RawPrices;
|
raw_prices: RawPrices;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RawPrices {
|
export interface RawPrices {
|
||||||
precision: number;
|
precision: number;
|
||||||
price: string;
|
price: string;
|
||||||
regular_price: string;
|
regular_price: string;
|
||||||
sale_price: string;
|
sale_price: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QuantityLimits {
|
export interface QuantityLimits {
|
||||||
minimum: number;
|
minimum: number;
|
||||||
maximum: number;
|
maximum: number;
|
||||||
multiple_of: number;
|
multiple_of: number;
|
||||||
editable: boolean;
|
editable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemTotals {
|
export interface ItemTotals {
|
||||||
line_subtotal: string;
|
line_subtotal: string;
|
||||||
line_subtotal_tax: string;
|
line_subtotal_tax: string;
|
||||||
line_total: string;
|
line_total: string;
|
||||||
line_total_tax: string;
|
line_total_tax: string;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
currency_symbol: string;
|
currency_symbol: string;
|
||||||
currency_minor_unit: number;
|
currency_minor_unit: number;
|
||||||
currency_decimal_separator: string;
|
currency_decimal_separator: string;
|
||||||
currency_thousand_separator: string;
|
currency_thousand_separator: string;
|
||||||
currency_prefix: string;
|
currency_prefix: string;
|
||||||
currency_suffix: string;
|
currency_suffix: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShippingRate {
|
export interface ShippingRate {
|
||||||
package_id: number;
|
package_id: number;
|
||||||
name: string;
|
name: string;
|
||||||
destination: Destination;
|
destination: Destination;
|
||||||
items: ShippingRateItem[];
|
items: ShippingRateItem[];
|
||||||
shipping_rates: ShippingRateShippingRate[];
|
shipping_rates: ShippingRateShippingRate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Destination {
|
export interface Destination {
|
||||||
address_1: string;
|
address_1: string;
|
||||||
address_2: string;
|
address_2: string;
|
||||||
city: string;
|
city: string;
|
||||||
state: string;
|
state: string;
|
||||||
postcode: string;
|
postcode: string;
|
||||||
country: string;
|
country: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShippingRateItem {
|
export interface ShippingRateItem {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShippingRateShippingRate {
|
export interface ShippingRateShippingRate {
|
||||||
rate_id: string;
|
rate_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
delivery_time: string;
|
delivery_time: string;
|
||||||
price: string;
|
price: string;
|
||||||
taxes: string;
|
taxes: string;
|
||||||
instance_id: number;
|
instance_id: number;
|
||||||
method_id: string;
|
method_id: string;
|
||||||
meta_data: MetaDatum[];
|
meta_data: MetaDatum[];
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
currency_symbol: string;
|
currency_symbol: string;
|
||||||
currency_minor_unit: number;
|
currency_minor_unit: number;
|
||||||
currency_decimal_separator: string;
|
currency_decimal_separator: string;
|
||||||
currency_thousand_separator: string;
|
currency_thousand_separator: string;
|
||||||
currency_prefix: string;
|
currency_prefix: string;
|
||||||
currency_suffix: string;
|
currency_suffix: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetaDatum {
|
export interface MetaDatum {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Totals {
|
export interface Totals {
|
||||||
total_items: string;
|
total_items: string;
|
||||||
total_items_tax: string;
|
total_items_tax: string;
|
||||||
total_fees: string;
|
total_fees: string;
|
||||||
total_fees_tax: string;
|
total_fees_tax: string;
|
||||||
total_discount: string;
|
total_discount: string;
|
||||||
total_discount_tax: string;
|
total_discount_tax: string;
|
||||||
total_shipping: string;
|
total_shipping: string;
|
||||||
total_shipping_tax: string;
|
total_shipping_tax: string;
|
||||||
total_price: string;
|
total_price: string;
|
||||||
total_tax: string;
|
total_tax: string;
|
||||||
tax_lines: TaxLine[];
|
tax_lines: TaxLine[];
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
currency_symbol: string;
|
currency_symbol: string;
|
||||||
currency_minor_unit: number;
|
currency_minor_unit: number;
|
||||||
currency_decimal_separator: string;
|
currency_decimal_separator: string;
|
||||||
currency_thousand_separator: string;
|
currency_thousand_separator: string;
|
||||||
currency_prefix: string;
|
currency_prefix: string;
|
||||||
currency_suffix: string;
|
currency_suffix: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaxLine {
|
export interface TaxLine {
|
||||||
name: string;
|
name: string;
|
||||||
price: string;
|
price: string;
|
||||||
rate: string;
|
rate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Extensions {
|
export interface Extensions {}
|
||||||
}
|
|
||||||
|
|
||||||
export interface Image {
|
export interface Image {
|
||||||
id: number;
|
id: number;
|
||||||
src: string;
|
src: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
srcset: string;
|
srcset: string;
|
||||||
sizes: string;
|
sizes: string;
|
||||||
name: string;
|
name: string;
|
||||||
alt: string;
|
alt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Prices {
|
export interface Prices {
|
||||||
price: string;
|
price: string;
|
||||||
regular_price: string;
|
regular_price: string;
|
||||||
sale_price: string;
|
sale_price: string;
|
||||||
price_range: null;
|
price_range: null;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
currency_symbol: string;
|
currency_symbol: string;
|
||||||
currency_minor_unit: number;
|
currency_minor_unit: number;
|
||||||
currency_decimal_separator: string;
|
currency_decimal_separator: string;
|
||||||
currency_thousand_separator: string;
|
currency_thousand_separator: string;
|
||||||
currency_prefix: string;
|
currency_prefix: string;
|
||||||
currency_suffix: string;
|
currency_suffix: string;
|
||||||
raw_prices: RawPrices;
|
raw_prices: RawPrices;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RawPrices {
|
export interface RawPrices {
|
||||||
precision: number;
|
precision: number;
|
||||||
price: string;
|
price: string;
|
||||||
regular_price: string;
|
regular_price: string;
|
||||||
sale_price: string;
|
sale_price: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QuantityLimits {
|
export interface QuantityLimits {
|
||||||
minimum: number;
|
minimum: number;
|
||||||
maximum: number;
|
maximum: number;
|
||||||
multiple_of: number;
|
multiple_of: number;
|
||||||
editable: boolean;
|
editable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Totals {
|
export interface Totals {
|
||||||
line_subtotal: string;
|
line_subtotal: string;
|
||||||
line_subtotal_tax: string;
|
line_subtotal_tax: string;
|
||||||
line_total: string;
|
line_total: string;
|
||||||
line_total_tax: string;
|
line_total_tax: string;
|
||||||
currency_code: string;
|
currency_code: string;
|
||||||
currency_symbol: string;
|
currency_symbol: string;
|
||||||
currency_minor_unit: number;
|
currency_minor_unit: number;
|
||||||
currency_decimal_separator: string;
|
currency_decimal_separator: string;
|
||||||
currency_thousand_separator: string;
|
currency_thousand_separator: string;
|
||||||
currency_prefix: string;
|
currency_prefix: string;
|
||||||
currency_suffix: string;
|
currency_suffix: string;
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ import crypto from 'node:crypto';
|
|||||||
import OAuth from 'oauth-1.0a';
|
import OAuth from 'oauth-1.0a';
|
||||||
import Url from 'url-parse';
|
import Url from 'url-parse';
|
||||||
import { DELETE, IWooRestApiOptions, WooRestApiEndpoint, WooRestApiMethod } from './clientOptions';
|
import { DELETE, IWooRestApiOptions, WooRestApiEndpoint, WooRestApiMethod } from './clientOptions';
|
||||||
import { CouponsParams } from './coupon';
|
import { Coupon, CouponsParams } from './coupon';
|
||||||
import { CustomersParams } from './customer';
|
import { Customer, CustomersParams } from './customer';
|
||||||
import { Order, OrdersMainParams } from './orders';
|
import { Order, OrdersMainParams } from './orders';
|
||||||
import { Product, ProductMainParams } from './product';
|
import { Product, ProductMainParams } from './product';
|
||||||
|
|
||||||
@ -27,10 +27,28 @@ export type WooRestApiParams = CouponsParams &
|
|||||||
/**
|
/**
|
||||||
* Define the response types for each endpoint.
|
* Define the response types for each endpoint.
|
||||||
*/
|
*/
|
||||||
type WooCommerceResponse<T extends WooRestApiEndpoint> =
|
type WooCommerceResponse<
|
||||||
T extends 'products' ? Product[] :
|
T extends WooRestApiEndpoint,
|
||||||
T extends 'orders' ? Order[] :
|
P extends Partial<WooRestApiParams> = {}
|
||||||
any;
|
> = 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
|
* WooCommerce REST API wrapper
|
||||||
@ -100,7 +118,7 @@ export default class WooCommerceRestApi<T extends WooRestApiOptions> {
|
|||||||
login(username: string, password: string): Promise<any> {
|
login(username: string, password: string): Promise<any> {
|
||||||
return this._request('POST', 'token', { username, password }, {}, 'jwt-auth/v1');
|
return this._request('POST', 'token', { username, password }, {}, 'jwt-auth/v1');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse params to object.
|
* Parse params to object.
|
||||||
*
|
*
|
||||||
@ -265,13 +283,13 @@ export default class WooCommerceRestApi<T extends WooRestApiOptions> {
|
|||||||
*
|
*
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
_request<T extends WooRestApiEndpoint>(
|
_request<T extends WooRestApiEndpoint, P extends Partial<WooRestApiParams>>(
|
||||||
method: WooRestApiMethod,
|
method: WooRestApiMethod,
|
||||||
endpoint: T,
|
endpoint: T,
|
||||||
data?: Record<string, unknown>,
|
data?: Record<string, unknown>,
|
||||||
params: Record<string, unknown> = {},
|
params: P = {} as P,
|
||||||
version?: string,
|
version?: string
|
||||||
): Promise<WooCommerceResponse<T>> {
|
): Promise<WooCommerceResponse<T, P>> {
|
||||||
const url = this._getUrl(endpoint, params, version);
|
const url = this._getUrl(endpoint, params, version);
|
||||||
const header: RawAxiosRequestHeaders = {
|
const header: RawAxiosRequestHeaders = {
|
||||||
Accept: 'application/json'
|
Accept: 'application/json'
|
||||||
@ -330,7 +348,7 @@ export default class WooCommerceRestApi<T extends WooRestApiOptions> {
|
|||||||
// Allow set and override Axios options.
|
// Allow set and override Axios options.
|
||||||
options = { ...options, ...this._opt.axiosConfig };
|
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}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
get<T extends WooRestApiEndpoint>(endpoint: T, params?: Partial<WooRestApiParams>): Promise<WooCommerceResponse<T>> {
|
get<T extends WooRestApiEndpoint, P extends Partial<WooRestApiParams>>(
|
||||||
return this._request('GET', endpoint, undefined, params);
|
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,
|
endpoint: T,
|
||||||
data: Pick<WooRestApiParams, 'force'>,
|
data: Pick<WooRestApiParams, 'force'>,
|
||||||
params: Pick<WooRestApiParams, 'id'>
|
params: Pick<WooRestApiParams, 'id'>
|
||||||
): Promise<WooCommerceResponse<T>> {
|
): Promise<WooCommerceResponse<T, Pick<WooRestApiParams, 'id'>>> {
|
||||||
return this._request('DELETE', endpoint, data, params);
|
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/v2"
|
||||||
// | "wc/v1"
|
// | "wc/v1"
|
||||||
// | "wc-api/v3"
|
// | "wc-api/v3"
|
||||||
// | "wc-api/v2"
|
// | "wc-api/v2"
|
||||||
// | "wc-api/v1";
|
// | "wc-api/v1";
|
||||||
export declare type WooRestApiEncoding = "utf-8" | "ascii";
|
export declare type WooRestApiEncoding = 'utf-8' | 'ascii';
|
||||||
export declare type WooRestApiMethod =
|
export declare type WooRestApiMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS';
|
||||||
| "GET"
|
|
||||||
| "POST"
|
|
||||||
| "PUT"
|
|
||||||
| "DELETE"
|
|
||||||
| "OPTIONS";
|
|
||||||
|
|
||||||
export declare type WooRestApiEndpoint =
|
export declare type WooRestApiEndpoint =
|
||||||
| "coupons"
|
| 'coupons'
|
||||||
| "customers"
|
| 'customers'
|
||||||
| "orders"
|
| 'orders'
|
||||||
| "products"
|
| 'products'
|
||||||
| "products/attributes"
|
| 'products/attributes'
|
||||||
| "products/categories"
|
| 'products/categories'
|
||||||
| "products/shipping_classes"
|
| 'products/shipping_classes'
|
||||||
| "products/tags"
|
| 'products/tags'
|
||||||
| "products/reviews"
|
| 'products/reviews'
|
||||||
| "system_status"
|
| 'system_status'
|
||||||
| "reports" // TODO: add support for reports
|
| 'reports' // TODO: add support for reports
|
||||||
| "settings" // TODO: add support for settings
|
| 'settings' // TODO: add support for settings
|
||||||
| "webhooks" // TODO: add support for webhooks
|
| 'webhooks' // TODO: add support for webhooks
|
||||||
| "shipping" // TODO: add support for shipping
|
| 'shipping' // TODO: add support for shipping
|
||||||
| "shipping_methods" // TODO: add support for shipping_methods
|
| 'shipping_methods' // TODO: add support for shipping_methods
|
||||||
| "taxes" // TODO: add support for taxes
|
| 'taxes' // TODO: add support for taxes
|
||||||
| "payment_gateways" // TODO: add support for payment_gateways
|
| 'payment_gateways' // TODO: add support for payment_gateways
|
||||||
| string; // I need to have next endpoint: "orders/<id>/notes"
|
| string; // I need to have next endpoint: "orders/<id>/notes"
|
||||||
|
|
||||||
export declare type IWooRestApiQuery = Record<string, unknown>;
|
export declare type IWooRestApiQuery = Record<string, unknown>;
|
||||||
@ -80,8 +75,7 @@ export interface IWooRestApiOptions<T> extends IWooCredentials {
|
|||||||
isHttps?: boolean;
|
isHttps?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface DELETE {
|
export interface DELETE {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
force?: boolean | string;
|
force?: boolean | string;
|
||||||
}
|
}
|
||||||
|
@ -38,4 +38,4 @@ export type Coupon_Lines = {
|
|||||||
meta_data: Partial<Meta_Data>;
|
meta_data: Partial<Meta_Data>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CouponsParams = Partial<Coupon>;
|
export type CouponsParams = Partial<Coupon>;
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { Meta_Data } from "./base";
|
import { Meta_Data } from './base';
|
||||||
import { Tax } from "./taxes";
|
import { Tax } from './taxes';
|
||||||
|
|
||||||
export type Line_Item = {
|
export type Line_Item = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
product_id: number;
|
product_id: number;
|
||||||
variation_id: number;
|
variation_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
tax_class: string;
|
tax_class: string;
|
||||||
subtotal: string;
|
subtotal: string;
|
||||||
subtotal_tax: string;
|
subtotal_tax: string;
|
||||||
total: string;
|
total: string;
|
||||||
total_tax: string;
|
total_tax: string;
|
||||||
taxes: Tax[];
|
taxes: Tax[];
|
||||||
meta_data: Meta_Data;
|
meta_data: Meta_Data;
|
||||||
sku: string;
|
sku: string;
|
||||||
price: number;
|
price: number;
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
export type Link = {
|
export type Link = {
|
||||||
self: Array<{
|
self: Array<{
|
||||||
href: string;
|
href: string;
|
||||||
}>;
|
}>;
|
||||||
collection: Array<{
|
collection: Array<{
|
||||||
href: string;
|
href: string;
|
||||||
}>;
|
}>;
|
||||||
up: Array<{
|
up: Array<{
|
||||||
href: string;
|
href: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Meta_Data } from './base';
|
import { Image, Meta_Data } from './base';
|
||||||
import { Billing } from './billing';
|
import { Billing } from './billing';
|
||||||
import { Coupon_Lines } from './coupon';
|
import { Coupon_Lines } from './coupon';
|
||||||
import { Fee_Lines } from './fee';
|
import { Fee_Lines } from './fee';
|
||||||
import { Line_Item } from './item';
|
|
||||||
import { Order_Refund_Line_Item, Refund } from './refund';
|
import { Order_Refund_Line_Item, Refund } from './refund';
|
||||||
import { Shipping, Shipping_Line } from './shipping';
|
import { Shipping, Shipping_Line } from './shipping';
|
||||||
import { Tax_Line } from './taxes';
|
import { Tax_Line } from './taxes';
|
||||||
@ -43,7 +42,7 @@ export interface Order {
|
|||||||
date_completed_gmt: string;
|
date_completed_gmt: string;
|
||||||
cart_hash: string;
|
cart_hash: string;
|
||||||
meta_data: Partial<Meta_Data>[];
|
meta_data: Partial<Meta_Data>[];
|
||||||
line_items: Partial<Line_Item>[];
|
line_items: Partial<OrderItem>[];
|
||||||
tax_lines: Partial<Tax_Line>[];
|
tax_lines: Partial<Tax_Line>[];
|
||||||
shipping_lines: Partial<Shipping_Line>[];
|
shipping_lines: Partial<Shipping_Line>[];
|
||||||
fee_lines: Partial<Fee_Lines>[];
|
fee_lines: Partial<Fee_Lines>[];
|
||||||
@ -51,6 +50,25 @@ export interface Order {
|
|||||||
refunds: Partial<Refund>[];
|
refunds: Partial<Refund>[];
|
||||||
set_paid: boolean;
|
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 {
|
export interface OrderNotes {
|
||||||
id: number;
|
id: number;
|
||||||
author: string;
|
author: string;
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { Attribute, Category, Default_Attribute, Dimension, Image, Meta_Data, Tag } from './base';
|
||||||
Attribute,
|
|
||||||
Category,
|
|
||||||
Default_Attribute,
|
|
||||||
Dimension,
|
|
||||||
Image,
|
|
||||||
Meta_Data,
|
|
||||||
Tag
|
|
||||||
} from './base';
|
|
||||||
|
|
||||||
export interface Product {
|
export interface Product {
|
||||||
id: number;
|
id: number;
|
||||||
@ -211,7 +203,6 @@ type ProductShippingClassesParams = Partial<ProductShippingClass>;
|
|||||||
type ProductTagsParams = Partial<ProductTags>;
|
type ProductTagsParams = Partial<ProductTags>;
|
||||||
type ProductReviewsParams = Partial<ProductReviews>;
|
type ProductReviewsParams = Partial<ProductReviews>;
|
||||||
|
|
||||||
|
|
||||||
export type ProductMainParams =
|
export type ProductMainParams =
|
||||||
| (ProductParams & ProductVariationsParams & ProductAttributesParams)
|
| (ProductParams & ProductVariationsParams & ProductAttributesParams)
|
||||||
| ProductAttributesTermsParams
|
| ProductAttributesTermsParams
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import { Meta_Data } from "./base";
|
import { Meta_Data } from './base';
|
||||||
|
|
||||||
export type Refund = {
|
export type Refund = {
|
||||||
id: number;
|
id: number;
|
||||||
reason: string;
|
reason: string;
|
||||||
total: string;
|
total: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Order_Refund_Line = {
|
export type Order_Refund_Line = {
|
||||||
id: number;
|
id: number;
|
||||||
total: string;
|
total: string;
|
||||||
subtotal: string;
|
subtotal: string;
|
||||||
refund_total: number;
|
refund_total: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Order_Refund_Line_Item = {
|
export type Order_Refund_Line_Item = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
product_id: number;
|
product_id: number;
|
||||||
variation_id: number;
|
variation_id: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
tax_class: string;
|
tax_class: string;
|
||||||
subtotal: string;
|
subtotal: string;
|
||||||
subtotal_tax: string;
|
subtotal_tax: string;
|
||||||
total: string;
|
total: string;
|
||||||
total_tax: string;
|
total_tax: string;
|
||||||
taxes: Partial<Order_Refund_Line>[];
|
taxes: Partial<Order_Refund_Line>[];
|
||||||
meta_data: Partial<Meta_Data>[];
|
meta_data: Partial<Meta_Data>[];
|
||||||
sku: string;
|
sku: string;
|
||||||
price: string;
|
price: string;
|
||||||
refund_total: number;
|
refund_total: number;
|
||||||
};
|
};
|
||||||
|
@ -76,4 +76,4 @@ export interface ShippingMethods {
|
|||||||
export type ShippingZonesParams = Partial<ShippingZone>;
|
export type ShippingZonesParams = Partial<ShippingZone>;
|
||||||
export type ShippingZonesLocationsParams = Partial<ShippingZoneLocation>;
|
export type ShippingZonesLocationsParams = Partial<ShippingZoneLocation>;
|
||||||
export type ShippingZonesMethodsParams = Partial<ShippingZoneMethod>;
|
export type ShippingZonesMethodsParams = Partial<ShippingZoneMethod>;
|
||||||
export type ShippingMethodsParams = Partial<ShippingMethods>;
|
export type ShippingMethodsParams = Partial<ShippingMethods>;
|
||||||
|
@ -45,4 +45,4 @@ export interface TaxClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TaxRatesParams = Partial<TaxRate>;
|
export type TaxRatesParams = Partial<TaxRate>;
|
||||||
export type TaxClassesParams = Partial<TaxClass>;
|
export type TaxClassesParams = Partial<TaxClass>;
|
||||||
|
@ -11,18 +11,18 @@ class WooCommerceStoreApiClient {
|
|||||||
constructor(baseURL: string) {
|
constructor(baseURL: string) {
|
||||||
const headers: RawAxiosRequestHeaders = {
|
const headers: RawAxiosRequestHeaders = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': '*/*',
|
Accept: '*/*'
|
||||||
};
|
};
|
||||||
|
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
headers,
|
headers
|
||||||
});
|
});
|
||||||
|
|
||||||
this.client.interceptors.response.use((response) => {
|
this.client.interceptors.response.use((response) => {
|
||||||
console.log('cart-token', response.headers['cart-token']);
|
console.log('cart-token', response.headers['cart-token']);
|
||||||
this.client.defaults.headers['cart-token'] = response.headers['cart-token'];
|
this.client.defaults.headers['cart-token'] = response.headers['cart-token'];
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -37,16 +37,22 @@ class WooCommerceStoreApiClient {
|
|||||||
return this.client.get<Cart>('/cart', { params }).then((response) => response.data);
|
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);
|
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);
|
return this.client.post<Cart>('/cart/update-item', payload).then((response) => response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeFromCart(payload: { key: string | number }): Promise<Cart> {
|
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 = {
|
const option: WooRestApiOptions = {
|
||||||
url: process.env.WOOCOMMERCE_URL ?? "http://wordpress.localhost",
|
url: process.env.WOOCOMMERCE_URL ?? 'http://wordpress.localhost',
|
||||||
consumerKey: process.env.WOOCOMMERCE_CONSUMER_KEY ?? "ck_1fb0a3c9b50ae813c31c7effc086a809d8416d90",
|
consumerKey:
|
||||||
consumerSecret: process.env.WOOCOMMERCE_CONSUMER_SECRET ?? "cs_ee4f1c9e061d07a7cb6025b69d414189a9157e20",
|
process.env.WOOCOMMERCE_CONSUMER_KEY ?? 'ck_1fb0a3c9b50ae813c31c7effc086a809d8416d90',
|
||||||
isHttps: false,
|
consumerSecret:
|
||||||
version: "wc/v3",
|
process.env.WOOCOMMERCE_CONSUMER_SECRET ?? 'cs_ee4f1c9e061d07a7cb6025b69d414189a9157e20',
|
||||||
queryStringAuth: false // Force Basic Authentication as query string true and using under
|
isHttps: false,
|
||||||
}
|
version: 'wc/v3',
|
||||||
export const woocommerce = new WooCommerceRestApi(option);
|
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'],
|
formats: ['image/avif', 'image/webp'],
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
protocol: "http",
|
protocol: 'http',
|
||||||
hostname: "**",
|
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"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
9
types/next-auth.d.ts
vendored
9
types/next-auth.d.ts
vendored
@ -4,14 +4,16 @@ import 'next-auth/jwt';
|
|||||||
declare module 'next-auth' {
|
declare module 'next-auth' {
|
||||||
interface Session {
|
interface Session {
|
||||||
user: {
|
user: {
|
||||||
|
store_id: number;
|
||||||
token: string;
|
token: string;
|
||||||
user_email: string;
|
user_email: string;
|
||||||
user_nicename: string;
|
user_nicename: string;
|
||||||
user_display_name: string;
|
user_display_name: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
|
store_id: number;
|
||||||
token: string;
|
token: string;
|
||||||
user_email: string;
|
user_email: string;
|
||||||
user_nicename: string;
|
user_nicename: string;
|
||||||
@ -22,10 +24,11 @@ declare module 'next-auth' {
|
|||||||
declare module 'next-auth/jwt' {
|
declare module 'next-auth/jwt' {
|
||||||
interface JWT {
|
interface JWT {
|
||||||
user: {
|
user: {
|
||||||
|
store_id: number;
|
||||||
token: string;
|
token: string;
|
||||||
user_email: string;
|
user_email: string;
|
||||||
user_nicename: string;
|
user_nicename: string;
|
||||||
user_display_name: string;
|
user_display_name: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user