@@ -64,6 +64,9 @@ const App: React.FC = () => {
6464 switch ( event . type ) {
6565 case 'START' :
6666 soundService . playDrop ( ) ;
67+ if ( event . participants && event . participants . length > 0 ) {
68+ setFounders ( buildParticipants ( event . participantCount ?? event . participants . length , event . participants ) ) ;
69+ }
6770 setGameState ( {
6871 currentPrice : event . startPrice ,
6972 status : AuctionStatus . RUNNING ,
@@ -144,7 +147,13 @@ const App: React.FC = () => {
144147
145148 if ( isRemote ) {
146149 try {
147- await syncService . sendEvent ( { type : 'START' , startTime, startPrice : config . startPrice } ) ;
150+ await syncService . sendEvent ( {
151+ type : 'START' ,
152+ startTime,
153+ startPrice : config . startPrice ,
154+ participantCount : founders . length ,
155+ participants : founders . map ( ( f ) => ( { id : f . id , name : f . name , color : f . color } ) ) ,
156+ } ) ;
148157 } catch ( err ) {
149158 showError ( err ) ;
150159 }
@@ -212,27 +221,13 @@ const App: React.FC = () => {
212221 } ;
213222
214223 useEffect ( ( ) => {
215- let timer : number | null = null ;
216-
217- const refreshClaims = async ( ) => {
218- if ( ! isRemote || ! isConnected ) return ;
219- const claimed = await syncService . listClaimedParticipants ( ) ;
220- setClaimedIds ( claimed ) ;
221- } ;
222-
223- void refreshClaims ( ) ;
224- if ( isRemote && isConnected ) {
225- timer = window . setInterval ( ( ) => {
226- void refreshClaims ( ) ;
227- } , 3000 ) ;
228- }
229-
224+ syncService . onParticipantsChanged = isRemote && isConnected ? ( claimed ) => setClaimedIds ( claimed ) : null ;
230225 return ( ) => {
231- if ( timer ) window . clearInterval ( timer ) ;
226+ syncService . onParticipantsChanged = null ;
232227 } ;
233228 } , [ isRemote , isConnected ] ) ;
234229
235- const applySetup = ( ) => {
230+ const applySetup = async ( ) => {
236231 const nextConfig : AuctionConfig = {
237232 startPrice : Math . max ( 1 , Math . floor ( draftConfig . startPrice ) ) ,
238233 floorPrice : Math . max ( 1 , Math . floor ( draftConfig . floorPrice ) ) ,
@@ -264,6 +259,15 @@ const App: React.FC = () => {
264259 history : [ ] ,
265260 nextDropTime : 0 ,
266261 } ) ;
262+
263+ if ( isRemote && isConnected && isHost ) {
264+ try {
265+ await syncService . publishRoomConfig ( nextFounders , nextFounders . length ) ;
266+ } catch ( err ) {
267+ showError ( err ) ;
268+ }
269+ }
270+
267271 setSetupOpen ( false ) ;
268272 } ;
269273
@@ -286,23 +290,46 @@ const App: React.FC = () => {
286290 </ div >
287291
288292 < div className = "flex items-center gap-2 md:gap-4" >
289- < button disabled = { ( isRemote && isConnected ) || gameState . status === AuctionStatus . RUNNING || ( isRemote && ! isHost ) } onClick = { ( ) => { setDraftConfig ( config ) ; setDraftInitials ( founders . map ( ( f ) => f . name ) . join ( ', ' ) ) ; setSetupOpen ( true ) ; } } className = "text-[9px ] md:text-[10px] uppercase font-bold text-slate-500 hover:text-amber-300 disabled:opacity-40 disabled:cursor-not-allowed transition-colors" >
293+ < button disabled = { ( isRemote && isConnected ) || gameState . status === AuctionStatus . RUNNING || ( isRemote && ! isHost ) } onClick = { ( ) => { setDraftConfig ( config ) ; setDraftInitials ( founders . map ( ( f ) => f . name ) . join ( ', ' ) ) ; setSetupOpen ( true ) ; } } className = "text-[11px ] md:text-xs uppercase font-bold text-slate-400 hover:text-amber-300 disabled:opacity-40 disabled:cursor-not-allowed transition-colors" >
290294 Setup
291295 </ button >
292296 { ! isRemote ? (
293- < button onClick = { ( ) => setIsRemote ( true ) } className = "text-[9px ] md:text-[10px] uppercase font-bold text-slate-500 hover:text-cyan-400 transition-colors" >
297+ < button onClick = { ( ) => setIsRemote ( true ) } className = "text-[11px ] md:text-xs uppercase font-bold text-slate-400 hover:text-cyan-400 transition-colors" >
294298 Go Remote
295299 </ button >
296300 ) : (
297301 < div className = "flex items-center gap-2" >
298302 { ! isConnected ? (
299303 < div className = "flex gap-1" >
300- < input type = "text" placeholder = "Room" className = "bg-slate-800 border border-slate-700 rounded px-2 py-1 text-[10px] w-16 md:w-24 outline-none focus:border-cyan-500" value = { roomCode } onChange = { ( e ) => setRoomCode ( e . target . value ) } />
301- < button onClick = { async ( ) => { if ( ! roomCode . trim ( ) ) { showError ( 'Please enter a room code.' ) ; return ; } await syncService . joinRoom ( roomCode , handleRemoteEvent ) ; setIsConnected ( true ) ; setIsHost ( syncService . isHost ( ) ) ; setClaimedIds ( await syncService . listClaimedParticipants ( ) ) ; } } className = "bg-cyan-600 px-2 py-1 rounded text-[10px] font-bold" > JOIN</ button >
304+ < input type = "text" placeholder = "Room code" className = "bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-xs w-28 md:w-32 outline-none focus:border-cyan-500" value = { roomCode } onChange = { ( e ) => setRoomCode ( e . target . value ) } />
305+ < button onClick = { async ( ) => {
306+ if ( ! roomCode . trim ( ) ) {
307+ showError ( 'Please enter a room code.' ) ;
308+ return ;
309+ }
310+ try {
311+ const roomInfo = await syncService . joinRoom ( roomCode , handleRemoteEvent ) ;
312+ const amHost = syncService . isHost ( ) ;
313+ setIsConnected ( true ) ;
314+ setIsHost ( amHost ) ;
315+
316+ if ( ! amHost && roomInfo . participants && roomInfo . participants . length > 0 ) {
317+ setFounders ( buildParticipants ( roomInfo . participantCount ?? roomInfo . participants . length , roomInfo . participants ) ) ;
318+ }
319+
320+ if ( amHost ) {
321+ await syncService . publishRoomConfig ( founders , founders . length ) ;
322+ }
323+
324+ setClaimedIds ( await syncService . listClaimedParticipants ( ) ) ;
325+ } catch ( err ) {
326+ showError ( err ) ;
327+ }
328+ } } className = "bg-cyan-600 px-2 py-1 rounded text-[10px] md:text-xs font-bold" > JOIN</ button >
302329 </ div >
303330 ) : (
304331 < div className = "flex items-center gap-2" >
305- < span className = "text-[9px ] md:text-[10px] text-cyan-300 font-mono uppercase" > Room: { roomCode } </ span >
332+ < span className = "text-[11px ] md:text-xs text-cyan-300 font-mono uppercase" > Room: { roomCode } </ span >
306333 < button
307334 onClick = { async ( ) => {
308335 try {
@@ -313,11 +340,11 @@ const App: React.FC = () => {
313340 showError ( 'Could not copy room code.' ) ;
314341 }
315342 } }
316- className = "text-[9px ] md:text-[10px] text-cyan-400 uppercase font-bold"
343+ className = "text-[11px ] md:text-xs text-cyan-400 uppercase font-bold"
317344 >
318345 Copy
319346 </ button >
320- < button onClick = { ( ) => { syncService . leaveRoom ( ) ; setIsConnected ( false ) ; setIsHost ( false ) ; setClaimedIds ( new Set ( ) ) ; setIsRemote ( false ) ; setMyFounderId ( null ) ; } } className = "text-[9px ] md:text-[10px] text-red-500 uppercase font-bold" > Disconnect</ button >
347+ < button onClick = { ( ) => { syncService . leaveRoom ( ) ; setIsConnected ( false ) ; setIsHost ( false ) ; setClaimedIds ( new Set ( ) ) ; setIsRemote ( false ) ; setMyFounderId ( null ) ; } } className = "text-[11px ] md:text-xs text-red-500 uppercase font-bold" > Disconnect</ button >
321348 </ div >
322349 ) }
323350 </ div >
@@ -373,7 +400,7 @@ const App: React.FC = () => {
373400
374401 < div className = "mt-5 flex justify-end gap-2" >
375402 < button onClick = { ( ) => { setDraftConfig ( config ) ; setDraftInitials ( founders . map ( ( f ) => f . name ) . join ( ', ' ) ) ; setSetupOpen ( false ) ; } } className = "px-3 py-2 rounded border border-slate-700" > Cancel</ button >
376- < button onClick = { applySetup } className = "px-3 py-2 rounded bg-cyan-500 text-slate-900 font-bold" > Apply & Reset </ button >
403+ < button onClick = { ( ) => { void applySetup ( ) ; } } className = "px-3 py-2 rounded bg-cyan-500 text-slate-900 font-bold" > Apply & Reset </ button >
377404 </ div >
378405 </ div >
379406 </ div >
@@ -415,7 +442,19 @@ const App: React.FC = () => {
415442 </ button >
416443 < div className = "grid grid-cols-1 gap-3" > { founders . map ( ( f ) => {
417444 const alreadyClaimed = claimedIds . has ( f . id ) ;
418- return < button key = { f . id } disabled = { alreadyClaimed } onClick = { async ( ) => { const ok = await syncService . claimParticipant ( f . id ) ; if ( ok ) { setMyFounderId ( f . id ) ; setClaimedIds ( await syncService . listClaimedParticipants ( ) ) ; } else { showError ( 'This participant is already claimed. Choose another.' ) ; } } } className = { `p-3 md:p-4 rounded-xl border-2 border-slate-800 transition-all font-bold text-base md:text-lg ${ f . color . replace ( 'bg-' , 'text-' ) } ${ alreadyClaimed ? 'opacity-40 cursor-not-allowed' : 'hover:border-cyan-500' } ` } >
445+ return < button key = { f . id } disabled = { alreadyClaimed } onClick = { async ( ) => {
446+ try {
447+ const ok = await syncService . claimParticipant ( f . id ) ;
448+ if ( ok ) {
449+ setMyFounderId ( f . id ) ;
450+ setClaimedIds ( await syncService . listClaimedParticipants ( ) ) ;
451+ } else {
452+ showError ( 'This participant is already claimed. Choose another.' ) ;
453+ }
454+ } catch ( err ) {
455+ showError ( err ) ;
456+ }
457+ } } className = { `p-3 md:p-4 rounded-xl border-2 border-slate-800 transition-all font-bold text-base md:text-lg ${ f . color . replace ( 'bg-' , 'text-' ) } ${ alreadyClaimed ? 'opacity-40 cursor-not-allowed' : 'hover:border-cyan-500' } ` } >
419458 < div > { f . name } </ div >
420459 < div className = "text-[10px] mt-1 text-slate-400" > { alreadyClaimed ? 'Already joined' : 'Available' } </ div >
421460 </ button > ;
@@ -427,9 +466,9 @@ const App: React.FC = () => {
427466 < main className = "flex-1 flex flex-col md:flex-row overflow-hidden" >
428467 < div className = "flex-1 flex flex-col p-3 md:p-6 relative min-w-0" >
429468 < div className = "flex-none mb-2 border-b border-slate-800 pb-2" >
430- < h3 className = "text-[8px ] md:text-xs font-bold text-slate-600 uppercase tracking-widest mb-1 md:mb-3" > History</ h3 >
469+ < h3 className = "text-[10px ] md:text-xs font-bold text-slate-500 uppercase tracking-widest mb-1 md:mb-3" > History</ h3 >
431470 < div className = "flex gap-1.5 md:gap-2 overflow-x-auto pb-1 mask-linear scrollbar-hide" >
432- { gameState . history . slice ( ) . reverse ( ) . map ( ( log , idx ) => < div key = { idx } className = "flex-shrink-0 px-2 py-1 rounded bg-slate-900 border border-slate-800 text-[9px] md:text-[10px] font-mono whitespace-nowrap" > < span className = { log . event === 'WIN' ? 'text-green-400' : 'text-slate-400 ' } > ${ log . price . toLocaleString ( ) } </ span > < span className = "mx-1 opacity-20" > |</ span > < span className = "opacity-70 " > { log . event === 'WIN' ? log . details : log . event } </ span > </ div > ) }
471+ { gameState . history . slice ( ) . reverse ( ) . map ( ( log , idx ) => < div key = { idx } className = "flex-shrink-0 px-2 py-1 rounded bg-slate-900 border border-slate-800 text-xs md:text-sm font-mono whitespace-nowrap" > < span className = { log . event === 'WIN' ? 'text-green-400' : 'text-slate-300 ' } > ${ log . price . toLocaleString ( ) } </ span > < span className = "mx-1 opacity-20" > |</ span > < span className = "opacity-75 " > { log . event === 'WIN' ? log . details : log . event } </ span > </ div > ) }
433472 </ div >
434473 </ div >
435474
0 commit comments