1- import { useState , useEffect , useCallback , useMemo } from 'react'
1+ import React , { useState , useEffect , useCallback , useMemo } from 'react'
22import { useConnection , useWallet } from '@solana/wallet-adapter-react'
33import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'
44import {
@@ -55,12 +55,13 @@ export default function Board() {
5555 const { publicKey, connected, sendTransaction } = useWallet ( )
5656 const [ jobs , setJobs ] = useState < JobOnChain [ ] > ( [ ] )
5757 const [ loading , setLoading ] = useState ( false )
58- const [ actionLog , setActionLog ] = useState < string [ ] > ( [ ] )
58+ const [ actionLog , setActionLog ] = useState < React . ReactNode [ ] > ( [ ] )
5959 const [ acting , setActing ] = useState < string | null > ( null )
6060 const [ rewardSol , setRewardSol ] = useState ( '0.1' )
6161 const [ jobDesc , setJobDesc ] = useState ( 'Summarize this research paper' )
6262 const [ jobTitle , setJobTitle ] = useState ( 'Research Task' )
6363 const [ metadataMap , setMetadataMap ] = useState < Record < string , TaskMetadata > > ( { } )
64+ const [ activeOnly , setActiveOnly ] = useState ( false )
6465
6566 const erBurner = useMemo ( ( ) => getBurner ( ) , [ ] )
6667
@@ -74,10 +75,16 @@ export default function Board() {
7475 return new anchor . Program ( idl as any , provider )
7576 } , [ publicKey , connection ] )
7677
77- const log = useCallback ( ( msg : string ) => {
78- setActionLog ( prev => [ ...prev . slice ( - 20 ) , ` ${ new Date ( ) . toLocaleTimeString ( ) } $ {msg } ` ] )
78+ const log = useCallback ( ( msg : React . ReactNode ) => {
79+ setActionLog ( prev => [ ...prev . slice ( - 20 ) , < span key = { Date . now ( ) } > [ { new Date ( ) . toLocaleTimeString ( ) } ] { msg } </ span > ] )
7980 } , [ ] )
8081
82+ const txLink = ( sig : string ) => (
83+ < a href = { `https://solscan.io/tx/${ sig } ?cluster=devnet` } target = "_blank" rel = "noreferrer" className = "tx-link" >
84+ { sig . slice ( 0 , 12 ) } …
85+ </ a >
86+ )
87+
8188 // Fetch all jobs
8289 const fetchJobs = useCallback ( async ( ) => {
8390 setLoading ( true )
@@ -210,7 +217,7 @@ export default function Board() {
210217 . transaction ( )
211218
212219 const sig = await sendTx ( connection , tx )
213- log ( ` ✅ Job #$ {jobId } created ($ {rewardSol } SOL escrowed) tx:${ sig . slice ( 0 , 12 ) } ...` )
220+ log ( < > ✅ Job #{ jobId } created ({ rewardSol } SOL escrowed) tx:{ txLink ( sig ) } </ > )
214221
215222 // Save hash + metadata locally for display
216223 localStorage . setItem ( `tf_hash_${ jobPDA . toBase58 ( ) } ` , metaHash )
@@ -224,7 +231,7 @@ export default function Board() {
224231 . accounts ( { payer : publicKey , job : jobPDA } )
225232 . transaction ( )
226233 const sig2 = await sendTx ( connection , delegateTx )
227- log ( ` ✅ Job is now open for bidding! tx:${ sig2 . slice ( 0 , 12 ) } ...` )
234+ log ( < > ✅ Job is now open for bidding! tx:{ txLink ( sig2 ) } </ > )
228235
229236 await fetchJobs ( )
230237 } catch ( e ) {
@@ -262,7 +269,7 @@ export default function Board() {
262269 tx . feePayer = erBurner . publicKey
263270 tx . sign ( erBurner )
264271 const sig = await erConn . sendRawTransaction ( tx . serialize ( ) )
265- log ( ` ⚡ Bid placed! stake=$ {( stakeAmount / LAMPORTS_PER_SOL ) . toFixed ( 4 ) } SOL tx:${ sig . slice ( 0 , 12 ) } ...` )
272+ log ( < > ⚡ Bid placed! stake={ ( stakeAmount / LAMPORTS_PER_SOL ) . toFixed ( 4 ) } SOL tx:{ txLink ( sig ) } </ > )
266273
267274 // Auto-close bidding
268275 log ( 'Closing bidding...' )
@@ -275,7 +282,7 @@ export default function Board() {
275282 closeTx . feePayer = erBurner . publicKey
276283 closeTx . sign ( erBurner )
277284 const sig2 = await erConn . sendRawTransaction ( closeTx . serialize ( ) )
278- log ( ` 🔒 Bidding closed, committing to L1... tx:${ sig2 . slice ( 0 , 12 ) } ...` )
285+ log ( < > 🔒 Bidding closed, committing to L1... tx:{ txLink ( sig2 ) } </ > )
279286
280287 // Poll for L1 commit
281288 for ( let i = 0 ; i < 24 ; i ++ ) {
@@ -313,7 +320,7 @@ export default function Board() {
313320 . accounts ( { job : job . pubkey , claimer : erBurner . publicKey , systemProgram : SystemProgram . programId } )
314321 . transaction ( )
315322 const sig = await sendL1WithBurner ( tx )
316- log ( ` 💎 Stake locked! tx:${ sig . slice ( 0 , 12 ) } ...` )
323+ log ( < > 💎 Stake locked! tx:{ txLink ( sig ) } </ > )
317324 await fetchJobs ( )
318325 } catch ( e ) {
319326 log ( `❌ Lock stake failed: ${ ( e as Error ) . message . slice ( 0 , 150 ) } ` )
@@ -331,7 +338,7 @@ export default function Board() {
331338 . accounts ( { job : job . pubkey , submitter : erBurner . publicKey } )
332339 . transaction ( )
333340 const sig = await sendL1WithBurner ( tx )
334- log ( ` 📝 Proof submitted! tx:${ sig . slice ( 0 , 12 ) } ...` )
341+ log ( < > 📝 Proof submitted! tx:{ txLink ( sig ) } </ > )
335342 await fetchJobs ( )
336343 } catch ( e ) {
337344 log ( `❌ Proof failed: ${ ( e as Error ) . message . slice ( 0 , 150 ) } ` )
@@ -355,7 +362,7 @@ export default function Board() {
355362 } )
356363 . transaction ( )
357364 const sig = await sendTx ( connection , tx )
358- log ( ` $ {verdict === 1 ? '✅' : '❌' } Job $ {action } ! SOL transferred. tx:${ sig . slice ( 0 , 12 ) } ...` )
365+ log ( < > { verdict === 1 ? '✅' : '❌' } Job { action } ! SOL transferred. tx:{ txLink ( sig ) } </ > )
359366 await fetchJobs ( )
360367 } catch ( e ) {
361368 log ( `❌ Settle failed: ${ ( e as Error ) . message . slice ( 0 , 150 ) } ` )
@@ -388,53 +395,26 @@ export default function Board() {
388395 </ header >
389396
390397 < div className = "board-actions-bar" >
391- < div className = "post-job-group" >
392- < button className = "board-btn board-btn-post" onClick = { postJob } disabled = { ! connected || ! ! acting } >
393- { acting === 'new' ? '⏳ Posting...' : `➕ Post Job` }
394- </ button >
395- < div className = "reward-input-wrap" >
396- < input
397- type = "number"
398- className = "reward-input"
399- value = { rewardSol }
400- onChange = { e => setRewardSol ( e . target . value ) }
401- min = "0.01"
402- step = "0.05"
403- disabled = { ! ! acting }
404- />
405- < span className = "reward-suffix" > SOL</ span >
406- </ div >
407- </ div >
408- < input
409- type = "text"
410- className = "desc-input desc-title"
411- placeholder = "Task title"
412- value = { jobTitle }
413- onChange = { e => setJobTitle ( e . target . value ) }
414- disabled = { ! ! acting }
415- />
416- < input
417- type = "text"
418- className = "desc-input"
419- placeholder = "Describe the task..."
420- value = { jobDesc }
421- onChange = { e => setJobDesc ( e . target . value ) }
422- disabled = { ! ! acting }
423- />
424398 < button className = "board-btn board-btn-refresh" onClick = { fetchJobs } disabled = { loading } >
425399 { loading ? '⏳ Loading...' : '🔄 Refresh' }
426400 </ button >
427- < span className = "board-job-count" > { jobs . length } jobs on-chain</ span >
401+ < div className = "board-filter" >
402+ < button className = { `filter-btn ${ ! activeOnly ? 'active' : '' } ` } onClick = { ( ) => setActiveOnly ( false ) } > All</ button >
403+ < button className = { `filter-btn ${ activeOnly ? 'active' : '' } ` } onClick = { ( ) => setActiveOnly ( true ) } > Active</ button >
404+ </ div >
405+ < span className = "board-job-count" >
406+ { activeOnly ? jobs . filter ( j => j . status < 4 ) . length : jobs . length } jobs{ activeOnly ? ' active' : ' on-chain' }
407+ </ span >
428408 </ div >
429409
430410 { /* Job Grid */ }
431411 < section className = "board-grid" >
432412 { jobs . length === 0 && ! loading && (
433413 < div className = "board-empty" >
434- { connected ? 'No jobs found. Post the first one!' : 'Connect wallet to browse jobs' }
414+ { connected ? 'No jobs found. Post the first one below !' : 'Connect wallet to browse jobs' }
435415 </ div >
436416 ) }
437- { jobs . map ( job => {
417+ { jobs . filter ( j => ! activeOnly || j . status < 4 ) . map ( job => {
438418 const statusInfo = STATUS_LABELS [ job . status ] || { label : `Status ${ job . status } ` , color : '#888' , icon : '❓' }
439419 const isMyJob = isPoster ( job )
440420 const isActing = acting === job . pubkey . toBase58 ( )
@@ -546,6 +526,48 @@ export default function Board() {
546526 } ) }
547527 </ section >
548528
529+ { /* Post New Job */ }
530+ { connected && (
531+ < section className = "board-post-card glass" >
532+ < h3 > ➕ Post a New Task</ h3 >
533+ < div className = "post-form" >
534+ < input
535+ type = "text"
536+ className = "post-input"
537+ placeholder = "Task title"
538+ value = { jobTitle }
539+ onChange = { e => setJobTitle ( e . target . value ) }
540+ disabled = { ! ! acting }
541+ />
542+ < input
543+ type = "text"
544+ className = "post-input"
545+ placeholder = "Describe what needs to be done..."
546+ value = { jobDesc }
547+ onChange = { e => setJobDesc ( e . target . value ) }
548+ disabled = { ! ! acting }
549+ />
550+ < div className = "post-row" >
551+ < div className = "reward-input-wrap" >
552+ < input
553+ type = "number"
554+ className = "reward-input"
555+ value = { rewardSol }
556+ onChange = { e => setRewardSol ( e . target . value ) }
557+ min = "0.01"
558+ step = "0.05"
559+ disabled = { ! ! acting }
560+ />
561+ < span className = "reward-suffix" > SOL</ span >
562+ </ div >
563+ < button className = "board-btn board-btn-post" onClick = { postJob } disabled = { ! connected || ! ! acting } >
564+ { acting === 'new' ? '⏳ Posting...' : '🚀 Post Task' }
565+ </ button >
566+ </ div >
567+ </ div >
568+ </ section >
569+ ) }
570+
549571 { /* Action Log */ }
550572 { actionLog . length > 0 && (
551573 < section className = "board-log glass" >
0 commit comments