// DEPLOY_CONFIG: {"cron": [{"name": "end-auction-trigger", "schedule": "* * * * *", "action": "event", "config": {"event_type": "auction.end"}}], "triggers": [{"name": "auction-history-email-notify", "on": "collection.add", "collection": "auction-history", "actions": [{"type": "email", "to": "{{winner_email}}", "subject": "You won the auction: {{item_title}}", "body": "Congratulations! You won the auction for {{item_title}} with a bid of {{winning_bid}}."}, {"type": "email", "to": "{{lister_email}}", "subject": "Your auction ended: {{item_title}}", "body": "The auction for {{item_title}} has ended. Winner: {{winner_name}} with a bid of {{winning_bid}}."}]}]} import { useState, useCallback, useEffect } from 'react'; import { useBroadcast, usePresence, useAuth, useIdentity, playSound } from '@deplixo/sdk'; import { AuctionItems } from './components/AuctionItems.jsx'; import { AddItem } from './components/AddItem.jsx'; // PROGRESS:sc_001:complete:Setting up the silent auction hub function App() { const [activeTab, setActiveTab] = useState('auction'); const [editItem, setEditItem] = useState(null); const [activeBidders, setActiveBidders] = useState([]); const [warningSoundedItems, setWarningSoundedItems] = useState([]); const [recentBidIds, setRecentBidIds] = useState([]); const [bidderName, setBidderName] = useState(''); const [bidderAvatar, setBidderAvatar] = useState(''); const auth = useAuth(); const identity = useIdentity(); const user = auth.user || identity.user || null; useEffect(() => { if (!user) return; const resolvedName = user.name || user.displayName || user.email || 'Anonymous bidder'; const resolvedAvatar = user.avatar || user.photoURL || user.image || ''; setBidderName(prev => prev || resolvedName); setBidderAvatar(prev => prev || resolvedAvatar); }, [user]); const handleBidBroadcast = useCallback((data) => { // Re-broadcasting handler in App is for presence-aware cross-tab coordination only. // The actual bid event handling lives in AuctionItems; this hook ensures the app // is wired for real-time bid traffic at the root level if needed later. if (data?.type === 'new-bid') { setRecentBidIds(prev => { const bidKey = data?.bidId || data?.itemId || JSON.stringify({ itemId: data?.itemId, amount: data?.amount, ts: data?.timestamp }); if (prev.includes(bidKey)) return prev; playSound('@ding'); return [...prev, bidKey].slice(-50); }); } return data; }, []); const { send: sendBidEvent } = useBroadcast('auction-bid', handleBidBroadcast); const handlePresenceUpdate = useCallback((users) => { setActiveBidders(Array.isArray(users) ? users : []); }, []); usePresence({ id: user?.id || user?.email || 'guest', name: bidderName || user?.name || user?.displayName || user?.email || 'Anonymous bidder', role: 'bidder', avatar: bidderAvatar || user?.avatar || user?.photoURL || user?.image || '', }, handlePresenceUpdate); useEffect(() => { if (user) { setActiveBidders(prev => { const exists = prev.some(u => (u?.id || u?.userId) === (user.id || user.email)); if (exists) return prev; return [ ...prev, { id: user.id || user.email, name: bidderName || user.name || user.displayName || user.email || 'Anonymous bidder', avatar: bidderAvatar || user.avatar || user.photoURL || user.image || '', }, ]; }); } }, [user, bidderName, bidderAvatar]); const handleBidPlaced = useCallback((bidEvent) => { const bidder = { id: user?.id || user?.email || 'guest', name: bidderName || user?.name || user?.displayName || user?.email || 'Anonymous bidder', avatar: bidderAvatar || user?.avatar || user?.photoURL || user?.image || '', }; sendBidEvent({ ...bidEvent, type: 'new-bid', bidder, timestamp: Date.now(), }); playSound('@success'); }, [sendBidEvent, user, bidderName, bidderAvatar]); const handleTimeWarning = useCallback((warningEvent) => { const warningKey = warningEvent?.itemId || warningEvent?.id || JSON.stringify(warningEvent || {}); setWarningSoundedItems(prev => { if (prev.includes(warningKey)) return prev; playSound('@beep'); return [...prev, warningKey].slice(-50); }); }, []); return (
Place your bids before time runs out