This commit is contained in:
root
2025-12-22 06:43:19 +01:00
parent a940d51475
commit 6f4ca75faf
25 changed files with 1350 additions and 221 deletions

View File

@@ -4,6 +4,7 @@ import { useState, useEffect, Suspense } from 'react'
import Image from 'next/image'
import AuthModal from './AuthModal'
import UnlockModal from './UnlockModal'
import { useI18n } from '@/lib/i18n'
interface DropData {
id: number
@@ -28,6 +29,7 @@ interface User {
}
export default function Drop() {
const { t } = useI18n()
const [drop, setDrop] = useState<DropData | null>(null)
const [loading, setLoading] = useState(true)
const [selectedSize, setSelectedSize] = useState(50)
@@ -52,11 +54,15 @@ export default function Drop() {
const [isWholesaleUnlocked, setIsWholesaleUnlocked] = useState(false)
const [showUnlockModal, setShowUnlockModal] = useState(false)
const [selectedImageIndex, setSelectedImageIndex] = useState(0)
const [shippingFee, setShippingFee] = useState<number | null>(null)
const [loadingShippingFee, setLoadingShippingFee] = useState(false)
const [currency, setCurrency] = useState<'CHF' | 'EUR'>('EUR') // Default to EUR
useEffect(() => {
fetchActiveDrop()
checkAuth()
checkWholesaleStatus()
fetchShippingFee() // Fetch currency info on mount
// Poll active drop every 30 seconds
const interval = setInterval(() => {
@@ -193,11 +199,12 @@ export default function Drop() {
const getMinimumGrams = () => {
if (!drop) return 0
// Minimum price is 5 CHF
// Calculate minimum grams needed for 5 CHF
const pricePerGram = drop.ppu / 1000
// Minimum price is 5 in user's currency
// Calculate minimum grams needed for 5 (EUR or CHF)
const pricePerGramEur = drop.ppu / 1000
const minPriceEur = currency === 'CHF' ? 5 / 0.97 : 5 // Convert min CHF to EUR equivalent
// Use the higher price (standard) to ensure minimum is met
return Math.ceil(5 / pricePerGram)
return Math.ceil(minPriceEur / pricePerGramEur)
}
const handleCustomQuantityChange = (value: string) => {
@@ -224,17 +231,17 @@ export default function Drop() {
const minimum = getMinimumGrams()
if (isNaN(numValue) || numValue <= 0) {
setQuantityError('Please enter a valid number')
setQuantityError(t('drop.enterValidNumber'))
return false
}
if (numValue < minimum) {
setQuantityError(`Minimum ${minimum}g required (5 CHF minimum)`)
setQuantityError(t('drop.minimumRequired', { minimum }))
return false
}
if (numValue > remaining) {
setQuantityError(`Maximum ${remaining}g available`)
setQuantityError(t('drop.maximumAvailable', { maximum: remaining }))
return false
}
@@ -311,6 +318,41 @@ export default function Drop() {
}
}
const fetchShippingFee = async () => {
setLoadingShippingFee(true)
try {
const response = await fetch('/api/shipping-fee', {
credentials: 'include',
})
if (response.ok) {
const data = await response.json()
setShippingFee(data.shipping_fee || 40)
setCurrency(data.currency || 'EUR')
} else {
// Default to 40 EUR if fetch fails
setShippingFee(40)
setCurrency('EUR')
}
} catch (error) {
console.error('Error fetching shipping fee:', error)
// Default to 40 EUR on error
setShippingFee(40)
setCurrency('EUR')
} finally {
setLoadingShippingFee(false)
}
}
// Convert EUR price to user's currency (CHF for Swiss, EUR for others)
// Database stores prices in EUR, so we need to convert if user is Swiss
const convertPrice = (priceInEur: number): number => {
if (currency === 'CHF') {
// Convert EUR to CHF (1 EUR ≈ 0.97 CHF)
return priceInEur * 0.97
}
return priceInEur
}
const handleJoinDrop = () => {
// Validate custom quantity if entered
if (customQuantity && !validateCustomQuantity()) {
@@ -322,9 +364,10 @@ export default function Drop() {
setShowAuthModal(true)
return
}
// Fetch available currencies and buyer data when opening confirm modal
// Fetch available currencies, buyer data, and shipping fee when opening confirm modal
fetchAvailableCurrencies()
fetchBuyerData()
fetchShippingFee()
setShowConfirmModal(true)
}
@@ -334,6 +377,7 @@ export default function Drop() {
// After login, fetch buyer data and show the confirmation modal
fetchAvailableCurrencies()
fetchBuyerData()
fetchShippingFee()
setShowConfirmModal(true)
}
@@ -342,7 +386,7 @@ export default function Drop() {
// Validate buyer data fields
if (!buyerFullname.trim() || !buyerAddress.trim() || !buyerPhone.trim()) {
setErrorMessage('Please fill in all delivery information (full name, address, and phone)')
setErrorMessage(t('drop.fillDeliveryInfo'))
setShowErrorModal(true)
return
}
@@ -434,23 +478,43 @@ export default function Drop() {
const calculatePrice = () => {
if (!drop) return 0
// ppu is stored as integer where 1000 = $1.00, so divide by 1000 to get actual price
// ppu is stored as integer where 1000 = 1.00 EUR, so divide by 1000 to get actual price in EUR
// Assuming ppu is per gram
const pricePerGram = drop.ppu / 1000
const priceToUse = isWholesaleUnlocked ? pricePerGram * 0.76 : pricePerGram
return selectedSize * priceToUse
const pricePerGramEur = drop.ppu / 1000
const priceToUseEur = isWholesaleUnlocked ? pricePerGramEur * 0.76 : pricePerGramEur
const priceEur = selectedSize * priceToUseEur
// Convert to user's currency
return convertPrice(priceEur)
}
const calculateStandardPrice = () => {
if (!drop) return 0
const pricePerGram = drop.ppu / 1000
return selectedSize * pricePerGram
const pricePerGramEur = drop.ppu / 1000
const priceEur = selectedSize * pricePerGramEur
// Convert to user's currency
return convertPrice(priceEur)
}
const calculateWholesalePrice = () => {
if (!drop) return 0
const pricePerGram = drop.ppu / 1000
return selectedSize * pricePerGram * 0.76
const pricePerGramEur = drop.ppu / 1000
const priceEur = selectedSize * pricePerGramEur * 0.76
// Convert to user's currency
return convertPrice(priceEur)
}
// Get price per gram in user's currency
const getPricePerGram = () => {
if (!drop) return 0
const pricePerGramEur = drop.ppu / 1000
return convertPrice(pricePerGramEur)
}
// Get wholesale price per gram in user's currency
const getWholesalePricePerGram = () => {
if (!drop) return 0
const pricePerGramEur = drop.ppu / 1000
return convertPrice(pricePerGramEur * 0.76)
}
const getTimeUntilStart = () => {
@@ -467,11 +531,16 @@ export default function Drop() {
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
if (diffDays > 0) {
return `${diffDays} day${diffDays > 1 ? 's' : ''}${diffHours > 0 ? ` ${diffHours} hour${diffHours > 1 ? 's' : ''}` : ''}`
const dayText = diffDays === 1 ? t('drop.day') : t('drop.days')
const hourText = diffHours > 0 ? (diffHours === 1 ? t('drop.hour') : t('drop.hours')) : ''
return `${diffDays} ${dayText}${diffHours > 0 ? ` ${diffHours} ${hourText}` : ''}`
} else if (diffHours > 0) {
return `${diffHours} hour${diffHours > 1 ? 's' : ''}${diffMinutes > 0 ? ` ${diffMinutes} minute${diffMinutes > 1 ? 's' : ''}` : ''}`
const hourText = diffHours === 1 ? t('drop.hour') : t('drop.hours')
const minuteText = diffMinutes > 0 ? (diffMinutes === 1 ? t('drop.minute') : t('drop.minutes')) : ''
return `${diffHours} ${hourText}${diffMinutes > 0 ? ` ${diffMinutes} ${minuteText}` : ''}`
} else {
return `${diffMinutes} minute${diffMinutes > 1 ? 's' : ''}`
const minuteText = diffMinutes === 1 ? t('drop.minute') : t('drop.minutes')
return `${diffMinutes} ${minuteText}`
}
}
@@ -495,7 +564,7 @@ export default function Drop() {
return (
<div className="drop">
<div style={{ gridColumn: '1 / -1', textAlign: 'center', padding: '40px' }}>
<p style={{ color: 'var(--muted)' }}>Loading...</p>
<p style={{ color: 'var(--muted)' }}>{t('drop.loading')}</p>
</div>
</div>
)
@@ -505,12 +574,12 @@ export default function Drop() {
return (
<div className="drop">
<div style={{ gridColumn: '1 / -1', textAlign: 'center', padding: '60px' }}>
<h2 style={{ marginBottom: '16px' }}>Drop Sold Out</h2>
<h2 style={{ marginBottom: '16px' }}>{t('drop.dropSoldOut')}</h2>
<p style={{ color: 'var(--muted)', marginBottom: '20px' }}>
The current collective drop has been fully reserved.
{t('drop.fullyReserved')}
</p>
<p style={{ color: 'var(--muted)' }}>
Next collective drop coming soon.
{t('drop.nextDropComingSoon')}
</p>
</div>
</div>
@@ -601,27 +670,26 @@ export default function Drop() {
color: 'var(--muted)',
}}
>
No Image
{t('common.noImage')}
</div>
)}
<div>
<h2>{drop.item}</h2>
<div className="meta">
{formatSize(drop.size, drop.unit)} batch
{formatSize(drop.size, drop.unit)} {t('drop.batch')}
</div>
<div className="price">
{(() => {
// ppu is stored as integer where 1000 = $1.00
// Assuming ppu is always per gram for display purposes
const pricePerGram = drop.ppu / 1000;
const wholesalePricePerGram = pricePerGram * 0.76;
// Get prices in user's currency
const pricePerGram = getPricePerGram();
const wholesalePricePerGram = getWholesalePricePerGram();
if (isWholesaleUnlocked) {
return (
<>
<strong>Wholesale price: {wholesalePricePerGram.toFixed(2)} CHF / g</strong>
<strong>{t('drop.wholesalePriceLabel')} {wholesalePricePerGram.toFixed(2)} {currency} / g</strong>
<span className="muted" style={{ display: 'block', marginTop: '6px', fontSize: '14px' }}>
Standard: {pricePerGram.toFixed(2)} CHF / g
{t('drop.standard')}: {pricePerGram.toFixed(2)} {currency} / g
</span>
</>
);
@@ -629,11 +697,11 @@ export default function Drop() {
return (
<>
<strong>Standard price: {pricePerGram.toFixed(2)} CHF / g</strong>
<strong>{t('drop.standardPriceLabel')} {pricePerGram.toFixed(2)} {currency} / g</strong>
<span className="muted">
Wholesale: {wholesalePricePerGram.toFixed(2)} CHF / g 🔒 <a href="#unlock" onClick={(e) => { e.preventDefault(); setShowUnlockModal(true); }}>unlock</a>
{t('drop.wholesale')}: {wholesalePricePerGram.toFixed(2)} {currency} / g 🔒 <a href="#unlock" onClick={(e) => { e.preventDefault(); setShowUnlockModal(true); }}>{t('drop.unlock')}</a>
</span>
<div className="hint">Unlock once. Keep wholesale forever.</div>
<div className="hint">{t('drop.unlockOnce')}</div>
</>
);
})()}
@@ -642,7 +710,7 @@ export default function Drop() {
{isUpcoming ? (
<div style={{ marginTop: '30px', padding: '20px', background: 'var(--bg-soft)', borderRadius: '12px', textAlign: 'center' }}>
<p style={{ margin: 0, color: 'var(--muted)', fontSize: '16px' }}>
Drop starts in <strong>{timeUntilStart}</strong>
{t('drop.dropStartsIn')} <strong>{timeUntilStart}</strong>
</p>
</div>
) : (
@@ -654,7 +722,7 @@ export default function Drop() {
{(() => {
const fillDisplay = drop.unit === 'kg' ? Math.round(drop.fill * 1000) : Math.round(drop.fill);
const sizeDisplay = drop.unit === 'kg' ? Math.round(drop.size * 1000) : drop.size;
return `${fillDisplay}g of ${sizeDisplay}g reserved`;
return `${fillDisplay}g ${t('drop.of')} ${sizeDisplay}g ${t('drop.reserved')}`;
})()}
</div>
{(() => {
@@ -663,7 +731,7 @@ export default function Drop() {
return pendingFill > 0 && (
<div className="meta" style={{ fontSize: '12px', color: 'var(--muted)', marginTop: '4px' }}>
{drop.unit === 'kg' ? pendingFill.toFixed(2) : Math.round(pendingFill)}
{drop.unit} on hold (10 min checkout window)
{drop.unit} {t('drop.onHold')}
</div>
)
})()}
@@ -692,7 +760,7 @@ export default function Drop() {
value={customQuantity}
onChange={(e) => handleCustomQuantityChange(e.target.value)}
onBlur={validateCustomQuantity}
placeholder="Custom (g)"
placeholder={t('drop.custom')}
min={getMinimumGrams()}
max={getRemainingInGrams()}
style={{
@@ -712,7 +780,7 @@ export default function Drop() {
)}
{!quantityError && customQuantity && (
<div style={{ marginTop: '6px', fontSize: '12px', color: 'var(--muted)' }}>
Min: {getMinimumGrams()}g · Max: {getRemainingInGrams()}g
{t('drop.min')}: {getMinimumGrams()}g · {t('drop.max')}: {getRemainingInGrams()}g
</div>
)}
</div>
@@ -722,42 +790,42 @@ export default function Drop() {
{isWholesaleUnlocked ? (
<>
<div style={{ fontSize: '18px', fontWeight: 500, marginBottom: '8px' }}>
Total: {calculatePrice().toFixed(2)} CHF
{t('drop.total')}: {calculatePrice().toFixed(2)} {currency}
</div>
<div style={{ fontSize: '14px', color: 'var(--muted)' }}>
Standard total: {calculateStandardPrice().toFixed(2)} CHF
{t('drop.standardTotal')}: {calculateStandardPrice().toFixed(2)} {currency}
</div>
</>
) : (
<>
<div style={{ fontSize: '18px', fontWeight: 500, marginBottom: '8px' }}>
Total: {calculateStandardPrice().toFixed(2)} CHF
{t('drop.total')}: {calculateStandardPrice().toFixed(2)} {currency}
</div>
<div style={{ fontSize: '14px', color: 'var(--muted)', display: 'flex', alignItems: 'center', gap: '6px' }}>
Wholesale total: {calculateWholesalePrice().toFixed(2)} CHF 🔒
{t('drop.wholesaleTotal')}: {calculateWholesalePrice().toFixed(2)} {currency} 🔒
</div>
</>
)}
</div>
<button className="cta" onClick={handleJoinDrop}>
Join the drop
{t('drop.joinTheDrop')}
</button>
<div className="cta-note">No subscription · No obligation</div>
<div className="cta-note">{t('drop.noSubscription')}</div>
</>
)}
{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.
{t('drop.lessThanRemaining', { amount: 50, unit: drop.unit })}
</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>
<p style={{ margin: 0, color: 'var(--muted)' }}>{t('drop.fullyReservedText')}</p>
</div>
)}
</div>
@@ -792,34 +860,34 @@ export default function Drop() {
onClick={(e) => e.stopPropagation()}
>
<h2 style={{ marginTop: 0, marginBottom: '20px' }}>
Confirm Purchase
{t('drop.confirmPurchase')}
</h2>
<div style={{ marginBottom: '24px' }}>
<p style={{ marginBottom: '12px', color: 'var(--muted)' }}>
<strong>Item:</strong> {drop.item}
<strong>{t('drop.item')}:</strong> {drop.item}
</p>
<p style={{ marginBottom: '12px', color: 'var(--muted)' }}>
<strong>Quantity:</strong> {selectedSize}g
<strong>{t('drop.quantity')}:</strong> {selectedSize}g
</p>
<p style={{ marginBottom: '12px', color: 'var(--muted)' }}>
<strong>Price per {drop.unit}:</strong> {(drop.ppu / 1000).toFixed(2)} CHF
<strong>{t('drop.pricePerUnit', { unit: drop.unit })}:</strong> {getPricePerGram().toFixed(2)} {currency}
</p>
{/* Delivery Information */}
<div style={{ marginTop: '24px', marginBottom: '16px' }}>
<h3 style={{ marginBottom: '16px', fontSize: '16px', color: 'var(--text)' }}>
Delivery Information
{t('drop.deliveryInformation')}
</h3>
<div style={{ marginBottom: '12px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', color: 'var(--muted)' }}>
<strong>Full Name *</strong>
<strong>{t('drop.fullNameRequired')}</strong>
</label>
<input
type="text"
value={buyerFullname}
onChange={(e) => setBuyerFullname(e.target.value)}
placeholder="Enter your full name"
placeholder={t('drop.enterFullName')}
required
style={{
width: '100%',
@@ -836,12 +904,12 @@ export default function Drop() {
<div style={{ marginBottom: '12px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', color: 'var(--muted)' }}>
<strong>Address *</strong>
<strong>{t('drop.addressRequired')}</strong>
</label>
<textarea
value={buyerAddress}
onChange={(e) => setBuyerAddress(e.target.value)}
placeholder="Enter your delivery address"
placeholder={t('drop.enterAddress')}
required
rows={3}
style={{
@@ -861,13 +929,13 @@ export default function Drop() {
<div style={{ marginBottom: '12px' }}>
<label style={{ display: 'block', marginBottom: '6px', fontSize: '14px', color: 'var(--muted)' }}>
<strong>Phone Number *</strong>
<strong>{t('drop.phoneRequired')}</strong>
</label>
<input
type="tel"
value={buyerPhone}
onChange={(e) => setBuyerPhone(e.target.value)}
placeholder="Enter your phone number"
placeholder={t('drop.enterPhone')}
required
style={{
width: '100%',
@@ -886,10 +954,10 @@ export default function Drop() {
{/* Currency Selection */}
<div style={{ marginTop: '24px', marginBottom: '16px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', color: 'var(--muted)' }}>
<strong>Payment Currency:</strong>
<strong>{t('drop.paymentCurrency')}:</strong>
</label>
{loadingCurrencies ? (
<p style={{ color: 'var(--muted)', fontSize: '14px' }}>Loading currencies...</p>
<p style={{ color: 'var(--muted)', fontSize: '14px' }}>{t('drop.loadingCurrencies')}</p>
) : (
<select
value={selectedCurrency}
@@ -941,9 +1009,32 @@ export default function Drop() {
marginTop: '16px',
}}
>
<p style={{ margin: 0, fontSize: '18px', fontWeight: 'bold' }}>
Total: {calculatePrice().toFixed(2)} CHF
</p>
<div style={{ marginBottom: '12px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style={{ color: 'var(--muted)', fontSize: '14px' }}>{t('drop.subtotal')}:</span>
<span style={{ fontWeight: 500, fontSize: '14px' }}>
{calculatePrice().toFixed(2)} {currency}
</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style={{ color: 'var(--muted)', fontSize: '14px' }}>{t('drop.shippingFee')}:</span>
<span style={{ fontWeight: 500, fontSize: '14px' }}>
{loadingShippingFee ? '...' : (shippingFee !== null ? shippingFee.toFixed(2) : '40.00')} {currency}
</span>
</div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
marginTop: '12px',
paddingTop: '12px',
borderTop: '1px solid var(--border)'
}}>
<span style={{ fontSize: '18px', fontWeight: 'bold' }}>{t('drop.total')}:</span>
<span style={{ fontSize: '18px', fontWeight: 'bold' }}>
{loadingShippingFee ? '...' : ((calculatePrice() + (shippingFee || 40)).toFixed(2))} {currency}
</span>
</div>
</div>
<p
style={{
margin: '4px 0 0 0',
@@ -951,7 +1042,7 @@ export default function Drop() {
color: 'var(--muted)',
}}
>
incl. 2.5% VAT
{t('drop.inclVat')}
</p>
<p
style={{
@@ -960,7 +1051,7 @@ export default function Drop() {
color: 'var(--muted)',
}}
>
Pay with: {String(selectedCurrency || '').toUpperCase()}
{t('drop.payWith')}: {String(selectedCurrency || '').toUpperCase()}
</p>
</div>
</div>
@@ -989,7 +1080,7 @@ export default function Drop() {
display: 'inline-block',
}}
>
Cancel
{t('common.cancel')}
</button>
<button
onClick={handleConfirmPurchase}
@@ -1009,7 +1100,7 @@ export default function Drop() {
display: 'inline-block',
}}
>
{processing ? 'Processing...' : 'Confirm Purchase'}
{processing ? t('common.processing') : t('drop.confirmPurchase')}
</button>
</div>
</div>
@@ -1061,14 +1152,14 @@ export default function Drop() {
onClick={(e) => e.stopPropagation()}
>
<h2 style={{ marginTop: 0, marginBottom: '20px', color: '#0a7931' }}>
Payment confirmed
{t('drop.paymentConfirmed')}
</h2>
<p style={{ marginBottom: '16px', color: 'var(--text)' }}>
Your order has been successfully processed and is now reserved in this drop.
{t('drop.orderProcessed')}
</p>
<div style={{ marginBottom: '24px' }}>
<p style={{ marginBottom: '12px', fontWeight: '600', color: 'var(--text)' }}>
What happens next:
{t('drop.whatHappensNext')}:
</p>
<ul style={{
margin: 0,
@@ -1076,13 +1167,13 @@ export default function Drop() {
color: 'var(--muted)',
lineHeight: '1.8'
}}>
<li>Your order will be processed within 24 hours</li>
<li>Shipped via express delivery</li>
<li>You'll receive a shipping confirmation and tracking link by email</li>
<li>{t('drop.orderProcessed24h')}</li>
<li>{t('drop.shippedExpress')}</li>
<li>{t('drop.shippingConfirmation')}</li>
</ul>
</div>
<p style={{ marginBottom: '24px', color: 'var(--muted)', fontStyle: 'italic' }}>
Thank you for being part of the collective.
{t('drop.thankYouCollective')}
</p>
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
<button
@@ -1104,7 +1195,7 @@ export default function Drop() {
display: 'inline-block',
}}
>
Close
{t('common.close')}
</button>
</div>
</div>
@@ -1145,7 +1236,7 @@ export default function Drop() {
onClick={(e) => e.stopPropagation()}
>
<h2 style={{ marginTop: 0, marginBottom: '20px', color: '#dc2626' }}>
Error
{t('drop.error')}
</h2>
<p style={{ marginBottom: '24px', color: 'var(--muted)' }}>
{errorMessage}
@@ -1172,7 +1263,7 @@ export default function Drop() {
display: 'inline-block',
}}
>
Close
{t('common.close')}
</button>
</div>
</div>
@@ -1240,20 +1331,50 @@ export default function Drop() {
onClick={(e) => e.stopPropagation()}
>
<h2 style={{ marginTop: 0, marginBottom: '20px' }}>
Complete Payment
{t('drop.completePayment')}
</h2>
<div style={{ marginBottom: '24px' }}>
<p style={{ marginBottom: '12px', color: 'var(--muted)' }}>
<strong>Amount to Pay:</strong> {paymentData.pay_amount} {paymentData.pay_currency.toUpperCase()}
</p>
<p style={{ marginBottom: '12px', color: 'var(--muted)' }}>
<strong>Price:</strong> {paymentData.price_amount} {paymentData.price_currency.toUpperCase()}
<strong>{t('drop.amountToPay')}:</strong> {paymentData.pay_amount} {paymentData.pay_currency.toUpperCase()}
</p>
<div style={{
marginTop: '20px',
marginBottom: '20px',
padding: '16px',
background: 'var(--bg-soft)',
borderRadius: '8px'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style={{ color: 'var(--muted)' }}>{t('drop.subtotal')}:</span>
<span style={{ fontWeight: 500 }}>
{paymentData.subtotal?.toFixed(2) || (paymentData.price_amount - (paymentData.shipping_fee || 0)).toFixed(2)} {paymentData.price_currency.toUpperCase()}
</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style={{ color: 'var(--muted)' }}>{t('drop.shippingFee')}:</span>
<span style={{ fontWeight: 500 }}>
{paymentData.shipping_fee?.toFixed(2) || '0.00'} {paymentData.price_currency.toUpperCase()}
</span>
</div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
marginTop: '12px',
paddingTop: '12px',
borderTop: '1px solid var(--border)'
}}>
<span style={{ fontWeight: 600, fontSize: '16px' }}>{t('drop.total')}:</span>
<span style={{ fontWeight: 600, fontSize: '16px' }}>
{paymentData.price_amount} {paymentData.price_currency.toUpperCase()}
</span>
</div>
</div>
<div style={{ marginTop: '20px', marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', color: 'var(--muted)' }}>
Send payment to this address:
{t('drop.sendPaymentTo')}
</label>
<div
style={{
@@ -1288,14 +1409,14 @@ export default function Drop() {
fontSize: '14px',
}}
>
Copy Address
{t('drop.copyAddress')}
</button>
</div>
{paymentData.payin_extra_id && (
<div style={{ marginTop: '20px', marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontSize: '14px', color: 'var(--muted)' }}>
Memo / Destination Tag (Required):
{t('drop.memoRequired')}:
</label>
<div
style={{
@@ -1318,34 +1439,34 @@ export default function Drop() {
button.textContent = 'Copied!'
setTimeout(() => {
if (button) button.textContent = originalText
}, 2000)
}}
style={{
padding: '8px 16px',
background: 'var(--bg-soft)',
border: '1px solid var(--border)',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '14px',
}}
>
Copy Memo
</button>
}, 2000)
}}
style={{
padding: '8px 16px',
background: 'var(--bg-soft)',
border: '1px solid var(--border)',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '14px',
}}
>
{t('drop.copyMemo')}
</button>
</div>
)}
{paymentData.expiration_estimate_date && (
<p style={{ marginTop: '16px', fontSize: '12px', color: 'var(--muted)' }}>
Payment expires: {new Date(paymentData.expiration_estimate_date).toLocaleString()}
{t('drop.paymentExpires')}: {new Date(paymentData.expiration_estimate_date).toLocaleString()}
</p>
)}
<div style={{ marginTop: '24px', padding: '16px', background: 'var(--bg-soft)', borderRadius: '8px' }}>
<p style={{ margin: 0, fontSize: '14px', color: 'var(--muted)' }}>
<strong>Status:</strong> {paymentData.payment_status}
<strong>{t('drop.status')}:</strong> {paymentData.payment_status}
</p>
<p style={{ margin: '12px 0 0 0', fontSize: '12px', color: '#dc2626', fontWeight: 500 }}>
Closing this window will cancel your reservation and free up the inventory.
{t('drop.closingWarning')}
</p>
</div>
</div>
@@ -1398,7 +1519,7 @@ export default function Drop() {
display: 'inline-block',
}}
>
Close
{t('common.close')}
</button>
</div>
</div>