Files
cbd420/app/components/Drop.tsx
2025-12-20 10:47:01 +05:30

181 lines
4.9 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import Image from 'next/image'
interface DropData {
id: number
item: string
size: number
fill: number
unit: string
ppu: number
image_url: string | null
created_at: string
}
export default function Drop() {
const [drop, setDrop] = useState<DropData | null>(null)
const [loading, setLoading] = useState(true)
const [selectedSize, setSelectedSize] = useState(50)
useEffect(() => {
fetchActiveDrop()
}, [])
const fetchActiveDrop = async () => {
try {
const response = await fetch('/api/drops/active')
if (response.ok) {
const data = await response.json()
setDrop(data)
}
} catch (error) {
console.error('Error fetching active drop:', error)
} finally {
setLoading(false)
}
}
const getProgressPercentage = (fill: number, size: number) => {
return Math.min((fill / size) * 100, 100)
}
const formatSize = (size: number, unit: string) => {
if (unit === 'g' && size >= 1000) {
return `${(size / 1000).toFixed(1)}kg`
}
return `${size}${unit}`
}
const getAvailableSizes = () => {
if (!drop) return []
const sizes = [50, 100, 250] // Always in grams
// Calculate remaining inventory in grams
let remainingInGrams = 0
if (drop.unit === 'kg') {
remainingInGrams = (drop.size - drop.fill) * 1000
} else {
// For 'g' or any other unit, assume same unit
remainingInGrams = drop.size - drop.fill
}
// Only show sizes that don't exceed remaining inventory
return sizes.filter((size) => size <= remainingInGrams)
}
if (loading) {
return (
<div className="drop">
<div style={{ gridColumn: '1 / -1', textAlign: 'center', padding: '40px' }}>
<p style={{ color: 'var(--muted)' }}>Loading...</p>
</div>
</div>
)
}
if (!drop) {
return (
<div className="drop">
<div style={{ gridColumn: '1 / -1', textAlign: 'center', padding: '60px' }}>
<h2 style={{ marginBottom: '16px' }}>Drop Sold Out</h2>
<p style={{ color: 'var(--muted)', marginBottom: '20px' }}>
The current collective drop has been fully reserved.
</p>
<p style={{ color: 'var(--muted)' }}>
Next collective drop coming soon.
</p>
</div>
</div>
)
}
const progressPercentage = getProgressPercentage(drop.fill, drop.size)
const availableSizes = getAvailableSizes()
// Calculate remaining in the drop's unit
const remaining = drop.size - drop.fill
const hasRemaining = remaining > 0
return (
<div className="drop">
{drop.image_url ? (
<Image
src={drop.image_url}
alt={drop.item}
width={420}
height={420}
style={{ width: '100%', height: 'auto', borderRadius: '16px', objectFit: 'cover' }}
/>
) : (
<div
style={{
width: '100%',
aspectRatio: '1 / 1',
background: 'var(--bg-soft)',
borderRadius: '16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--muted)',
}}
>
No Image
</div>
)}
<div>
<h2>{drop.item}</h2>
<div className="meta">
{formatSize(drop.size, drop.unit)} Batch
</div>
<div className="price">
{drop.ppu.toFixed(2)} CHF / {drop.unit} · incl. 2.5% VAT
</div>
<div className="progress">
<span style={{ width: `${progressPercentage}%` }}></span>
</div>
<div className="meta">
{drop.fill}
{drop.unit} of {drop.size}
{drop.unit} reserved
</div>
{hasRemaining && availableSizes.length > 0 && (
<>
<div className="options">
{availableSizes.map((size) => (
<button
key={size}
className={selectedSize === size ? 'active' : ''}
onClick={() => setSelectedSize(size)}
>
{size}g
</button>
))}
</div>
<button className="cta">Join Drop</button>
</>
)}
{hasRemaining && availableSizes.length === 0 && (
<div style={{ marginTop: '30px', padding: '20px', background: 'var(--bg-soft)', borderRadius: '12px', textAlign: 'center' }}>
<p style={{ margin: 0, color: 'var(--muted)' }}>
Less than 50{drop.unit} remaining. This drop is almost fully reserved.
</p>
</div>
)}
{!hasRemaining && (
<div style={{ marginTop: '30px', padding: '20px', background: 'var(--bg-soft)', borderRadius: '12px', textAlign: 'center' }}>
<p style={{ margin: 0, color: 'var(--muted)' }}>This drop is fully reserved</p>
</div>
)}
</div>
</div>
)
}