11"use client" ;
22
3- import { useState , Suspense } from "react" ;
3+ import { useState , useEffect , Suspense } from "react" ;
44import {
55 Typography , Box , Paper , Grid , Chip , Table , TableBody , TableCell ,
66 TableContainer , TableHead , TableRow , TextField , Button , Alert ,
77 CircularProgress , Select , MenuItem , FormControl , InputLabel ,
8+ useMediaQuery , useTheme ,
89} from "@mui/material" ;
910import { useAccount , useReadContract , useReadContracts } from "wagmi" ;
1011import { useSearchParams } from "next/navigation" ;
1112import { zeroAddress } from "viem" ;
1213import { CONTRACTS , REWARDS_PROGRAM_ABI , MemberRoleLabels , MemberTypeLabels } from "@/config/contracts" ;
1314import { toBytes12 , fromBytes12 , fromBytes8 , shortenAddress , formatFula , formatContractError , fromBytes16 } from "@/lib/utils" ;
14- import { useProgramCount , useProgram , useTransferToParent , useWithdraw , useApproveToken , useAddTokens , useRewardTypes , useTransferLimit } from "@/hooks/useRewardsProgram" ;
15+ import { useProgramCount , useProgram , useTransferToParent , useWithdraw , useApproveToken , useAddTokens , useRewardTypes , useTransferLimit , useClaimMember } from "@/hooks/useRewardsProgram" ;
1516import { OnChainDisclaimer } from "@/components/common/OnChainDisclaimer" ;
1617import { QRCodeDisplay } from "@/components/common/QRCodeDisplay" ;
1718import { QRScannerButton } from "@/components/common/QRScannerButton" ;
@@ -44,26 +45,89 @@ function MemberProgramRow({ memberID, programId }: { memberID: string; programId
4445 return (
4546 < TableRow >
4647 < TableCell > { program ? program . name : `Program #${ programId } ` } </ TableCell >
47- < TableCell > { program ? fromBytes8 ( program . code as `0x${string } `) : "-" } </ TableCell >
48+ < TableCell sx = { { display : { xs : "none" , sm : "table-cell" } } } > { program ? fromBytes8 ( program . code as `0x${string } `) : "-" } </ TableCell >
4849 < TableCell >
4950 < Chip label = { MemberRoleLabels [ Number ( member . role ) ] || "Unknown" } size = "small"
5051 color = { Number ( member . role ) === 3 ? "primary" : Number ( member . role ) === 2 ? "secondary" : "default" } />
5152 </ TableCell >
5253 < TableCell >
5354 < Chip label = { MemberTypeLabels [ Number ( member . memberType ) ] || "Free" } size = "small" variant = "outlined" />
5455 </ TableCell >
55- < TableCell > { shortenAddress ( member . parent ) } </ TableCell >
56+ < TableCell sx = { { display : { xs : "none" , md : "table-cell" } } } > { shortenAddress ( member . parent ) } </ TableCell >
5657 < TableCell sx = { { color : "success.main" } } > { balance ? formatFula ( balance [ 0 ] ) : "-" } </ TableCell >
57- < TableCell sx = { { color : "error.main" } } > { balance ? formatFula ( balance [ 1 ] ) : "-" } </ TableCell >
58- < TableCell sx = { { color : "warning.main" } } > { balance ? formatFula ( balance [ 2 ] ) : "-" } </ TableCell >
59- < TableCell > { member . active ? "Active" : "Inactive" } </ TableCell >
58+ < TableCell sx = { { color : "error.main" , display : { xs : "none" , sm : "table-cell" } } } > { balance ? formatFula ( balance [ 1 ] ) : "-" } </ TableCell >
59+ < TableCell sx = { { color : "warning.main" , display : { xs : "none" , sm : "table-cell" } } } > { balance ? formatFula ( balance [ 2 ] ) : "-" } </ TableCell >
60+ < TableCell sx = { { display : { xs : "none" , md : "table-cell" } } } > { member . active ? "Active" : "Inactive" } </ TableCell >
6061 < TableCell >
6162 < QRCodeDisplay programId = { programId } memberID = { memberID } size = { 64 } />
6263 </ TableCell >
6364 </ TableRow >
6465 ) ;
6566}
6667
68+ /* -- Claim Member Section -- */
69+
70+ function ClaimMemberSection ( { memberID, programCount } : { memberID : string ; programCount : number } ) {
71+ const { address, isConnected } = useAccount ( ) ;
72+ const { claimMember, isPending, isConfirming, isSuccess, error } = useClaimMember ( ) ;
73+ const [ editCode , setEditCode ] = useState ( "" ) ;
74+ const [ claimProgramId , setClaimProgramId ] = useState ( "1" ) ;
75+ const [ disclaimer , setDisclaimer ] = useState ( false ) ;
76+
77+ useEffect ( ( ) => {
78+ if ( isSuccess ) {
79+ setEditCode ( "" ) ;
80+ setDisclaimer ( false ) ;
81+ }
82+ } , [ isSuccess ] ) ;
83+
84+ if ( ! isConnected ) {
85+ return (
86+ < Alert severity = "info" sx = { { mt : 2 } } >
87+ Connect your wallet to claim this member and link your wallet address.
88+ </ Alert >
89+ ) ;
90+ }
91+
92+ const handleClaim = ( ) => {
93+ const pid = parseInt ( claimProgramId ) ;
94+ if ( ! pid || ! editCode ) return ;
95+ const codeHex = editCode . startsWith ( "0x" ) ? editCode as `0x${string } ` : `0x${ editCode } ` as `0x${string } `;
96+ claimMember ( pid , memberID , codeHex ) ;
97+ } ;
98+
99+ return (
100+ < Paper sx = { { p : 3 , mt : 2 } } >
101+ < Typography variant = "h6" gutterBottom > Claim this Member</ Typography >
102+ < Typography variant = "body2" color = "text.secondary" sx = { { mb : 2 } } >
103+ This is a walletless member. Enter the edit code you received to link your connected wallet ({ address ? shortenAddress ( address ) : "" } ).
104+ </ Typography >
105+ < Grid container spacing = { 2 } >
106+ < Grid item xs = { 12 } sm = { 3 } >
107+ < TextField label = "Program ID" value = { claimProgramId }
108+ onChange = { ( e ) => setClaimProgramId ( e . target . value ) }
109+ type = "number" fullWidth size = "small" />
110+ </ Grid >
111+ < Grid item xs = { 12 } sm = { 9 } >
112+ < TextField label = "Edit Code (0x...)" value = { editCode }
113+ onChange = { ( e ) => setEditCode ( e . target . value ) }
114+ fullWidth size = "small" placeholder = "0x..."
115+ sx = { { fontFamily : "monospace" } } />
116+ </ Grid >
117+ </ Grid >
118+ < OnChainDisclaimer accepted = { disclaimer } onChange = { setDisclaimer } />
119+ < Box sx = { { mt : 2 , display : "flex" , gap : 1 , alignItems : "center" } } >
120+ < Button variant = "contained" onClick = { handleClaim }
121+ disabled = { isPending || isConfirming || ! editCode || ! claimProgramId || ! disclaimer } >
122+ { isPending || isConfirming ? < CircularProgress size = { 20 } /> : "Claim Member" }
123+ </ Button >
124+ </ Box >
125+ { error && < Alert severity = "error" sx = { { mt : 2 } } > { formatContractError ( error ) } </ Alert > }
126+ { isSuccess && < Alert severity = "success" sx = { { mt : 2 } } > Member claimed! Your wallet is now linked. Refresh the page to see updated status.</ Alert > }
127+ </ Paper >
128+ ) ;
129+ }
130+
67131/* -- Actions panel (only for wallet owner) -- */
68132
69133function OwnerActions ( { memberWallet } : { memberWallet : string } ) {
@@ -180,6 +244,8 @@ function OwnerActions({ memberWallet }: { memberWallet: string }) {
180244/* -- Main balance view -- */
181245
182246function BalanceContent ( ) {
247+ const theme = useTheme ( ) ;
248+ const isMobile = useMediaQuery ( theme . breakpoints . down ( "sm" ) ) ;
183249 const searchParams = useSearchParams ( ) ;
184250 const memberParam = searchParams . get ( "member" ) || "" ;
185251 const [ memberID , setMemberID ] = useState ( memberParam ) ;
@@ -230,9 +296,7 @@ function BalanceContent() {
230296 }
231297 }
232298
233- const handleSearch = ( ) => {
234- setSearchID ( memberID ) ;
235- } ;
299+ const handleSearch = ( ) => setSearchID ( memberID ) ;
236300
237301 const handleQRScan = ( { programId : _p , memberID : m } : { programId : number ; memberID : string } ) => {
238302 setMemberID ( m ) ;
@@ -244,22 +308,17 @@ function BalanceContent() {
244308 < Typography variant = "h4" gutterBottom > Member Balance</ Typography >
245309
246310 < Paper sx = { { p : 3 , mb : 3 } } >
247- < Box sx = { { display : "flex" , gap : 2 , alignItems : "flex-end" } } >
311+ < Box sx = { { display : "flex" , gap : 2 , alignItems : "flex-end" , flexWrap : "wrap" } } >
248312 < TextField
249313 label = "Member ID (Reward ID)"
250314 value = { memberID }
251315 onChange = { ( e ) => setMemberID ( e . target . value ) }
252- sx = { { flexGrow : 1 } }
316+ sx = { { flexGrow : 1 , minWidth : 150 } }
253317 inputProps = { { maxLength : 12 } }
254318 placeholder = "Enter member ID..."
255319 />
256- < QRScannerButton
257- tooltip = "Scan member QR to search"
258- onScan = { handleQRScan }
259- />
260- < Button variant = "contained" onClick = { handleSearch } disabled = { ! memberID } >
261- Look Up
262- </ Button >
320+ < QRScannerButton tooltip = "Scan member QR to search" onScan = { handleQRScan } />
321+ < Button variant = "contained" onClick = { handleSearch } disabled = { ! memberID } > Look Up</ Button >
263322 </ Box >
264323 </ Paper >
265324
@@ -268,7 +327,7 @@ function BalanceContent() {
268327 { memberWallet && (
269328 < Paper sx = { { p : 2 , mb : 3 } } >
270329 < Typography variant = "body2" color = "text.secondary" >
271- Wallet: < Typography component = "span" sx = { { fontFamily : "monospace" } } > { memberWallet } </ Typography >
330+ Wallet: < Typography component = "span" sx = { { fontFamily : "monospace" } } > { isMobile ? shortenAddress ( memberWallet ) : memberWallet } </ Typography >
272331 </ Typography >
273332 </ Paper >
274333 ) }
@@ -278,12 +337,15 @@ function BalanceContent() {
278337 ) }
279338
280339 { memberExists && ! memberWallet && (
281- < Alert severity = "info" sx = { { mb : 3 } } > Member "{ searchID } " exists but has no linked wallet (walletless member).</ Alert >
340+ < >
341+ < Alert severity = "info" sx = { { mb : 2 } } > Member "{ searchID } " exists but has no linked wallet (walletless member).</ Alert >
342+ < ClaimMemberSection memberID = { searchID } programCount = { count } />
343+ </ >
282344 ) }
283345
284346 { memberExists && (
285347 < >
286- < Paper sx = { { p : 2 } } >
348+ < Paper sx = { { p : 2 , mt : 2 } } >
287349 < Typography variant = "h6" gutterBottom > Programs & Balances for & quot ; { searchID } "</ Typography >
288350 < Typography variant = "caption" color = "text.secondary" sx = { { mb : 2 , display : "block" } } >
289351 Balance columns: Available / Permanently Locked / Time-Locked (FULA)
@@ -293,14 +355,14 @@ function BalanceContent() {
293355 < TableHead >
294356 < TableRow >
295357 < TableCell > Program</ TableCell >
296- < TableCell > Code</ TableCell >
358+ < TableCell sx = { { display : { xs : "none" , sm : "table-cell" } } } > Code</ TableCell >
297359 < TableCell > Role</ TableCell >
298360 < TableCell > Type</ TableCell >
299- < TableCell > Parent</ TableCell >
361+ < TableCell sx = { { display : { xs : "none" , md : "table-cell" } } } > Parent</ TableCell >
300362 < TableCell > Available</ TableCell >
301- < TableCell > Locked</ TableCell >
302- < TableCell > Time-Locked</ TableCell >
303- < TableCell > Status</ TableCell >
363+ < TableCell sx = { { display : { xs : "none" , sm : "table-cell" } } } > Locked</ TableCell >
364+ < TableCell sx = { { display : { xs : "none" , sm : "table-cell" } } } > Time-Locked</ TableCell >
365+ < TableCell sx = { { display : { xs : "none" , md : "table-cell" } } } > Status</ TableCell >
304366 < TableCell > QR</ TableCell >
305367 </ TableRow >
306368 </ TableHead >
0 commit comments