drop active

This commit is contained in:
2025-12-20 10:47:01 +05:30
parent 7f8ee79b13
commit 9871289bfb
2 changed files with 163 additions and 37 deletions

View File

@@ -1,11 +1,11 @@
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
import pool from '@/lib/db' import pool from '@/lib/db'
// GET /api/drops/active - Get the currently active drop (not sold out) // GET /api/drops/active - Get the earliest unfilled drop (not sold out)
export async function GET() { export async function GET() {
try { try {
const [rows] = await pool.execute( const [rows] = await pool.execute(
'SELECT * FROM drops WHERE fill < size ORDER BY created_at DESC LIMIT 1' 'SELECT * FROM drops WHERE fill < size ORDER BY created_at ASC LIMIT 1'
) )
const drops = rows as any[] const drops = rows as any[]

View File

@@ -1,52 +1,178 @@
'use client' 'use client'
import { useState } from 'react' import { useState, useEffect } from 'react'
import Image from 'next/image' import Image from 'next/image'
export default function Drop() { interface DropData {
const [selectedSize, setSelectedSize] = useState('50g') 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 ( return (
<div className="drop"> <div className="drop">
<Image <div style={{ gridColumn: '1 / -1', textAlign: 'center', padding: '40px' }}>
src="https://images.unsplash.com/photo-1604908554027-0b6c2c9c7e92" <p style={{ color: 'var(--muted)' }}>Loading...</p>
alt="Harlequin CBD" </div>
width={420} </div>
height={420} )
style={{ width: '100%', height: 'auto', borderRadius: '16px', objectFit: 'cover' }} }
/>
<div> if (!drop) {
<h2>Harlequin Collective Drop</h2> return (
<div className="meta">1kg Batch · Indoor · Switzerland</div> <div className="drop">
<div className="price">2.50 CHF / g · incl. 2.5% VAT</div> <div style={{ gridColumn: '1 / -1', textAlign: 'center', padding: '60px' }}>
<h2 style={{ marginBottom: '16px' }}>Drop Sold Out</h2>
<div className="progress"> <p style={{ color: 'var(--muted)', marginBottom: '20px' }}>
<span></span> The current collective drop has been fully reserved.
</div> </p>
<div className="meta">620g of 1,000g reserved</div> <p style={{ color: 'var(--muted)' }}>
Next collective drop coming soon.
<div className="options"> </p>
<button </div>
className={selectedSize === '50g' ? 'active' : ''} </div>
onClick={() => setSelectedSize('50g')} )
> }
50g
</button> const progressPercentage = getProgressPercentage(drop.fill, drop.size)
<button const availableSizes = getAvailableSizes()
className={selectedSize === '100g' ? 'active' : ''}
onClick={() => setSelectedSize('100g')} // Calculate remaining in the drop's unit
> const remaining = drop.size - drop.fill
100g const hasRemaining = remaining > 0
</button>
<button return (
className={selectedSize === '250g' ? 'active' : ''} <div className="drop">
onClick={() => setSelectedSize('250g')} {drop.image_url ? (
> <Image
250g src={drop.image_url}
</button> alt={drop.item}
</div> width={420}
height={420}
<button className="cta">Join Drop</button> 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>
</div> </div>
) )