Skip to content

Commit 9e719d3

Browse files
committed
added more features to UI and chain like edit, QR and access code
1 parent 25781b3 commit 9e719d3

14 files changed

Lines changed: 521 additions & 386 deletions

File tree

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"Bash(find /mnt/e/GitHub/rewards-program/src -type f \\\\\\(-name *.ts -o -name *.tsx \\\\\\))",
1818
"Bash(find /mnt/e/GitHub/rewards-program/src -type f \\\\\\(-name *.tsx -o -name *.ts \\\\\\))",
1919
"Bash(xargs ls:*)",
20-
"Bash(npm run:*)"
20+
"Bash(npm run:*)",
21+
"Bash(node:*)"
2122
]
2223
}
2324
}

package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"@mui/material": "^6.4.1",
1616
"@rainbow-me/rainbowkit": "^2.2.10",
1717
"@tanstack/react-query": "^5.62.0",
18+
"html5-qrcode": "^2.3.8",
1819
"next": "^15.1.6",
20+
"qrcode.react": "^4.2.0",
1921
"react": "^19.0.0",
2022
"react-dom": "^19.0.0",
2123
"viem": "^2.22.0",

src/app/balance/page.tsx

Lines changed: 42 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import { useAccount, useReadContract, useReadContracts } from "wagmi";
1010
import { useSearchParams } from "next/navigation";
1111
import { zeroAddress } from "viem";
1212
import { CONTRACTS, REWARDS_PROGRAM_ABI, MemberRoleLabels } from "@/config/contracts";
13-
import { toBytes12, fromBytes12, fromBytes8, shortenAddress, formatFula, formatDate, formatContractError } from "@/lib/utils";
14-
import { useProgramCount, useProgram, useTimeLocks, useDirectChildren, useTransferToParent, useWithdraw, useApproveToken, useAddTokens } from "@/hooks/useRewardsProgram";
13+
import { toBytes12, fromBytes12, fromBytes8, shortenAddress, formatFula, formatContractError } from "@/lib/utils";
14+
import { useProgramCount, useProgram, useTransferToParent, useWithdraw, useApproveToken, useAddTokens } from "@/hooks/useRewardsProgram";
1515
import { OnChainDisclaimer } from "@/components/common/OnChainDisclaimer";
16+
import { QRCodeDisplay } from "@/components/common/QRCodeDisplay";
17+
import { QRScannerButton } from "@/components/common/QRScannerButton";
1618

1719
/* -- Per-program membership row -- */
1820

