Beginning of ecommerce

This commit is contained in:
2024-09-26 09:50:24 +05:30
parent e4925eeb16
commit a5398565cf
25 changed files with 493 additions and 34 deletions

View File

@ -0,0 +1,87 @@
'use client';
import { useCart, useUpdateLineItem, useDeleteLineItem } from 'medusa-react';
import { useRouter } from 'next/navigation';
export default function CartPage() {
const { cart } = useCart();
const router = useRouter();
// Ensure cart is loaded
if (!cart?.id) {
return <p>Loading cart...</p>;
}
// Hook for updating a cart line item
const {
mutate: updateLineItem,
isLoading: isUpdating,
error: updateError,
} = useUpdateLineItem(cart.id);
// Hook for deleting a cart line item
const {
mutate: deleteLineItem,
isLoading: isDeleting,
error: deleteError,
} = useDeleteLineItem(cart?.id);
if (!cart) return <p>Loading cart...</p>;
const handleUpdateItem = (lineId: string, quantity: number) => {
updateLineItem(
{ lineId, quantity },
{
onSuccess: () => {
// Optionally refetch cart or show a success message
},
},
);
};
const handleRemoveItem = (lineId: string) => {
deleteLineItem(
{ lineId },
{
onSuccess: () => {
// Optionally refetch cart or show a success message
},
},
);
};
const proceedToCheckout = () => {
router.push('/checkout');
};
return (
<div>
<h1>Your Cart</h1>
{cart.items?.length ? (
cart.items.map((item) => (
<div key={item.id}>
<h2>{item.title}</h2>
<p>Quantity: {item.quantity}</p>
<button
onClick={() => handleUpdateItem(item.id, item.quantity + 1)}
disabled={isUpdating}
>
Increase Quantity
</button>
<button
onClick={() => handleRemoveItem(item.id)}
disabled={isDeleting}
>
Remove
</button>
{updateError && <p>Error updating item: {updateError.message}</p>}
{deleteError && <p>Error removing item: {deleteError.message}</p>}
</div>
))
) : (
<p>Your cart is empty.</p>
)}
<p>Total: {cart.total ? `$${(cart.total / 100).toFixed(2)}` : '$0.00'}</p>
<button onClick={proceedToCheckout}>Proceed to Checkout</button>
</div>
);
}

View File

@ -0,0 +1,21 @@
import '../globals.css';
import { ReactNode } from 'react';
import { Providers } from '../providers';
import { Footer } from '@/components/footer';
import { Navbar } from '@/components/navbar';
interface RootLayoutProps {
children: ReactNode;
}
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang='en'>
<body>
<Navbar />
<Providers>{children}</Providers>
<Footer />
</body>
</html>
);
}

View File

@ -0,0 +1,79 @@
'use client';
import { FC } from 'react';
import { useEffect, useState } from 'react';
import { getProductByHandle } from '@/lib/product-by-handle';
import { PricedProduct } from '@medusajs/medusa/dist/types/pricing';
interface ProductDetailsProps {
handle: string;
}
const ProductDetails: FC<ProductDetailsProps> = ({ handle }) => {
const [product, setProduct] = useState<PricedProduct | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<unknown>(null);
useEffect(() => {
const fetchProduct = async () => {
try {
const { product } = await getProductByHandle(handle);
console.log(product);
if (!product) {
setProduct(null);
} else {
setProduct(product);
}
} catch (err) {
setError(err);
} finally {
setIsLoading(false);
}
};
fetchProduct();
}, [handle]);
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Something went wrong: {error.message}</div>;
}
if (!product) {
return <div>Product not found.</div>;
}
return (
<div className='container mx-auto p-4'>
<h1 className='text-4xl font-bold'>{product.title}</h1>
<div className='flex flex-col md:flex-row gap-6 mt-4'>
{/* Product Images */}
<div className='flex-1'>
{product?.images?.length > 0 && (
<img
src={product.images[0].url}
alt={product.title}
className='rounded-lg shadow-lg'
/>
)}
</div>
{/* Product Info */}
<div className='flex-1'>
<p className='text-lg font-semibold text-gray-500'>
Price: ${product.variants[0].prices[0].amount / 100}
</p>
<p className='text-lg text-gray-700'>{product.description}</p>
<button className='mt-4 px-6 py-2 bg-black text-white rounded hover:bg-gray-700'>
Add to Cart
</button>
</div>
</div>
</div>
);
};
export default ProductDetails;

View File

@ -0,0 +1,37 @@
import { FC } from 'react';
import { getProductsList } from '@/lib/product';
import ProductDetails from './ProductDetails';
interface ProductPageProps {
params: {
handle: string;
};
}
export async function generateStaticParams() {
// Fetch the list of products
const products = await getProductsList({}).then(
({ response }) => response.products,
);
if (!products) {
return [];
}
// Generate static params based on product handles
const staticParams = products.map((product) => ({
handle: product.handle,
}));
return staticParams;
}
const ProductPage: FC<ProductPageProps> = ({ params }) => {
const { handle } = params; // Extract the handle from the params
return (
// Render the client component and pass necessary props
<ProductDetails handle={handle} />
);
};
export default ProductPage;

View File

@ -0,0 +1,40 @@
'use client';
import { useProducts } from 'medusa-react';
import Image from 'next/image';
import Link from 'next/link';
export default function ProductsPage() {
const { products, isLoading, error } = useProducts();
if (isLoading) {
return <div>Loading...</div>;
}
if (error) return <p>Error: {error.message}</p>;
return (
<div className='container mx-auto py-12'>
<h1 className='text-3xl font-bold mb-8'>Handbags Collection</h1>
<div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8'>
{products?.map((product) => (
<Link key={product.id} href={`/products/${product.handle}`}>
<div className='border border-gray-200 p-4 rounded-lg shadow-lg'>
<Image
src={product.thumbnail}
alt={product.title}
width={300}
height={300}
className='object-cover rounded-md'
/>
<h2 className='mt-4 text-lg font-semibold'>{product.title}</h2>
<p className='mt-2 text-gray-600'>{product.description}</p>
<p className='mt-2 text-lg font-bold text-gray-900'>
${product.variants[0]?.prices[0].amount / 100}
</p>
</div>
</Link>
))}
</div>
</div>
);
}