@@ -39,9 +39,18 @@ function generateEditCode(): `0x${string}` {
3939
4040/* -- Program List Row -- */
4141
42- function ProgramRow ( { programId } : { programId : number } ) {
42+ function ProgramRow ( { programId, filterMine , wallet } : { programId : number ; filterMine ?: boolean ; wallet ?: `0x${ string } ` } ) {
4343 const { data : program } = useProgram ( programId ) ;
44+ const { data : member } = useReadContract ( {
45+ address : CONTRACTS . rewardsProgram ,
46+ abi : REWARDS_PROGRAM_ABI ,
47+ functionName : "getMember" ,
48+ args : wallet ? [ programId , wallet ] : undefined ,
49+ query : { enabled : ! ! filterMine && ! ! wallet } ,
50+ } ) ;
51+
4452 if ( ! program ) return null ;
53+ if ( filterMine && ( ! member || ! member . active ) ) return null ;
4554
4655 return (
4756 < TableRow hover >
@@ -273,6 +282,7 @@ function ProgramDetail({ programId }: { programId: number }) {
273282 const [ displayEditCode , setDisplayEditCode ] = useState ( "" ) ;
274283 const [ displayMemberId , setDisplayMemberId ] = useState ( "" ) ;
275284 const [ copied , setCopied ] = useState ( false ) ;
285+ const [ copiedUrl , setCopiedUrl ] = useState ( false ) ;
276286
277287 const paWalletValid = ! paWallet || isValidAddress ( paWallet ) ;
278288 const mWalletValid = ! mWallet || isValidAddress ( mWallet ) ;
@@ -358,12 +368,22 @@ function ProgramDetail({ programId }: { programId: number }) {
358368 addMember ( programId , wallet , mMemberId , mRole , hash , mMemberType ) ;
359369 } ;
360370
371+ const claimUrl = displayEditCode && displayMemberId
372+ ? `${ typeof window !== "undefined" ? window . location . origin : "" } /balance?member=${ encodeURIComponent ( displayMemberId ) } &claim=${ programId } &code=${ encodeURIComponent ( displayEditCode ) } `
373+ : "" ;
374+
361375 const handleCopyEditCode = ( ) => {
362376 navigator . clipboard . writeText ( displayEditCode ) ;
363377 setCopied ( true ) ;
364378 setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
365379 } ;
366380
381+ const handleCopyUrl = ( ) => {
382+ navigator . clipboard . writeText ( claimUrl ) ;
383+ setCopiedUrl ( true ) ;
384+ setTimeout ( ( ) => setCopiedUrl ( false ) , 2000 ) ;
385+ } ;
386+
367387 if ( ! program ) return < Typography > Loading...</ Typography > ;
368388
369389 return (
@@ -611,16 +631,28 @@ function ProgramDetail({ programId }: { programId: number }) {
611631 < Alert severity = "warning" sx = { { mb : 2 } } >
612632 Save this edit code now. It cannot be retrieved later. Share it with "{ displayMemberId } " so they can claim their membership.
613633 </ Alert >
614- < Paper sx = { { p : 2 , bgcolor : "background.default" , display : "flex" , alignItems : "center" , gap : 1 } } >
634+ < Typography variant = "subtitle2" sx = { { mb : 0.5 } } > Edit Code</ Typography >
635+ < Paper sx = { { p : 2 , bgcolor : "background.default" , display : "flex" , alignItems : "center" , gap : 1 , mb : 2 } } >
615636 < Typography variant = "body2" sx = { { fontFamily : "monospace" , wordBreak : "break-all" , flexGrow : 1 } } >
616637 { displayEditCode }
617638 </ Typography >
618- < Tooltip title = { copied ? "Copied!" : "Copy to clipboard " } >
639+ < Tooltip title = { copied ? "Copied!" : "Copy code " } >
619640 < IconButton onClick = { handleCopyEditCode } size = "small" >
620641 < ContentCopyIcon fontSize = "small" />
621642 </ IconButton >
622643 </ Tooltip >
623644 </ Paper >
645+ < Typography variant = "subtitle2" sx = { { mb : 0.5 } } > Claim URL (share with member)</ Typography >
646+ < Paper sx = { { p : 2 , bgcolor : "background.default" , display : "flex" , alignItems : "center" , gap : 1 } } >
647+ < Typography variant = "body2" sx = { { fontFamily : "monospace" , wordBreak : "break-all" , flexGrow : 1 , fontSize : "0.75rem" } } >
648+ { claimUrl }
649+ </ Typography >
650+ < Tooltip title = { copiedUrl ? "Copied!" : "Copy URL" } >
651+ < IconButton onClick = { handleCopyUrl } size = "small" >
652+ < ContentCopyIcon fontSize = "small" />
653+ </ IconButton >
654+ </ Tooltip >
655+ </ Paper >
624656 < Typography variant = "caption" color = "text.secondary" sx = { { mt : 1 , display : "block" } } >
625657 Member ID: { displayMemberId } | Program ID: { programId }
626658 </ Typography >
@@ -640,8 +672,9 @@ function ProgramDetail({ programId }: { programId: number }) {
640672function ProgramList ( ) {
641673 const theme = useTheme ( ) ;
642674 const isMobile = useMediaQuery ( theme . breakpoints . down ( "sm" ) ) ;
675+ const { address } = useAccount ( ) ;
643676 const { isAdmin } = useUserRole ( ) ;
644- const { data : programCount } = useProgramCount ( ) ;
677+ const { data : programCount , refetch : refetchCount } = useProgramCount ( ) ;
645678 const { createProgram, isPending, isConfirming, isSuccess, error } = useCreateProgram ( ) ;
646679
647680 const [ open , setOpen ] = useState ( false ) ;
@@ -650,14 +683,28 @@ function ProgramList() {
650683 const [ description , setDescription ] = useState ( "" ) ;
651684 const [ disclaimer , setDisclaimer ] = useState ( false ) ;
652685
686+ // Filters
687+ const [ filterMode , setFilterMode ] = useState < "all" | "mine" > ( "all" ) ;
688+ const [ searchId , setSearchId ] = useState ( "" ) ;
689+
653690 useEffect ( ( ) => {
654691 if ( isSuccess ) {
692+ refetchCount ( ) ;
655693 const t = setTimeout ( ( ) => { setOpen ( false ) ; setCode ( "" ) ; setName ( "" ) ; setDescription ( "" ) ; setDisclaimer ( false ) ; } , 1500 ) ;
656694 return ( ) => clearTimeout ( t ) ;
657695 }
658- } , [ isSuccess ] ) ;
696+ } , [ isSuccess , refetchCount ] ) ;
659697
660698 const count = Number ( programCount || 0 ) ;
699+ const searchProgramId = searchId ? parseInt ( searchId ) : 0 ;
700+
701+ // Build list of program IDs to show
702+ let programIds : number [ ] ;
703+ if ( searchProgramId > 0 && searchProgramId <= count ) {
704+ programIds = [ searchProgramId ] ;
705+ } else {
706+ programIds = Array . from ( { length : count } , ( _ , i ) => i + 1 ) ;
707+ }
661708
662709 return (
663710 < Box >
@@ -670,6 +717,32 @@ function ProgramList() {
670717 ) }
671718 </ Box >
672719
720+ < Paper sx = { { p : 2 , mb : 2 } } >
721+ < Grid container spacing = { 2 } alignItems = "center" >
722+ < Grid item xs = { 12 } sm = { 4 } >
723+ < TextField label = "Search by Program ID" value = { searchId }
724+ onChange = { ( e ) => setSearchId ( e . target . value ) }
725+ type = "number" fullWidth size = "small" placeholder = "All programs"
726+ helperText = { searchProgramId > count ? `Max ID is ${ count } ` : undefined }
727+ error = { searchProgramId > count } />
728+ </ Grid >
729+ < Grid item xs = { 12 } sm = { 4 } >
730+ < FormControl fullWidth size = "small" >
731+ < InputLabel > Show</ InputLabel >
732+ < Select value = { filterMode } onChange = { ( e ) => setFilterMode ( e . target . value as "all" | "mine" ) } label = "Show" >
733+ < MenuItem value = "all" > All Programs</ MenuItem >
734+ < MenuItem value = "mine" disabled = { ! address } > My Programs</ MenuItem >
735+ </ Select >
736+ </ FormControl >
737+ </ Grid >
738+ < Grid item xs = { 12 } sm = { 4 } >
739+ < Typography variant = "body2" color = "text.secondary" >
740+ { count } program{ count !== 1 ? "s" : "" } total
741+ </ Typography >
742+ </ Grid >
743+ </ Grid >
744+ </ Paper >
745+
673746 < TableContainer component = { Paper } >
674747 < Table >
675748 < TableHead >
@@ -682,12 +755,16 @@ function ProgramList() {
682755 </ TableRow >
683756 </ TableHead >
684757 < TableBody >
685- { count === 0 ? (
758+ { programIds . length === 0 ? (
686759 < TableRow >
687760 < TableCell colSpan = { 5 } align = "center" > No programs yet.</ TableCell >
688761 </ TableRow >
689762 ) : (
690- Array . from ( { length : count } , ( _ , i ) => < ProgramRow key = { i + 1 } programId = { i + 1 } /> )
763+ programIds . map ( id => (
764+ < ProgramRow key = { id } programId = { id }
765+ filterMine = { filterMode === "mine" }
766+ wallet = { address } />
767+ ) )
691768 ) }
692769 </ TableBody >
693770 </ Table >
0 commit comments