@@ -32,12 +34,12 @@ function MemberProgramRow({ memberID, programId }: { memberID: string; programId
3234
const { data: balance } = useReadContract({
3335
address: CONTRACTS.rewardsProgram,
3436
abi: REWARDS_PROGRAM_ABI,
35-
functionName: "getEffectiveBalance",
37+
functionName: "getBalance",
3638
args: member?.wallet && member.wallet !== zeroAddress ? [programId, member.wallet as `0x${string}`] : undefined,
3739
query: { enabled: !!member?.wallet && member.wallet !== zeroAddress },
3840
});
3941

40-
if (!member || !member.wallet || member.wallet === zeroAddress) return null;
42+
if (!member || !member.active) return null;
4143

4244
return (
4345
<TableRow>
@@ -52,6 +54,9 @@ function MemberProgramRow({ memberID, programId }: { memberID: string; programId
5254
<TableCell sx={{ color: "error.main" }}>{balance ? formatFula(balance[1]) : "-"}</TableCell>
5355
<TableCell sx={{ color: "warning.main" }}>{balance ? formatFula(balance[2]) : "-"}</TableCell>
5456
<TableCell>{member.active ? "Active" : "Inactive"}</TableCell>
57+
<TableCell>
58+
<QRCodeDisplay programId={programId} memberID={memberID} size={64} />
59+
</TableCell>
5560
</TableRow>
5661
);
5762
}
@@ -146,95 +151,6 @@ function OwnerActions({ memberWallet }: { memberWallet: string }) {
146151
);
147152
}
148153

149-
/* -- Time Locks detail -- */
150-
151-
function TimeLockDetails({ programId, wallet }: { programId: number; wallet: `0x${string}` }) {
152-
const { data: timeLocks } = useTimeLocks(programId, wallet);
153-
154-
if (!timeLocks || timeLocks.length === 0) return null;
155-
156-
return (
157-
<Box sx={{ mt: 1 }}>
158-
{timeLocks.map((lock, i) => (
159-
<Typography key={i} variant="body2" color="text.secondary">
160-
{formatFula(BigInt(lock.amount))} FULA - unlocks {formatDate(Number(lock.unlockTime))}
161-
</Typography>
162-
))}
163-
</Box>
164-
);
165-
}
166-
167-
/* -- Sub-member row -- */
168-
169-
function SubMemberRow({ programId, wallet }: { programId: number; wallet: `0x${string}` }) {
170-
const { data: member } = useReadContract({
171-
address: CONTRACTS.rewardsProgram,
172-
abi: REWARDS_PROGRAM_ABI,
173-
functionName: "getMember",
174-
args: [programId, wallet],
175-
});
176-
const { data: balance } = useReadContract({
177-
address: CONTRACTS.rewardsProgram,
178-
abi: REWARDS_PROGRAM_ABI,
179-
functionName: "getEffectiveBalance",
180-
args: [programId, wallet],
181-
});
182-
183-
if (!member) return null;
184-
185-
return (
186-
<TableRow>
187-
<TableCell>{fromBytes12(member.memberID as `0x${string}`)}</TableCell>
188-
<TableCell sx={{ fontFamily: "monospace", fontSize: "0.85rem" }}>{shortenAddress(member.wallet)}</TableCell>
189-
<TableCell>
190-
<Chip label={MemberRoleLabels[Number(member.role)] || "Unknown"} size="small"
191-
color={Number(member.role) === 3 ? "primary" : Number(member.role) === 2 ? "secondary" : "default"} />
192-
</TableCell>
193-
<TableCell sx={{ color: "success.main" }}>{balance ? formatFula(balance[0]) : "-"}</TableCell>
194-
<TableCell sx={{ color: "error.main" }}>{balance ? formatFula(balance[1]) : "-"}</TableCell>
195-
<TableCell sx={{ color: "warning.main" }}>{balance ? formatFula(balance[2]) : "-"}</TableCell>
196-
<TableCell>{member.active ? "Active" : "Inactive"}</TableCell>
197-
</TableRow>
198-
);
199-
}
200-
201-
/* -- Sub-members for a program -- */
202-
203-
function SubMembersSection({ programId, wallet }: { programId: number; wallet: `0x${string}` }) {
204-
const { data: children } = useDirectChildren(programId, wallet);
205-
const { data: program } = useProgram(programId);
206-
207-
if (!children || children.length === 0) return null;
208-
209-
return (
210-
<Box sx={{ mt: 2 }}>
211-
<Typography variant="subtitle2" gutterBottom>
212-
Sub-members in {program ? program.name : `Program #${programId}`}
213-
</Typography>
214-
<TableContainer>
215-
<Table size="small">
216-
<TableHead>
217-
<TableRow>
218-
<TableCell>Member ID</TableCell>
219-
<TableCell>Wallet</TableCell>
220-
<TableCell>Role</TableCell>
221-
<TableCell>Withdrawable</TableCell>
222-
<TableCell>Locked</TableCell>
223-
<TableCell>Time-Locked</TableCell>
224-
<TableCell>Status</TableCell>
225-
</TableRow>
226-
</TableHead>
227-
<TableBody>
228-
{children.map((child) => (
229-
<SubMemberRow key={child} programId={programId} wallet={child as `0x${string}`} />
230-
))}
231-
</TableBody>
232-
</Table>
233-
</TableContainer>
234-
</Box>
235-
);
236-
}
237-
238154
/* -- Main balance view -- */
239155

240156
function BalanceContent() {
@@ -274,10 +190,29 @@ function BalanceContent() {
274190
}
275191
}
276192

193+
// Check if member exists in any program (even walletless)
194+
let memberExists = false;
195+
if (multicallResults) {
196+
for (const result of multicallResults) {
197+
if (result.status === "success" && result.result) {
198+
const member = result.result as { active: boolean };
199+
if (member.active) {
200+
memberExists = true;
201+
break;
202+
}
203+
}
204+
}
205+
}
206+
277207
const handleSearch = () => {
278208
setSearchID(memberID);
279209
};
280210

211+
const handleQRScan = ({ programId: _p, memberID: m }: { programId: number; memberID: string }) => {
212+
setMemberID(m);
213+
setSearchID(m);
214+
};
215+
281216
return (
282217
<Box>
283218
<Typography variant="h4" gutterBottom>Member Balance</Typography>
@@ -292,6 +227,10 @@ function BalanceContent() {
292227
inputProps={{ maxLength: 12 }}
293228
placeholder="Enter member ID..."
294229
/>
230+
<QRScannerButton
231+
tooltip="Scan member QR to search"
232+
onScan={handleQRScan}
233+
/>
295234
<Button variant="contained" onClick={handleSearch} disabled={!memberID}>
296235
Look Up
297236
</Button>
@@ -308,16 +247,20 @@ function BalanceContent() {
308247
</Paper>
309248
)}
310249

311-
{!memberWallet && (
250+
{!memberExists && (
312251
<Alert severity="warning" sx={{ mb: 3 }}>No member found with ID &quot;{searchID}&quot; in any program.</Alert>
313252
)}
314253

315-
{memberWallet && (
254+
{memberExists && !memberWallet && (
255+
<Alert severity="info" sx={{ mb: 3 }}>Member &quot;{searchID}&quot; exists but has no linked wallet (walletless member).</Alert>
256+
)}
257+
258+
{memberExists && (
316259
<>
317260
<Paper sx={{ p: 2 }}>
318261
<Typography variant="h6" gutterBottom>Programs & Balances for &quot;{searchID}&quot;</Typography>
319262
<Typography variant="caption" color="text.secondary" sx={{ mb: 2, display: "block" }}>
320-
Balance columns: Withdrawable / Permanently Locked / Time-Locked (FULA)
263+
Balance columns: Available / Permanently Locked / Time-Locked (FULA)
321264
</Typography>
322265
<TableContainer>
323266
<Table size="small">
@@ -327,10 +270,11 @@ function BalanceContent() {
327270
<TableCell>Code</TableCell>
328271
<TableCell>Role</TableCell>
329272
<TableCell>Parent</TableCell>
330-
<TableCell>Withdrawable</TableCell>
273+
<TableCell>Available</TableCell>
331274
<TableCell>Locked</TableCell>
332275
<TableCell>Time-Locked</TableCell>
333276
<TableCell>Status</TableCell>
277+
<TableCell>QR</TableCell>
334278
</TableRow>
335279
</TableHead>
336280
<TableBody>
@@ -342,15 +286,7 @@ function BalanceContent() {
342286
</TableContainer>
343287
</Paper>
344288

345-
{/* Sub-members per program */}
346-
<Paper sx={{ p: 2, mt: 3 }}>
347-
<Typography variant="h6" gutterBottom>Sub-Members</Typography>
348-
{Array.from({ length: count }, (_, i) => (
349-
<SubMembersSection key={i + 1} programId={i + 1} wallet={memberWallet as `0x${string}`} />
350-
))}
351-
</Paper>
352-
353-
<OwnerActions memberWallet={memberWallet} />
289+
{memberWallet && <OwnerActions memberWallet={memberWallet} />}
354290
</>
355291
)}
356292
</>

0 commit comments

Comments
 (0)