diff --git a/src/app/shop/page.tsx b/src/app/shop/page.tsx new file mode 100644 index 0000000..7a77ba5 --- /dev/null +++ b/src/app/shop/page.tsx @@ -0,0 +1,108 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import ProductCard from '@/components/ProductCard'; +import { ThemeProvider } from '@/components/theme-provider'; +import Navbar from '@/components/Navbar'; +import Footer from '@/components/Footer'; + +interface StripeProduct { + id: string; + name: string; + description: string | null; + images: string[]; + default_price: { + id: string; + unit_amount: number; + currency: string; + } | null; + metadata: Record; +} + +export default function ShopPage() { + const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchProducts = async () => { + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL || ''}/stripe/project/products?projectId=32a65411-f209-4b18-ba90-6f24fb6d2dcb&expandDefaultPrice=true` + ); + if (!response.ok) throw new Error('Failed to fetch products'); + const data = await response.json(); + setProducts(data.data?.data || []); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load products'); + } finally { + setLoading(false); + } + }; + + fetchProducts(); + }, []); + + const handleCheckout = async (productId: string, priceId: string) => { + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL || ''}/stripe/project/checkout-session`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + projectId: '32a65411-f209-4b18-ba90-6f24fb6d2dcb', + items: [{ priceId, quantity: 1 }], + successUrl: window.location.origin + '/shop?success=true', + cancelUrl: window.location.origin + '/shop?canceled=true', + }), + } + ); + if (!response.ok) throw new Error('Failed to create checkout session'); + const data = await response.json(); + if (data.data?.url) { + window.location.href = data.data.url; + } + } catch (err) { + alert(err instanceof Error ? err.message : 'Checkout failed'); + } + }; + + return ( + +
+ +
+

Our Products

+ + {loading && ( +
+
+
+ )} + + {error && ( +
{error}
+ )} + + {!loading && !error && products.length === 0 && ( +
+ No products available yet. +
+ )} + +
+ {products.map((product) => ( + + ))} +
+
+
+
+
+ ); +} diff --git a/src/components/ProductCard.tsx b/src/components/ProductCard.tsx new file mode 100644 index 0000000..f84ab4a --- /dev/null +++ b/src/components/ProductCard.tsx @@ -0,0 +1,73 @@ +'use client'; + +import Image from 'next/image'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; + +interface StripeProduct { + id: string; + name: string; + description: string | null; + images: string[]; + default_price: { + id: string; + unit_amount: number; + currency: string; + } | null; + metadata: Record; +} + +interface ProductCardProps { + product: StripeProduct; + onCheckout: (productId: string, priceId: string) => void; +} + +export default function ProductCard({ product, onCheckout }: ProductCardProps) { + const formatPrice = (amount: number, currency: string) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: currency.toUpperCase(), + }).format(amount / 100); + }; + + return ( + + {product.images[0] && ( +
+ {product.name} +
+ )} + + {product.name} + {product.default_price && ( +

+ {formatPrice(product.default_price.unit_amount, product.default_price.currency)} +

+ )} +
+ + {product.description && ( + {product.description} + )} + + + + +
+ ); +}