Aunty-Sparkles-Website/app/shop/page.tsx

484 lines
17 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import SquarePaymentForm from "@/components/square-payment-form"
import SiteFooter from "@/components/site-footer"
interface Product {
id: string
variationId: string
name: string
description: string
price: number
currency: string
imageUrl: string
inventory: number
category: string
}
interface ApiResponse {
success: boolean
products?: Product[]
error?: string
message?: string
details?: any
}
export default function Shop() {
const [products, setProducts] = useState<Product[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string>("")
const [cart, setCart] = useState<
Array<{
id: string
variationId: string
name: string
price: number
quantity: number
description?: string
}>
>([])
const [showCheckout, setShowCheckout] = useState(false)
const [orderComplete, setOrderComplete] = useState(false)
useEffect(() => {
fetchProducts()
}, [])
const fetchProducts = async () => {
try {
setLoading(true)
setError("")
console.log("Fetching products from /api/square/catalog...")
// First check Square health
const healthResponse = await fetch("/api/square/health")
const healthData = await healthResponse.json()
if (!healthData.success) {
console.error("Square health check failed:", healthData)
setError(`Square API configuration issue: ${healthData.error || 'Connection failed'}`)
return
}
console.log("Square health check passed, fetching catalog...")
const response = await fetch("/api/square/catalog?includeInventory=true")
console.log("Response status:", response.status)
console.log("Response headers:", Object.fromEntries(response.headers.entries()))
// Check if response is ok
if (!response.ok) {
const errorText = await response.text()
console.error("HTTP error:", response.status, errorText.substring(0, 500))
setError(`Server error (${response.status}): ${response.statusText}`)
return
}
// Check if response is JSON
const contentType = response.headers.get("content-type")
if (!contentType || !contentType.includes("application/json")) {
const responseText = await response.text()
console.error("Non-JSON response:", responseText.substring(0, 500))
setError("Server returned invalid response format. Check server logs for details.")
return
}
const data: ApiResponse = await response.json()
console.log("API Response:", data)
if (data.success && data.products) {
setProducts(data.products)
console.log("Products loaded:", data.products.length)
} else {
const errorMsg = data.error || data.message || "Failed to load products"
console.error("API returned error:", errorMsg)
setError(errorMsg)
// Show additional details if available
if (data.details) {
console.error("Error details:", data.details)
}
}
} catch (error) {
console.error("Error fetching products:", error)
// More specific error handling
if (error instanceof TypeError && error.message.includes("json")) {
setError("Server returned invalid data format. This usually means Square SDK failed to load.")
} else if (error instanceof TypeError && error.message.includes("fetch")) {
setError("Network connection failed. Please check your internet connection.")
} else {
setError(`Unexpected error: ${error instanceof Error ? error.message : "Unknown error"}`)
}
} finally {
setLoading(false)
}
}
const addToCart = (product: Product) => {
if (product.inventory <= 0) {
alert("Sorry, this item is out of stock!")
return
}
setCart((prev) => {
const existing = prev.find((item) => item.id === product.id)
if (existing) {
const newQuantity = existing.quantity + 1
if (newQuantity > product.inventory) {
alert(`Sorry, only ${product.inventory} items available in stock!`)
return prev
}
return prev.map((item) => (item.id === product.id ? { ...item, quantity: newQuantity } : item))
}
return [
...prev,
{
id: product.id,
variationId: product.variationId,
name: product.name,
price: product.price,
quantity: 1,
description: product.description,
},
]
})
}
const removeFromCart = (productId: string) => {
setCart((prev) => prev.filter((item) => item.id !== productId))
}
const updateQuantity = (productId: string, quantity: number) => {
if (quantity === 0) {
removeFromCart(productId)
return
}
const product = products.find((p) => p.id === productId)
if (product && quantity > product.inventory) {
alert(`Sorry, only ${product.inventory} items available in stock!`)
return
}
setCart((prev) => prev.map((item) => (item.id === productId ? { ...item, quantity } : item)))
}
const handlePaymentSuccess = (paymentResult: any) => {
console.log("Payment successful:", paymentResult)
setOrderComplete(true)
setCart([])
setShowCheckout(false)
fetchProducts()
}
const handlePaymentError = (error: string) => {
console.error("Payment error:", error)
alert(`Payment failed: ${error}`)
}
if (orderComplete) {
return (
<div className="min-h-screen bg-black flex items-center justify-center">
<Card className="max-w-md mx-auto">
<CardContent className="text-center p-8">
<div className="text-6xl mb-4"></div>
<h2 className="text-2xl font-bold mb-4">Order Complete!</h2>
<p className="text-gray-600 mb-6">
Thank you for your purchase! You'll receive a confirmation email shortly.
</p>
<Button onClick={() => setOrderComplete(false)}>Continue Shopping</Button>
</CardContent>
</Card>
</div>
)
}
if (showCheckout && cart.length > 0) {
return (
<div className="min-h-screen bg-black">
<header className="bg-[#e8e4d3] py-4">
<div className="container mx-auto px-4 flex items-center justify-between">
<div className="flex items-center">
<a href="/" className="flex items-center">
<Image
src="/images/logo-black-white.png"
alt="Aunty Sparkles Logo"
width={96}
height={96}
className="mr-4"
/>
</a>
</div>
<div className="flex items-center space-x-8">
<nav>
<ul className="flex space-x-8 text-xl font-bold">
<li>
<a href="/about" className="text-gray-700 hover:text-gray-900 transition-colors">
About
</a>
</li>
<li>
<a href="/gallery" className="text-gray-700 hover:text-gray-900 transition-colors">
Gallery
</a>
</li>
<li>
<a href="/contact" className="text-gray-700 hover:text-gray-900 transition-colors">
Contact
</a>
</li>
</ul>
</nav>
<div className="w-8"></div>
</div>
</div>
</header>
<section className="section-dark py-20">
<div className="container mx-auto px-4">
<div className="flex items-center justify-between mb-8">
<h1 className="text-4xl font-bold text-white">Checkout</h1>
<Button
onClick={() => setShowCheckout(false)}
variant="outline"
className="text-white border-white hover:bg-white hover:text-black"
>
Back to Shop
</Button>
</div>
<SquarePaymentForm
items={cart}
onPaymentSuccess={handlePaymentSuccess}
onPaymentError={handlePaymentError}
/>
</div>
</section>
<SiteFooter />
</div>
)
}
return (
<div className="min-h-screen bg-black">
{/* Header */}
<header className="bg-[#e8e4d3] py-4">
<div className="container mx-auto px-4 flex items-center justify-between">
<div className="flex items-center">
<a href="/" className="flex items-center">
<Image
src="/images/logo-black-white.png"
alt="Aunty Sparkles Logo"
width={96}
height={96}
className="mr-4"
/>
</a>
</div>
<div className="flex items-center space-x-8">
<nav>
<ul className="flex space-x-8 text-xl font-bold">
<li>
<a href="/about" className="text-gray-700 hover:text-gray-900 transition-colors">
About
</a>
</li>
<li>
<a href="/gallery" className="text-gray-700 hover:text-gray-900 transition-colors">
Gallery
</a>
</li>
<li>
<a href="/contact" className="text-gray-700 hover:text-gray-900 transition-colors">
Contact
</a>
</li>
</ul>
</nav>
<div className="w-8">
{cart.length > 0 && (
<div className="relative">
<Button
onClick={() => setShowCheckout(true)}
className="bg-yellow-400 text-black hover:bg-yellow-300 text-xs px-2 py-1"
>
Cart ({cart.reduce((sum, item) => sum + item.quantity, 0)})
</Button>
</div>
)}
</div>
</div>
</div>
</header>
{/* Hero Section */}
<section className="hero-bg py-20">
<div className="container mx-auto px-4 text-center">
<h1 className="text-5xl font-bold text-white mb-4">Shop</h1>
<div className="w-16 h-1 bg-yellow-400 mb-8 mx-auto"></div>
<p className="text-lg text-gray-300 max-w-2xl mx-auto">
Discover unique, handcrafted pieces that tell a story. Each item is lovingly upcycled and designed to help
you sparkle!
</p>
</div>
</section>
{/* Debug Info */}
{error && (
<section className="bg-red-900 py-4">
<div className="container mx-auto px-4 text-center">
<p className="text-white font-semibold">Debug Info: {error}</p>
<p className="text-red-200 text-sm mt-2">
Check browser console for more details. This usually means Square API credentials need to be configured.
</p>
</div>
</section>
)}
{/* Products Grid */}
<section className="section-dark py-20">
<div className="container mx-auto px-4">
{loading ? (
<div className="text-center text-white">
<div className="text-2xl mb-4">Loading products...</div>
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-yellow-400 mx-auto"></div>
<p className="text-gray-300 mt-4">Connecting to Square catalog...</p>
</div>
) : error ? (
<div className="text-center text-white">
<div className="text-2xl mb-4">Unable to load products</div>
<p className="text-gray-300 mb-4">{error}</p>
<div className="space-y-2 mb-4">
<p className="text-sm text-gray-400">Common solutions:</p>
<ul className="text-sm text-gray-400 list-disc list-inside">
<li>Check that Square API credentials are configured in environment variables</li>
<li>Verify that products exist in your Square catalog</li>
<li>Ensure Square API access token has catalog permissions</li>
</ul>
</div>
<button
onClick={fetchProducts}
className="bg-yellow-400 text-black px-6 py-2 rounded hover:bg-yellow-300 transition-colors"
>
Try Again
</button>
</div>
) : products.length === 0 ? (
<div className="text-center text-white">
<div className="text-2xl mb-4">No products available</div>
<p className="text-gray-300">Add products to your Square catalog to see them here.</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{products.map((product) => (
<Card key={product.id} className="bg-gray-800 border-gray-700">
<CardHeader className="p-0 relative">
<Image
src={product.imageUrl || "/placeholder.svg"}
alt={product.name}
width={400}
height={400}
className="w-full h-64 object-cover rounded-t-lg"
/>
{product.inventory <= 0 && (
<Badge className="absolute top-2 right-2 bg-red-500">Out of Stock</Badge>
)}
{product.inventory > 0 && product.inventory <= 5 && (
<Badge className="absolute top-2 right-2 bg-orange-500">Only {product.inventory} left</Badge>
)}
</CardHeader>
<CardContent className="p-6">
<CardTitle className="text-white mb-2">{product.name}</CardTitle>
<p className="text-gray-300 text-sm mb-4 line-clamp-3">{product.description}</p>
<div className="flex items-center justify-between">
<span className="text-2xl font-bold text-yellow-400">${product.price.toFixed(2)}</span>
<Button
onClick={() => addToCart(product)}
disabled={product.inventory <= 0}
className="bg-pink-500 text-white hover:bg-pink-400 disabled:bg-gray-500 disabled:cursor-not-allowed"
>
{product.inventory <= 0 ? "Out of Stock" : "Add to Cart"}
</Button>
</div>
<div className="mt-2 text-sm text-gray-400">
{product.inventory > 5 ? "In Stock" : `${product.inventory} in stock`}
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
</section>
{/* Cart Summary */}
{cart.length > 0 && (
<section className="section-dark py-10 border-t border-gray-700">
<div className="container mx-auto px-4">
<div className="max-w-md mx-auto">
<h3 className="text-xl font-bold text-white mb-4">Shopping Cart</h3>
<div className="space-y-2 mb-4">
{cart.map((item) => (
<div key={item.id} className="flex items-center justify-between text-white">
<span className="text-sm">
{item.name} x{item.quantity}
</span>
<div className="flex items-center space-x-2">
<Button
size="sm"
variant="outline"
onClick={() => updateQuantity(item.id, item.quantity - 1)}
className="h-6 w-6 p-0"
>
-
</Button>
<Button
size="sm"
variant="outline"
onClick={() => updateQuantity(item.id, item.quantity + 1)}
className="h-6 w-6 p-0"
>
+
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => removeFromCart(item.id)}
className="h-6 w-6 p-0"
>
×
</Button>
</div>
</div>
))}
</div>
<div className="flex justify-between items-center mb-4">
<span className="text-white font-bold">
Total: ${cart.reduce((sum, item) => sum + item.price * item.quantity, 0).toFixed(2)}
</span>
</div>
<Button
onClick={() => setShowCheckout(true)}
className="w-full bg-yellow-400 text-black hover:bg-yellow-300"
>
Proceed to Checkout
</Button>
</div>
</div>
</section>
)}
<SiteFooter />
</div>
)
}