Skip to content

Commit a94c997

Browse files
committed
imp
1 parent 8ad29eb commit a94c997

File tree

6 files changed

+963
-263
lines changed

6 files changed

+963
-263
lines changed

src/app/balance/page.tsx

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
"use client";
22

3-
import { useState, Suspense } from "react";
3+
import { useState, useEffect, Suspense } from "react";
44
import {
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";
910
import { useAccount, useReadContract, useReadContracts } from "wagmi";
1011
import { useSearchParams } from "next/navigation";
1112
import { zeroAddress } from "viem";
1213
import { CONTRACTS, REWARDS_PROGRAM_ABI, MemberRoleLabels, MemberTypeLabels } from "@/config/contracts";
1314
import { 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";
1516
import { OnChainDisclaimer } from "@/components/common/OnChainDisclaimer";
1617
import { QRCodeDisplay } from "@/components/common/QRCodeDisplay";
1718
import { 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

69133
function OwnerActions({ memberWallet }: { memberWallet: string }) {
@@ -180,6 +244,8 @@ function OwnerActions({ memberWallet }: { memberWallet: string }) {
180244
/* -- Main balance view -- */
181245

182246
function 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 &quot;{searchID}&quot; exists but has no linked wallet (walletless member).</Alert>
340+
<>
341+
<Alert severity="info" sx={{ mb: 2 }}>Member &quot;{searchID}&quot; 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}&quot;</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

Comments
 (0)