Beginning of ecommerce
This commit is contained in:
87
src/app/(storefront)/cart/page.tsx.exclude
Normal file
87
src/app/(storefront)/cart/page.tsx.exclude
Normal 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>
|
||||
);
|
||||
}
|
||||
21
src/app/(storefront)/layout.tsx
Normal file
21
src/app/(storefront)/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
79
src/app/(storefront)/products/[handle]/ProductDetails.tsx
Normal file
79
src/app/(storefront)/products/[handle]/ProductDetails.tsx
Normal 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;
|
||||
37
src/app/(storefront)/products/[handle]/page.tsx
Normal file
37
src/app/(storefront)/products/[handle]/page.tsx
Normal 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;
|
||||
40
src/app/(storefront)/products/page.tsx
Normal file
40
src/app/(storefront)/products/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user