From a16571aca0185d79dda62f30d6fcd8dba7edcbb4 Mon Sep 17 00:00:00 2001 From: paolosantarsiero Date: Wed, 15 Jan 2025 12:57:24 +0100 Subject: [PATCH] refactor: all components --- app/api/cart/route.ts | 2 +- app/api/payments/route.ts | 14 ++ app/article/[id]/page.tsx | 20 +++ app/checkout/page.tsx | 155 +++++++++++++------ app/globals.css | 6 - app/login/page.tsx | 49 +++--- app/page.tsx | 127 +++++++++------ app/product/[name]/page.tsx | 65 +++++--- app/profile/{ => @user}/orders/[id]/page.tsx | 2 +- app/profile/{ => @user}/orders/page.tsx | 0 app/profile/{ => @user}/page.tsx | 0 app/profile/layout.tsx | 21 ++- app/search/page.tsx | 6 +- app/signup/page.tsx | 114 +++++++------- components/button/logout.tsx | 2 +- components/cart/modal.tsx | 12 +- components/checkout/payments-form.tsx | 3 + components/grid/three-items.tsx | 5 +- components/layout/navbar/search.tsx | 9 +- components/layout/search/filter/index.tsx | 34 +++- components/product/product-card.tsx | 37 +++++ components/product/product-suspense.tsx | 33 ++++ components/shipping/form.tsx | 104 ++++--------- 23 files changed, 498 insertions(+), 322 deletions(-) create mode 100644 app/api/payments/route.ts create mode 100644 app/article/[id]/page.tsx rename app/profile/{ => @user}/orders/[id]/page.tsx (97%) rename app/profile/{ => @user}/orders/page.tsx (100%) rename app/profile/{ => @user}/page.tsx (100%) create mode 100644 components/checkout/payments-form.tsx create mode 100644 components/product/product-card.tsx create mode 100644 components/product/product-suspense.tsx diff --git a/app/api/cart/route.ts b/app/api/cart/route.ts index a7f34a724..f1d501675 100644 --- a/app/api/cart/route.ts +++ b/app/api/cart/route.ts @@ -10,7 +10,7 @@ export async function GET(req: NextRequest) { const cart = await storeApi.getCart(); return NextResponse.json(cart, { status: 200 }); } catch (error) { - return NextResponse.json({ error: 'Failed to fetch cart' }, { status: 500 }); + return NextResponse.json({ error: 'Failed to fetch cart', message: error }, { status: 500 }); } } diff --git a/app/api/payments/route.ts b/app/api/payments/route.ts new file mode 100644 index 000000000..5f8287d7c --- /dev/null +++ b/app/api/payments/route.ts @@ -0,0 +1,14 @@ +import { PaymentGateways } from 'lib/woocomerce/models/payment'; +import { woocommerce } from 'lib/woocomerce/woocommerce'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(req: NextRequest) { + try { + const payments = await woocommerce + .get('payment_gateways') + .then((gateways) => gateways.filter((gateway: PaymentGateways) => gateway.enabled)); + return NextResponse.json(payments, { status: 200 }); + } catch (error) { + return NextResponse.json({ error: JSON.stringify(error) }, { status: 500 }); + } +} diff --git a/app/article/[id]/page.tsx b/app/article/[id]/page.tsx new file mode 100644 index 000000000..6040563df --- /dev/null +++ b/app/article/[id]/page.tsx @@ -0,0 +1,20 @@ +import Prose from 'components/prose'; +import { wordpress } from 'lib/wordpress/wordpress'; +import { notFound } from 'next/navigation'; + +export default async function ArticlePage(props: { params: Promise<{ id: string }> }) { + const { id } = await props.params; + const article = await wordpress.get(`posts/${id}`); + if (!article) return notFound(); + + return ( +
+
+
+

{article.title.rendered}

+ +
+
+
+ ); +} diff --git a/app/checkout/page.tsx b/app/checkout/page.tsx index aafc8cfaf..e73362e3a 100644 --- a/app/checkout/page.tsx +++ b/app/checkout/page.tsx @@ -1,13 +1,14 @@ 'use client'; -import { Accordion, AccordionItem, Checkbox } from '@nextui-org/react'; +import { Accordion, AccordionItem, Checkbox, Radio, RadioGroup } from '@nextui-org/react'; import { useCart } from 'components/cart/cart-context'; import CartItemView from 'components/cart/cart-item'; import Price from 'components/price'; import ShippingForm from 'components/shipping/form'; +import { PaymentGateways } from 'lib/woocomerce/models/payment'; import { OrderPayload } from 'lib/woocomerce/storeApi'; import { useRouter } from 'next/navigation'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { z } from 'zod'; const shippingSchema = z.object({ @@ -18,7 +19,8 @@ const shippingSchema = z.object({ city: z.string().min(3), state: z.string().min(3), postcode: z.string().min(3), - country: z.string().min(3) + country: z.string().min(3), + company: z.string().optional() }); export default function CheckoutPage() { @@ -55,21 +57,43 @@ export default function CheckoutPage() { }; const [formData, setFormData] = useState(initialState); const [sameBilling, setSameBilling] = useState(true); + const [paymentGateways, setPaymentGateways] = useState([]); + + useEffect(() => { + const fetchPaymentGateways = async () => { + const paymentGateways = await (await fetch('/api/payments')).json(); + setPaymentGateways(paymentGateways); + }; + fetchPaymentGateways(); + }, []); + const handleChangeShipping = (e: any) => { - setFormData(e); + setFormData({ ...formData, shipping_address: e }); + if (sameBilling) { + setFormData({ + ...formData, + billing_address: { ...formData.billing_address, ...e } + }); + } }; const handleChangeBilling = (e: any) => { - setFormData(e); + setFormData({ ...formData, billing_address: e }); }; return (

Checkout

{ + onSubmit={(e) => { + e.preventDefault(); try { - console.log(formData); + if (sameBilling) { + setFormData({ + ...formData, + billing_address: { ...formData.billing_address, ...formData.shipping_address } + }); + } shippingSchema.parse(formData.shipping_address); } catch (error) { console.log(error); @@ -77,52 +101,81 @@ export default function CheckoutPage() { }} className="rounded-lg border border-neutral-200 bg-white p-4 dark:border-neutral-800 dark:bg-black" > -
- - - - setSameBilling(v)} className="mt-2"> - Use same address for billing? - - - - - - -
-

Payment

+
+
+ {cart && ( +
+
    + {cart.items?.length && + cart.items + .sort((a, b) => a.name.localeCompare(b.name)) + .map((item, i) => { + return ( +
  • + +
  • + ); + })} +
+
+

Total

+ +
- - - - {cart && ( -
-
    - {cart.items?.length && - cart.items - .sort((a, b) => a.name.localeCompare(b.name)) - .map((item, i) => { - return ( -
  • +
    + + + + setSameBilling(v)} className="mt-2"> + Use same address for billing? + + + + + + +
    +
    + + {paymentGateways.map((gateway: any) => ( + { + setFormData((prev) => ({ + ...prev, + payment_method: e.target.value, + payment_title: gateway.title + })); + }} > - -
  • - ); - })} -
-
-

Total

- -
-
- )} + {gateway.title} + + ))} + +
+
+ + +
+
+ +
diff --git a/app/page.tsx b/app/page.tsx index 3201b60f5..232d3bfa4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,43 +6,46 @@ export const metadata = { }; import { Carousel } from 'components/carousel'; import { ThreeItemGrid } from 'components/grid/three-items'; +import ProductSuspense from 'components/product/product-suspense'; import { Category } from 'lib/woocomerce/models/base'; import { Product } from 'lib/woocomerce/models/product'; import { woocommerce } from 'lib/woocomerce/woocommerce'; import { wordpress } from 'lib/wordpress/wordpress'; -import React from 'react'; +import Link from 'next/link'; +import { Suspense } from 'react'; -export default async function HomePage() { +async function Products({ category }: { category: Category }) { + const products: Product[] = await woocommerce.get('products', { + category: category.id.toString() + }); + + return ; +} + +async function ProductsByCategory() { const categories: Category[] = await woocommerce.get('products/categories'); - const productsByCategory: Record = {}; - await Promise.all( - categories.map((category) => - woocommerce.get('products', { category: category.id.toString() }).then((products) => { - productsByCategory[category.name] = products; - }) - ) - ); - const posts = await wordpress.get('posts'); return ( -
+ <> {categories.map((category, index) => ( -
+
- - {category.name} - + {category.name}
- - {category.description} - + {category.description}
- - {productsByCategory[category.name] && ( - - )} - + + {[...Array(3)].map((_, i) => ( + + ))} +
+ } + > + + {index === 1 && (
Top products @@ -51,27 +54,63 @@ export default async function HomePage() { )}
))} -
- Latest posts -
- {posts.map((post: any) => ( -
- {post.title.rendered} -
-

{post.title.rendered}

-
-
-
- ))} -
+ + ); +} + +async function LatestPosts() { + const posts = await wordpress.get('posts?_embed'); + + return ( +
+ Latest posts +
+ {posts.map((post: any) => ( +
+ {post.title.rendered} + +

{post.title.rendered}

+
+ +
+ ))}
+
+ ); +} + +export default async function HomePage() { + return ( +
+ + {[...Array(3)].map((_, i) => ( + + ))} +
+ } + > + + + + {[...Array(3)].map((_, i) => ( + + ))} +
+ } + > + +
); } diff --git a/app/product/[name]/page.tsx b/app/product/[name]/page.tsx index 2d536690c..91e84fdc8 100644 --- a/app/product/[name]/page.tsx +++ b/app/product/[name]/page.tsx @@ -40,6 +40,43 @@ export async function generateMetadata(props: { }; } +async function RelatedProducts({ product }: { product: Product }) { + const relatedProducts = await Promise.all( + product.related_ids?.map(async (id) => woocommerce.get(`products/${id}`)) || [] + ); + + return ( + <> + {relatedProducts.length > 0 && ( +
+

Related Products

+
+ {relatedProducts.map((relatedProduct) => { + return ( + + {relatedProduct.name} +
+

{relatedProduct.name}

+
+
+ + ); + })} +
+
+ )} + + ); +} + export default async function ProductPage(props: { params: Promise<{ name: string }> }) { const params = await props.params; const product: Product | undefined = ( @@ -119,31 +156,9 @@ export default async function ProductPage(props: { params: Promise<{ name: strin
-
-

Related Products

-
- {relatedProducts.map((relatedProduct) => { - return ( -
- {relatedProduct.name} -
- -

{relatedProduct.name}

- -
-
-
- ); - })} -
-
+ + +
); diff --git a/app/profile/orders/[id]/page.tsx b/app/profile/@user/orders/[id]/page.tsx similarity index 97% rename from app/profile/orders/[id]/page.tsx rename to app/profile/@user/orders/[id]/page.tsx index e7bd1e616..b569fa741 100644 --- a/app/profile/orders/[id]/page.tsx +++ b/app/profile/@user/orders/[id]/page.tsx @@ -15,7 +15,7 @@ export default async function OrderPage(props: { params: Promise<{ id: number }> const order = await woocommerce.get('orders', { id: params.id }); return ( -
+

Order

diff --git a/app/profile/orders/page.tsx b/app/profile/@user/orders/page.tsx similarity index 100% rename from app/profile/orders/page.tsx rename to app/profile/@user/orders/page.tsx diff --git a/app/profile/page.tsx b/app/profile/@user/page.tsx similarity index 100% rename from app/profile/page.tsx rename to app/profile/@user/page.tsx diff --git a/app/profile/layout.tsx b/app/profile/layout.tsx index e1ce1eceb..1dad922a0 100644 --- a/app/profile/layout.tsx +++ b/app/profile/layout.tsx @@ -8,7 +8,7 @@ import { Shipping } from 'lib/woocomerce/models/shipping'; import Link from 'next/link'; import { useEffect, useState } from 'react'; -export default function ProfileLayout({ children }: { children: React.ReactNode }) { +export default function ProfileLayout({ user }: { user: React.ReactNode }) { const [customer, setCustomer] = useState(undefined); const [shippingAddress, setShippingAddress] = useState(undefined); @@ -31,38 +31,37 @@ export default function ProfileLayout({ children }: { children: React.ReactNode return (
-

Profile

{customer && (
- -
+ +
+ Ciao {customer.first_name} - {customer.last_name}
-
+
-
-
+
-
-
+
)}
- {children} + {user}
); diff --git a/app/search/page.tsx b/app/search/page.tsx index 936657ed5..208725f5c 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -12,13 +12,15 @@ export default async function SearchPage(props: { searchParams?: Promise<{ [key: string]: string | string[] | undefined }>; }) { const searchParams = await props.searchParams; - const { sort, q: searchValue } = searchParams as { [key: string]: string }; + const { sort, q: searchValue, minPrice, maxPrice } = searchParams as { [key: string]: string }; const { sortKey, order } = sorting.find((item) => item.slug === sort) || defaultSort; const products = await woocommerce.get('products', { search: searchValue, orderby: sortKey, - order + order, + min_price: minPrice ?? '0', + max_price: maxPrice ?? '1000' }); const resultsText = products.length > 1 ? 'results' : 'result'; diff --git a/app/signup/page.tsx b/app/signup/page.tsx index 7d960c779..cee2db7fe 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -1,5 +1,6 @@ 'use client'; +import { Button, Input } from '@nextui-org/react'; import { useState } from 'react'; import { z } from 'zod'; @@ -66,78 +67,71 @@ export default function SignUpPage() {

Sign up

- +
- - - {error['username'] &&

{error['username']}

} -
-
- - - {error['email'] &&

{error['email']}

} -
-
- - - {error['password'] &&

{error['password']}

} -
-
- - - {error['confirmPassword'] &&

{error['confirmPassword']}

}
-
+
+ +
+
+ +
+
+ +
diff --git a/components/button/logout.tsx b/components/button/logout.tsx index d9d56f8c3..2f436655e 100644 --- a/components/button/logout.tsx +++ b/components/button/logout.tsx @@ -8,7 +8,7 @@ export default function LogoutButton() { return (