mirror of
https://github.com/vercel/commerce.git
synced 2025-06-28 09:21:22 +00:00
refactor: shipping form and preferences profile
This commit is contained in:
parent
177ab89884
commit
ae6ec92608
@ -3,10 +3,12 @@
|
|||||||
import { Accordion, AccordionItem, Checkbox, Radio, RadioGroup } from '@nextui-org/react';
|
import { Accordion, AccordionItem, Checkbox, Radio, RadioGroup } from '@nextui-org/react';
|
||||||
import { useCart } from 'components/cart/cart-context';
|
import { useCart } from 'components/cart/cart-context';
|
||||||
import CartItemView from 'components/cart/cart-item';
|
import CartItemView from 'components/cart/cart-item';
|
||||||
|
import { useCheckout } from 'components/checkout/checkout-provider';
|
||||||
|
import ShippingForm from 'components/checkout/form';
|
||||||
import Price from 'components/price';
|
import Price from 'components/price';
|
||||||
import ShippingForm from 'components/shipping/form';
|
import { Billing } from 'lib/woocomerce/models/billing';
|
||||||
import { PaymentGateways } from 'lib/woocomerce/models/payment';
|
import { PaymentGateways } from 'lib/woocomerce/models/payment';
|
||||||
import { OrderPayload } from 'lib/woocomerce/storeApi';
|
import { Shipping } from 'lib/woocomerce/models/shipping';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@ -17,7 +19,7 @@ const shippingSchema = z.object({
|
|||||||
address_1: z.string().min(3),
|
address_1: z.string().min(3),
|
||||||
address_2: z.string().optional(),
|
address_2: z.string().optional(),
|
||||||
city: z.string().min(3),
|
city: z.string().min(3),
|
||||||
state: z.string().min(3),
|
state: z.string().max(2).min(2),
|
||||||
postcode: z.string().min(3),
|
postcode: z.string().min(3),
|
||||||
country: z.string().min(3),
|
country: z.string().min(3),
|
||||||
company: z.string().optional()
|
company: z.string().optional()
|
||||||
@ -26,36 +28,8 @@ const shippingSchema = z.object({
|
|||||||
export default function CheckoutPage() {
|
export default function CheckoutPage() {
|
||||||
const { cart } = useCart();
|
const { cart } = useCart();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { checkout, setShipping, setBilling, setPayment } = useCheckout();
|
||||||
const initialState: OrderPayload = {
|
const [error, setError] = useState<Shipping | Billing | undefined>(undefined);
|
||||||
shipping_address: {
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
company: '',
|
|
||||||
address_1: '',
|
|
||||||
address_2: '',
|
|
||||||
city: '',
|
|
||||||
state: '',
|
|
||||||
postcode: '',
|
|
||||||
country: ''
|
|
||||||
},
|
|
||||||
billing_address: {
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
company: '',
|
|
||||||
email: '',
|
|
||||||
phone: '',
|
|
||||||
address_1: '',
|
|
||||||
address_2: '',
|
|
||||||
city: '',
|
|
||||||
state: '',
|
|
||||||
postcode: '',
|
|
||||||
country: ''
|
|
||||||
},
|
|
||||||
payment_method: '',
|
|
||||||
payment_data: []
|
|
||||||
};
|
|
||||||
const [formData, setFormData] = useState(initialState);
|
|
||||||
const [sameBilling, setSameBilling] = useState(true);
|
const [sameBilling, setSameBilling] = useState(true);
|
||||||
const [paymentGateways, setPaymentGateways] = useState<PaymentGateways[]>([]);
|
const [paymentGateways, setPaymentGateways] = useState<PaymentGateways[]>([]);
|
||||||
|
|
||||||
@ -67,38 +41,38 @@ export default function CheckoutPage() {
|
|||||||
fetchPaymentGateways();
|
fetchPaymentGateways();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChangeShipping = (e: any) => {
|
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
setFormData({ ...formData, shipping_address: e });
|
console.log('submit');
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
if (sameBilling) {
|
if (sameBilling) {
|
||||||
setFormData({
|
setBilling({ ...checkout?.billing, ...checkout?.shipping } as Billing);
|
||||||
...formData,
|
}
|
||||||
billing_address: { ...formData.billing_address, ...e }
|
if (!checkout) {
|
||||||
});
|
return;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeBilling = (e: any) => {
|
shippingSchema.parse(checkout.shipping);
|
||||||
setFormData({ ...formData, billing_address: e });
|
router.push('/checkout/review');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
const errorObj: Record<string, string> = {};
|
||||||
|
error.errors.forEach((err) => {
|
||||||
|
const key = err.path[0] as string;
|
||||||
|
errorObj[key] = err.message;
|
||||||
|
});
|
||||||
|
console.log(errorObj);
|
||||||
|
setError(errorObj as Shipping);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mx-auto grid h-full gap-4 px-4 pb-4">
|
<section className="mx-auto grid h-full gap-4 px-4 pb-4">
|
||||||
<p>Checkout</p>
|
<p>Checkout</p>
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={onSubmit}
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
if (sameBilling) {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
billing_address: { ...formData.billing_address, ...formData.shipping_address }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
shippingSchema.parse(formData.shipping_address);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-black"
|
className="rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-black"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4 md:flex-row">
|
<div className="flex flex-col gap-4 md:flex-row">
|
||||||
@ -140,13 +114,32 @@ export default function CheckoutPage() {
|
|||||||
className="text-white sm:w-full md:w-2/3"
|
className="text-white sm:w-full md:w-2/3"
|
||||||
>
|
>
|
||||||
<AccordionItem key="1" title="Shipping Info" className="text-white">
|
<AccordionItem key="1" title="Shipping Info" className="text-white">
|
||||||
<ShippingForm handleChangeAction={handleChangeShipping} />
|
<ShippingForm
|
||||||
|
onChangeInput={(e) => {
|
||||||
|
const updatedShipping = {
|
||||||
|
...checkout?.shipping,
|
||||||
|
[e.target.name]: e.target.value
|
||||||
|
} as Shipping;
|
||||||
|
setShipping(updatedShipping as Shipping);
|
||||||
|
setError(undefined);
|
||||||
|
}}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
<Checkbox defaultSelected onValueChange={(v) => setSameBilling(v)} className="mt-2">
|
<Checkbox defaultSelected onValueChange={(v) => setSameBilling(v)} className="mt-2">
|
||||||
Use same address for billing?
|
Hai bisogno di fatturazione?
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem key="2" title="Billing Info" className="text-white">
|
<AccordionItem key="2" title="Billing Info" className="text-white">
|
||||||
<ShippingForm handleChangeAction={handleChangeBilling} />
|
<ShippingForm
|
||||||
|
onChangeInput={(e) => {
|
||||||
|
const updatedBilling = {
|
||||||
|
...checkout?.shipping,
|
||||||
|
[e.target.name]: e.target.value
|
||||||
|
} as Billing;
|
||||||
|
setBilling(updatedBilling);
|
||||||
|
setError(undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem key="3" title="Payment" className="text-white">
|
<AccordionItem key="3" title="Payment" className="text-white">
|
||||||
<div className="flex flex-col justify-between overflow-hidden">
|
<div className="flex flex-col justify-between overflow-hidden">
|
||||||
@ -160,11 +153,7 @@ export default function CheckoutPage() {
|
|||||||
key={gateway.id}
|
key={gateway.id}
|
||||||
value={gateway.id}
|
value={gateway.id}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFormData((prev) => ({
|
setPayment(e.target.value, gateway.title);
|
||||||
...prev,
|
|
||||||
payment_method: e.target.value,
|
|
||||||
payment_title: gateway.title
|
|
||||||
}));
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{gateway.title}
|
{gateway.title}
|
||||||
|
49
app/checkout/review/page.tsx
Normal file
49
app/checkout/review/page.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
'use client';
|
||||||
|
import { Button } from '@nextui-org/react';
|
||||||
|
import { useCart } from 'components/cart/cart-context';
|
||||||
|
import CartItemView from 'components/cart/cart-item';
|
||||||
|
import { useCheckout } from 'components/checkout/checkout-provider';
|
||||||
|
import Price from 'components/price';
|
||||||
|
|
||||||
|
export default function CheckoutReview() {
|
||||||
|
const { cart } = useCart();
|
||||||
|
const { checkout } = useCheckout();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="mt-4 grid w-full gap-4 px-4 pb-4">
|
||||||
|
<h1 className="text-2xl font-bold">Riassunto</h1>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{cart?.items.map((item, i) => (
|
||||||
|
<li
|
||||||
|
key={i}
|
||||||
|
className="flex w-full flex-col border-b border-neutral-300 dark:border-neutral-700"
|
||||||
|
>
|
||||||
|
<CartItemView item={item} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<span className="mt-4 text-lg font-bold">Totale</span>
|
||||||
|
<Price
|
||||||
|
amount={cart?.totals?.total_price ?? '0'}
|
||||||
|
currencyCode={cart?.totals.currency_code ?? 'EUR'}
|
||||||
|
needSplit
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="mt-4 text-lg font-bold">Indirizzo di spedizione</span>
|
||||||
|
<span>
|
||||||
|
{checkout?.shipping?.first_name} {checkout?.shipping?.last_name}
|
||||||
|
</span>
|
||||||
|
<span>{checkout?.shipping?.address_1}</span>
|
||||||
|
<span>
|
||||||
|
{checkout?.shipping?.city} {checkout?.shipping?.state} {checkout?.shipping?.postcode}
|
||||||
|
</span>
|
||||||
|
<span>{checkout?.shipping?.country}</span>
|
||||||
|
<span className="mt-4 text-lg font-bold">Metodo di pagamento</span>
|
||||||
|
<span>{checkout?.payment_method}</span>
|
||||||
|
|
||||||
|
<Button title="Vai al pagamento" color="primary" className="text-white">
|
||||||
|
Vai al pagamento
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
@ -5,7 +5,9 @@ import { woocommerce } from 'lib/woocomerce/woocommerce';
|
|||||||
export default async function ProductPage(props: { params: Promise<{ slug: string }> }) {
|
export default async function ProductPage(props: { params: Promise<{ slug: string }> }) {
|
||||||
const slug = (await props.params).slug;
|
const slug = (await props.params).slug;
|
||||||
const category = (await woocommerce.get('products/categories', { slug }))?.[0];
|
const category = (await woocommerce.get('products/categories', { slug }))?.[0];
|
||||||
const products: Product[] = await woocommerce.get('products', { category: category.id.toString() });
|
const products: Product[] = await woocommerce.get('products', {
|
||||||
|
category: category.id.toString()
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { CartProvider } from 'components/cart/cart-context';
|
import { CartProvider } from 'components/cart/cart-context';
|
||||||
|
import { CheckoutProvider } from 'components/checkout/checkout-provider';
|
||||||
import Footer from 'components/layout/footer';
|
import Footer from 'components/layout/footer';
|
||||||
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';
|
||||||
@ -42,12 +43,14 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|||||||
<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>
|
<CartProvider>
|
||||||
|
<CheckoutProvider>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main>
|
<main>
|
||||||
{children}
|
{children}
|
||||||
<Toaster closeButton />
|
<Toaster closeButton />
|
||||||
<WelcomeToast />
|
<WelcomeToast />
|
||||||
</main>
|
</main>
|
||||||
|
</CheckoutProvider>
|
||||||
</CartProvider>
|
</CartProvider>
|
||||||
<Footer />
|
<Footer />
|
||||||
</NextAuthProvider>
|
</NextAuthProvider>
|
||||||
|
@ -2,10 +2,12 @@ import Link from 'next/link';
|
|||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="p-4">
|
||||||
<h2>Not Found</h2>
|
<h2>Not Found</h2>
|
||||||
<p>Could not find requested resource</p>
|
<p>Could not find requested resource</p>
|
||||||
<Link href="/">Return Home</Link>
|
<Link href="/" className="hover:text-indigo-500">
|
||||||
|
Return Home
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import { ProductDescription } from 'components/product/product-description';
|
|||||||
import { VariantSelector } from 'components/product/variant-selector';
|
import { VariantSelector } from 'components/product/variant-selector';
|
||||||
import Prose from 'components/prose';
|
import Prose from 'components/prose';
|
||||||
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
||||||
import { isStrinInteger } from 'lib/utils';
|
|
||||||
import { Image } from 'lib/woocomerce/models/base';
|
import { Image } from 'lib/woocomerce/models/base';
|
||||||
import { Product, ProductVariations } from 'lib/woocomerce/models/product';
|
import { Product, ProductVariations } from 'lib/woocomerce/models/product';
|
||||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||||
@ -19,12 +18,9 @@ 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;
|
||||||
let product: Product | undefined = undefined;
|
const product: Product | undefined = (
|
||||||
if (isStrinInteger(params.name)) {
|
await woocommerce.get('products', { slug: params.name })
|
||||||
product = await woocommerce.get(`products/${params.name}`);
|
)?.[0];
|
||||||
} else {
|
|
||||||
product = (await woocommerce.get('products', { slug: params.name }))?.[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!product) return notFound();
|
if (!product) return notFound();
|
||||||
|
|
||||||
@ -83,12 +79,9 @@ async function RelatedProducts({ product }: { product: Product }) {
|
|||||||
|
|
||||||
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;
|
||||||
let product: Product | undefined = undefined;
|
const product: Product | undefined = (
|
||||||
if (isStrinInteger(params.name)) {
|
await woocommerce.get('products', { slug: params.name })
|
||||||
product = await woocommerce.get(`products/${params.name}`);
|
)?.[0];
|
||||||
} else {
|
|
||||||
product = (await woocommerce.get('products', { slug: params.name }))?.[0];
|
|
||||||
}
|
|
||||||
let variations: ProductVariations[] = [];
|
let variations: ProductVariations[] = [];
|
||||||
if (product?.variations?.length) {
|
if (product?.variations?.length) {
|
||||||
variations = await woocommerce.get(`products/${product?.id}/variations`);
|
variations = await woocommerce.get(`products/${product?.id}/variations`);
|
||||||
@ -96,6 +89,10 @@ export default async function ProductPage(props: { params: Promise<{ name: strin
|
|||||||
|
|
||||||
if (!product) return notFound();
|
if (!product) return notFound();
|
||||||
|
|
||||||
|
const relatedProducts = await Promise.all(
|
||||||
|
product.related_ids?.map(async (id) => woocommerce.get(`products/${id}`)) || []
|
||||||
|
);
|
||||||
|
|
||||||
const productJsonLd = {
|
const productJsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'Product',
|
'@type': 'Product',
|
||||||
|
@ -1,37 +1,18 @@
|
|||||||
import Price from 'components/price';
|
import Price from 'components/price';
|
||||||
import { authOptions } from 'lib/auth/config';
|
|
||||||
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
import { woocommerce } from 'lib/woocomerce/woocommerce';
|
||||||
import { getServerSession } from 'next-auth';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
export default async function OrderPage(props: { params: Promise<{ id: number }> }) {
|
export default async function OrderPage(props: { params: Promise<{ id: number }> }) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const data = await getServerSession(authOptions);
|
|
||||||
try {
|
|
||||||
const order = await woocommerce.get('orders', { id: params.id });
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
const order = await woocommerce.get('orders', { id: params.id });
|
const order = await woocommerce.get('orders', { id: params.id });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mt-4 grid max-w-screen-2xl gap-4 px-4 pb-4">
|
<section className="mt-4 grid w-full gap-4 px-4 pb-4">
|
||||||
<h1 className="text-2xl font-bold">Order</h1>
|
<h1 className="text-2xl font-bold">Order</h1>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label
|
<span className="text-lg font-bold">Ordine #{order.number}</span>
|
||||||
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>
|
</div>
|
||||||
{order.line_items.map((item, i) => (
|
{order.line_items.map((item, i) => (
|
||||||
<li
|
<li
|
||||||
@ -49,7 +30,7 @@ export default async function OrderPage(props: { params: Promise<{ id: number }>
|
|||||||
src={item.image?.src || ''}
|
src={item.image?.src || ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col text-base">
|
<div className="ms-4 flex flex-1 flex-col text-base">
|
||||||
<span className="leading-tight">{item.name}</span>
|
<span className="leading-tight">{item.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -63,21 +44,22 @@ export default async function OrderPage(props: { params: Promise<{ id: number }>
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<div className="mt-4">
|
|
||||||
<label
|
<span className="mt-4 text-lg font-bold">Dettagli</span>
|
||||||
htmlFor="total"
|
<span>
|
||||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
|
Totale {order.total} {order.currency}
|
||||||
>
|
</span>
|
||||||
Total
|
<span>Metodo di pagamento: {order.payment_method}</span>
|
||||||
</label>
|
|
||||||
<input
|
<span className="mt-4 text-lg font-bold">Indirizzo di spedizione</span>
|
||||||
type="text"
|
<span>
|
||||||
id="total"
|
{order.shipping.first_name} {order.shipping.last_name}
|
||||||
value={order.total}
|
</span>
|
||||||
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"
|
<span>{order.shipping.address_1}</span>
|
||||||
disabled
|
<span>
|
||||||
/>
|
{order.shipping.city} {order.shipping.state} {order.shipping.postcode}
|
||||||
</div>
|
</span>
|
||||||
|
<span>{order.shipping.country}</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
@ -7,33 +7,33 @@ import Link from 'next/link';
|
|||||||
|
|
||||||
export default async function OrdersPage() {
|
export default async function OrdersPage() {
|
||||||
const data = await getServerSession(authOptions);
|
const data = await getServerSession(authOptions);
|
||||||
const orders = await woocommerce.get('orders');
|
const orders = await woocommerce.get('orders', { customer: data?.user?.store_id });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="mt-4 grid max-w-screen-2xl gap-4 px-4 pb-4">
|
<section className="mt-4 grid w-full gap-4 px-4 pb-4">
|
||||||
<h1 className="text-2xl font-bold">Orders</h1>
|
<h1 className="text-2xl font-bold">Orders</h1>
|
||||||
{orders.map((order) => (
|
{orders.map((order) => (
|
||||||
<div key={order.id} className="flex flex-col rounded border border-neutral-300 dark:border-neutral-700 p-4">
|
<div
|
||||||
<div className="grid grid-cols-3 gap-6">
|
key={order.id}
|
||||||
|
className="flex flex-col rounded border border-neutral-300 p-4 dark:border-neutral-700"
|
||||||
|
>
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span>ID ORDINE:</span>
|
<span>ID ORDINE:</span>
|
||||||
<span>{order.id}</span>
|
<span className="font-bold">{order.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span>EFFETTUATO IL:</span>
|
<span>EFFETTUATO IL:</span>
|
||||||
<span>{new Date(order.date_created).toLocaleDateString()}</span>
|
<span className="font-bold">{new Date(order.date_created).toLocaleDateString()}</span>
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span>TOTALE:</span>
|
|
||||||
<span>{order.total} {order.currency}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
{order.line_items.map((item, i) => (
|
{order.line_items.map((item, i) => (
|
||||||
<li
|
<li key={i} className="flex w-full flex-col">
|
||||||
key={i}
|
<Link
|
||||||
className="flex w-full flex-col"
|
href={`/product/${item.product_id}`}
|
||||||
|
className="flex w-full flex-row justify-between px-1 py-4"
|
||||||
>
|
>
|
||||||
<Link href={`/product/${item.product_id}`} className="flex w-full flex-row justify-between px-1 py-4">
|
|
||||||
<div className="flex flex-row">
|
<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">
|
<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
|
<Image
|
||||||
@ -44,7 +44,7 @@ export default async function OrdersPage() {
|
|||||||
src={item.image?.src || ''}
|
src={item.image?.src || ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col ms-4 text-base hover:underline">
|
<div className="ms-4 flex flex-1 flex-col text-base hover:underline">
|
||||||
<span className="leading-tight">{item.name}</span>
|
<span className="leading-tight">{item.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -58,8 +58,11 @@ export default async function OrdersPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<div className="flex flex-row-reverse mt-4">
|
</div>
|
||||||
<Link href={`/profile/orders/${order.id}`} className="hover:text-indigo-500">Vedi dettagli</Link>
|
<div className="mt-4 flex flex-row-reverse">
|
||||||
|
<Link href={`/profile/orders/${order.id}`} className="hover:text-indigo-500">
|
||||||
|
Vedi dettagli
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
3
app/profile/@user/preferences/page.tsx
Normal file
3
app/profile/@user/preferences/page.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default async function PreferencesArea() {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { CubeIcon, UserCircleIcon } from '@heroicons/react/24/outline';
|
import { Cog8ToothIcon, CubeIcon, UserCircleIcon } from '@heroicons/react/24/outline';
|
||||||
import { Avatar } from '@nextui-org/react';
|
import { Avatar } from '@nextui-org/react';
|
||||||
import LogoutButton from 'components/button/logout';
|
import LogoutButton from 'components/button/logout';
|
||||||
import { Customer } from 'lib/woocomerce/models/customer';
|
import { Customer } from 'lib/woocomerce/models/customer';
|
||||||
@ -54,6 +54,14 @@ export default function ProfileLayout({ user }: { user: React.ReactNode }) {
|
|||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex-start mt-2 flex">
|
||||||
|
<Link href={`/profile/preferences`} className="hover:text-indigo-500">
|
||||||
|
<button type="button" className="flex flex-row items-center rounded-md py-1">
|
||||||
|
<Cog8ToothIcon className="me-2 h-4" />
|
||||||
|
Preferences
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<LogoutButton />
|
<LogoutButton />
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,11 +7,13 @@ import { EditItemQuantityButton } from './edit-item-quantity-button';
|
|||||||
|
|
||||||
export default function CartItemView({
|
export default function CartItemView({
|
||||||
item,
|
item,
|
||||||
|
quantity = 1,
|
||||||
deletable = false,
|
deletable = false,
|
||||||
editable = false,
|
editable = false,
|
||||||
closeCart = () => {}
|
closeCart = () => {}
|
||||||
}: {
|
}: {
|
||||||
item: CartItem;
|
item: CartItem;
|
||||||
|
quantity?: number;
|
||||||
deletable?: boolean;
|
deletable?: boolean;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
closeCart?: () => void;
|
closeCart?: () => void;
|
||||||
@ -45,6 +47,11 @@ export default function CartItemView({
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-16 flex-col justify-between">
|
<div className="flex h-16 flex-col justify-between">
|
||||||
|
{item.quantity > 1 && (
|
||||||
|
<span className="w-full text-sm">
|
||||||
|
<span className="w-full text-sm">x{item.quantity}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<Price
|
<Price
|
||||||
className="flex justify-end space-y-2 text-right text-sm"
|
className="flex justify-end space-y-2 text-right text-sm"
|
||||||
amount={item.prices?.price}
|
amount={item.prices?.price}
|
||||||
|
93
components/checkout/checkout-provider.tsx
Normal file
93
components/checkout/checkout-provider.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { NextUIProvider } from '@nextui-org/react';
|
||||||
|
import { Billing } from 'lib/woocomerce/models/billing';
|
||||||
|
import { Shipping } from 'lib/woocomerce/models/shipping';
|
||||||
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
|
|
||||||
|
type Checkout = {
|
||||||
|
shipping: Shipping;
|
||||||
|
billing: Billing;
|
||||||
|
payment_method: string;
|
||||||
|
payment_method_title: string;
|
||||||
|
};
|
||||||
|
type CheckoutContextType = {
|
||||||
|
checkout: Checkout | undefined;
|
||||||
|
setShipping: (shipping: Shipping) => void;
|
||||||
|
setBilling: (billing: Billing) => void;
|
||||||
|
setPayment: (paymentMethod: string, paymentMethodTitle: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState: Checkout = {
|
||||||
|
shipping: {
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
address_1: '',
|
||||||
|
address_2: '',
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
postcode: '',
|
||||||
|
country: '',
|
||||||
|
company: ''
|
||||||
|
},
|
||||||
|
billing: {
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
address_1: '',
|
||||||
|
address_2: '',
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
postcode: '',
|
||||||
|
country: '',
|
||||||
|
company: '',
|
||||||
|
phone: '',
|
||||||
|
email: ''
|
||||||
|
},
|
||||||
|
payment_method: '',
|
||||||
|
payment_method_title: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const CheckoutContext = createContext<CheckoutContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function CheckoutProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [checkout, setCheckout] = useState<Checkout>(initialState);
|
||||||
|
|
||||||
|
const setShipping = (shipping: Shipping) => {
|
||||||
|
setCheckout({ ...checkout, shipping: { ...checkout.shipping, ...shipping } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBilling = (billing: Billing) => {
|
||||||
|
setCheckout({ ...checkout, billing: { ...checkout.billing, ...billing } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPayment = (paymentMethod: string, paymentMethodTitle: string) => {
|
||||||
|
setCheckout({
|
||||||
|
...checkout,
|
||||||
|
payment_method: paymentMethod,
|
||||||
|
payment_method_title: paymentMethodTitle
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NextUIProvider>
|
||||||
|
<CheckoutContext.Provider
|
||||||
|
value={{
|
||||||
|
checkout,
|
||||||
|
setShipping,
|
||||||
|
setBilling,
|
||||||
|
setPayment
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CheckoutContext.Provider>
|
||||||
|
</NextUIProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCheckout() {
|
||||||
|
const context = useContext(CheckoutContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useCheckout must be used within a CheckoutProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
@ -3,41 +3,27 @@ import { Avatar, Input, Select, SelectItem } from '@nextui-org/react';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { getCountries } from 'lib/utils';
|
import { getCountries } from 'lib/utils';
|
||||||
import { Billing } from 'lib/woocomerce/models/billing';
|
import { Billing } from 'lib/woocomerce/models/billing';
|
||||||
import { useState } from 'react';
|
import { Shipping } from 'lib/woocomerce/models/shipping';
|
||||||
|
import { useCheckout } from './checkout-provider';
|
||||||
|
|
||||||
const optionalFields = ['company'];
|
const optionalFields = ['company', 'address_2'];
|
||||||
|
|
||||||
export default function ShippingForm({
|
export default function ShippingForm({
|
||||||
className,
|
className,
|
||||||
title,
|
title,
|
||||||
handleChangeAction
|
onChangeInput,
|
||||||
|
error
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
handleChangeAction?: (data: Billing) => void;
|
onChangeInput?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
error?: Shipping | Billing | undefined;
|
||||||
}) {
|
}) {
|
||||||
const countries = getCountries();
|
const countries = getCountries();
|
||||||
const initialState: Billing = {
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
address_1: '',
|
|
||||||
address_2: '',
|
|
||||||
city: '',
|
|
||||||
state: '',
|
|
||||||
postcode: '',
|
|
||||||
country: '',
|
|
||||||
company: '',
|
|
||||||
phone: '',
|
|
||||||
email: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState(initialState);
|
const { checkout } = useCheckout();
|
||||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const newData = { ...formData, [e.target.name]: e.target.value };
|
onChangeInput?.(e);
|
||||||
setFormData(newData);
|
|
||||||
if (handleChangeAction) {
|
|
||||||
handleChangeAction(newData);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLabel = (key: string) => key.charAt(0).toUpperCase() + key.slice(1).replace('_', ' ');
|
const getLabel = (key: string) => key.charAt(0).toUpperCase() + key.slice(1).replace('_', ' ');
|
||||||
@ -45,7 +31,7 @@ export default function ShippingForm({
|
|||||||
return (
|
return (
|
||||||
<div className={clsx('flex flex-col', className)}>
|
<div className={clsx('flex flex-col', className)}>
|
||||||
{title && <h2 className="mt-2 text-2xl font-bold">{title}</h2>}
|
{title && <h2 className="mt-2 text-2xl font-bold">{title}</h2>}
|
||||||
{Object.entries(formData)
|
{Object.entries(checkout?.shipping || {})
|
||||||
.filter(([key]) => key !== 'country')
|
.filter(([key]) => key !== 'country')
|
||||||
.map(([key, value], index) => (
|
.map(([key, value], index) => (
|
||||||
<div className={index !== 0 ? 'mt-4' : ''} key={key}>
|
<div className={index !== 0 ? 'mt-4' : ''} key={key}>
|
||||||
@ -58,6 +44,8 @@ export default function ShippingForm({
|
|||||||
size="md"
|
size="md"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
label={getLabel(key)}
|
label={getLabel(key)}
|
||||||
|
isInvalid={error && !!(error as any)[key]}
|
||||||
|
errorMessage={error && (error as any)[key]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -74,13 +62,13 @@ export default function ShippingForm({
|
|||||||
isRequired
|
isRequired
|
||||||
name="country"
|
name="country"
|
||||||
aria-label="Select a country"
|
aria-label="Select a country"
|
||||||
value={formData.country}
|
value={checkout?.shipping.country}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onChange({
|
onChange({
|
||||||
target: {
|
target: {
|
||||||
name: 'country',
|
name: 'country',
|
||||||
value: event.target.value,
|
value: event.target.value
|
||||||
} as unknown as EventTarget & HTMLInputElement,
|
} as unknown as EventTarget & HTMLInputElement
|
||||||
} as React.ChangeEvent<HTMLInputElement>)
|
} as React.ChangeEvent<HTMLInputElement>)
|
||||||
}
|
}
|
||||||
>
|
>
|
@ -1,5 +1,3 @@
|
|||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
const Price = ({
|
const Price = ({
|
||||||
amount,
|
amount,
|
||||||
className,
|
className,
|
||||||
@ -20,7 +18,6 @@ const Price = ({
|
|||||||
currency: currencyCode,
|
currency: currencyCode,
|
||||||
currencyDisplay: 'narrowSymbol'
|
currencyDisplay: 'narrowSymbol'
|
||||||
}).format(parseFloat(amount) / (needSplit ? 100 : 1))}`}
|
}).format(parseFloat(amount) / (needSplit ? 100 : 1))}`}
|
||||||
<span className={clsx('ml-1 inline', currencyCodeClassName)}>{`${currencyCode}`}</span>
|
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -21,4 +21,4 @@ export const isStrinInteger = (value: string) => {
|
|||||||
const parsed = parseInt(value, 10);
|
const parsed = parseInt(value, 10);
|
||||||
|
|
||||||
return !isNaN(parsed) && parsed.toString() === value.trim();
|
return !isNaN(parsed) && parsed.toString() === value.trim();
|
||||||
}
|
};
|
||||||
|
@ -27,6 +27,7 @@ export interface Order {
|
|||||||
total: string;
|
total: string;
|
||||||
total_tax: string;
|
total_tax: string;
|
||||||
prices_include_tax: boolean;
|
prices_include_tax: boolean;
|
||||||
|
customer: number;
|
||||||
customer_id: number;
|
customer_id: number;
|
||||||
customer_ip_address: string;
|
customer_ip_address: string;
|
||||||
customer_user_agent: string;
|
customer_user_agent: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user