Scaffold and Phase 1
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Real-time state hook for the silent auction catalog.
|
||||
* Subscribes to silent_bid_accepted, silent_outbid, silent_window_closing,
|
||||
* silent_window_extended, silent_item_closed.
|
||||
*/
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { getSocket } from "../lib/socket.js";
|
||||
import { useOfflineBids } from "./useOfflineBids.js";
|
||||
import { useAuthStore } from "../store/auth.js";
|
||||
import type { AuctionItem } from "@storybid/shared";
|
||||
|
||||
export function useSilentAuction(eventId: string) {
|
||||
const [items, setItems] = useState<AuctionItem[]>([]);
|
||||
const [outbidItemIds, setOutbidItemIds] = useState<Set<string>>(new Set());
|
||||
const bidderId = useAuthStore((s) => s.bidder?.id);
|
||||
const { queueBid, getDeviceId } = useOfflineBids();
|
||||
|
||||
let clientSeq = 0;
|
||||
|
||||
useEffect(() => {
|
||||
const socket = getSocket();
|
||||
socket.emit("join_event", eventId);
|
||||
|
||||
socket.on("silent_bid_accepted", ({ item }) => {
|
||||
setItems((prev) =>
|
||||
prev.map((i) => (i.id === item.id ? item : i)),
|
||||
);
|
||||
// Clear outbid flag if we just won
|
||||
if (item.currentHighBidderId === bidderId) {
|
||||
setOutbidItemIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(item.id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("silent_outbid", ({ itemId }) => {
|
||||
setOutbidItemIds((prev) => new Set([...prev, itemId]));
|
||||
});
|
||||
|
||||
socket.on("silent_item_closed", ({ itemId }) => {
|
||||
setItems((prev) =>
|
||||
prev.map((i) => (i.id === itemId ? { ...i, state: "closed" } : i)),
|
||||
);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.emit("leave_event", eventId);
|
||||
socket.off("silent_bid_accepted");
|
||||
socket.off("silent_outbid");
|
||||
socket.off("silent_item_closed");
|
||||
};
|
||||
}, [eventId, bidderId]);
|
||||
|
||||
const placeSilentBid = useCallback(
|
||||
async (itemId: string, amount: number) => {
|
||||
if (!bidderId) return;
|
||||
const socket = getSocket();
|
||||
const deviceId = getDeviceId();
|
||||
const seq = ++clientSeq;
|
||||
|
||||
if (socket.connected) {
|
||||
socket.emit("place_silent_bid", {
|
||||
itemId,
|
||||
amount,
|
||||
deviceId,
|
||||
clientSeq: seq,
|
||||
clientCreatedAt: new Date().toISOString(),
|
||||
});
|
||||
} else {
|
||||
// Offline – write to IndexedDB outbox
|
||||
await queueBid(itemId, bidderId, amount);
|
||||
}
|
||||
},
|
||||
[bidderId, getDeviceId, queueBid],
|
||||
);
|
||||
|
||||
return { items, setItems, outbidItemIds, placeSilentBid };
|
||||
}
|
||||
Reference in New Issue
Block a user