Skip to content

Commit 5a63f01

Browse files
committed
resolved minor bugs
1 parent 3eb322e commit 5a63f01

7 files changed

Lines changed: 303 additions & 72 deletions

File tree

.claude/settings.local.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,24 @@
1818
"Bash(find /mnt/e/GitHub/rewards-program/src -type f \\\\\\(-name *.tsx -o -name *.ts \\\\\\))",
1919
"Bash(xargs ls:*)",
2020
"Bash(npm run:*)",
21-
"Bash(node:*)"
21+
"Bash(node:*)",
22+
"Bash(find /mnt/e/GitHub/fula-chain -type f -name *test*rewards* -o -name *rewards*test*)",
23+
"Bash(grep:*)",
24+
"Bash(python3:*)",
25+
"Bash(ls /mnt/e/GitHub/rewards-program/README* /mnt/e/GitHub/rewards-program/readme*)",
26+
"Bash(ls /mnt/e/GitHub/rewards-program/*.md)",
27+
"Bash(find /mnt/e/GitHub/fula-chain/test -name *.js -o -name *.ts)",
28+
"Bash(cast keccak:*)",
29+
"Bash(npx viem:*)",
30+
"Bash(find /mnt/e/GitHub/rewards-program -name *.sol -type f)",
31+
"Bash(find /mnt/e/GitHub/rewards-program -type f \\\\\\(-name *.sol -o -name *Rewards* -o -name *Member* \\\\\\))",
32+
"Bash(find /mnt/e/GitHub/rewards-program -type f -name *.md -o -name *contract* -o -name *abi*)",
33+
"Bash(find /mnt/e/GitHub -name *.sol -type f)",
34+
"Bash(find /mnt/e/GitHub/fula-chain -name *.sol -type f)",
35+
"Bash(find /mnt/e/GitHub/fula-chain -name *.sol -path */test* -o -name *Test* -o -name *test*)",
36+
"Bash(find /mnt/e/GitHub -name *.test.ts -o -name *.spec.ts)",
37+
"Bash(find /mnt/e/GitHub/fula-chain -name *.ts -o -name *.js)",
38+
"Bash(find /mnt/e/GitHub/fula-chain -type f \\\\\\(-name *.test.ts -o -name *.spec.ts -o -name *test*.ts \\\\\\))"
2239
]
2340
}
2441
}

src/app/balance/page.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useSearchParams } from "next/navigation";
1212
import { zeroAddress } from "viem";
1313
import { CONTRACTS, REWARDS_PROGRAM_ABI, MemberRoleLabels, MemberTypeLabels } from "@/config/contracts";
1414
import { toBytes12, fromBytes12, fromBytes8, shortenAddress, formatFula, formatContractError, fromBytes16 } from "@/lib/utils";
15-
import { useProgramCount, useProgram, useTransferToParent, useWithdraw, useApproveToken, useAddTokens, useRewardTypes, useTransferLimit, useClaimMember } from "@/hooks/useRewardsProgram";
15+
import { useProgramCount, useProgram, useTransferToParent, useWithdraw, useDepositTokens, useRewardTypes, useTransferLimit, useClaimMember } from "@/hooks/useRewardsProgram";
1616
import { OnChainDisclaimer } from "@/components/common/OnChainDisclaimer";
1717
import { QRCodeDisplay } from "@/components/common/QRCodeDisplay";
1818
import { QRScannerButton } from "@/components/common/QRScannerButton";
@@ -153,8 +153,7 @@ function OwnerActions({ memberWallet }: { memberWallet: string }) {
153153
const [depositAmount, setDepositAmount] = useState("");
154154
const [depositRewardType, setDepositRewardType] = useState(0);
155155
const [depositNote, setDepositNote] = useState("");
156-
const { approve, isPending: isApproving, isConfirming: isAppConf, isSuccess: approveSuccess } = useApproveToken();
157-
const { addTokens, isPending: isDepositing, isConfirming: isDepConf, isSuccess: depositSuccess, error: depositError } = useAddTokens();
156+
const { deposit: depositTokens, isApproving, isDepositing, isPending: isDepPending, isSuccess: depositSuccess, error: depositError } = useDepositTokens();
158157
const { data: rewardTypesData } = useRewardTypes();
159158

160159
const [disclaimer, setDisclaimer] = useState(false);
@@ -188,16 +187,13 @@ function OwnerActions({ memberWallet }: { memberWallet: string }) {
188187
onChange={(e) => setDepositNote(e.target.value.slice(0, 128))}
189188
fullWidth size="small" sx={{ mt: 1 }} inputProps={{ maxLength: 128 }} />
190189
<Box sx={{ display: "flex", gap: 1, mt: 1 }}>
191-
<Button size="small" variant="outlined" onClick={() => approve(depositAmount)}
192-
disabled={isApproving || isAppConf || !depositAmount || !disclaimer}>
193-
{isApproving || isAppConf ? <CircularProgress size={16} /> : "Approve"}
194-
</Button>
195-
<Button size="small" variant="contained" onClick={() => addTokens(pid, depositAmount, depositRewardType, depositNote)}
196-
disabled={isDepositing || isDepConf || !depositAmount || !disclaimer}>
197-
{isDepositing || isDepConf ? <CircularProgress size={16} /> : "Deposit"}
190+
<Button size="small" variant="contained" onClick={() => depositTokens(pid, depositAmount, depositRewardType, depositNote)}
191+
disabled={isDepPending || !depositAmount || !disclaimer}>
192+
{isApproving ? <><CircularProgress size={16} sx={{ mr: 0.5 }} /> Approving...</>
193+
: isDepositing ? <><CircularProgress size={16} sx={{ mr: 0.5 }} /> Depositing...</>
194+
: "Deposit"}
198195
</Button>
199196
</Box>
200-
{approveSuccess && <Alert severity="info" sx={{ mt: 1 }}>Approved. Now click Deposit.</Alert>}
201197
{depositSuccess && <Alert severity="success" sx={{ mt: 1 }}>Deposited!</Alert>}
202198
{depositError && <Alert severity="error" sx={{ mt: 1 }}>{formatContractError(depositError)}</Alert>}
203199
</Grid>

src/app/members/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export default function MembersPage() {
158158
<TextField
159159
label={searchType === "memberID" ? "Member ID (= Reward ID)" : "Program Code"}
160160
value={searchValue}
161-
onChange={(e) => { setSearchValue(e.target.value); setSearchTriggered(false); }}
161+
onChange={(e) => { setSearchValue(searchType === "programCode" ? e.target.value.toUpperCase() : e.target.value); setSearchTriggered(false); }}
162162
sx={{ flexGrow: 1, minWidth: 150 }}
163163
inputProps={{ maxLength: searchType === "memberID" ? 12 : 8 }}
164164
/>

src/app/programs/page.tsx

Lines changed: 128 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import BlockIcon from "@mui/icons-material/Block";
1414
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
1515
import DeleteIcon from "@mui/icons-material/Delete";
1616
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
17-
import { useAccount, useReadContract } from "wagmi";
17+
import { useAccount } from "wagmi";
1818
import { useSearchParams } from "next/navigation";
1919
import { keccak256 } from "viem";
2020
import Link from "next/link";
@@ -26,8 +26,10 @@ import {
2626
useUpdateProgram, useDeactivateProgram,
2727
useAddRewardType, useRemoveRewardType, useRewardTypes,
2828
useAddSubType, useRemoveSubType, useSubTypes,
29+
useDepositTokens, useTransferToSubMember, useTransferToParent, useWithdraw,
30+
useTokenBalance,
2931
} from "@/hooks/useRewardsProgram";
30-
import { CONTRACTS, REWARDS_PROGRAM_ABI, MemberRoleLabels, MemberRoleEnum, MemberTypeLabels } from "@/config/contracts";
32+
import { MemberRoleLabels, MemberRoleEnum, MemberTypeLabels } from "@/config/contracts";
3133
import { fromBytes8, fromBytes12, fromBytes16, shortenAddress, formatFula, isValidAddress, formatContractError } from "@/lib/utils";
3234
import { OnChainDisclaimer } from "@/components/common/OnChainDisclaimer";
3335
import { QRCodeDisplay } from "@/components/common/QRCodeDisplay";
@@ -41,16 +43,10 @@ function generateEditCode(): `0x${string}` {
4143

4244
function ProgramRow({ programId, filterMine, wallet, isAdmin }: { programId: number; filterMine?: boolean; wallet?: `0x${string}`; isAdmin?: boolean }) {
4345
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 && !isAdmin && !!wallet },
50-
});
46+
const { role, isActive } = useMemberRole(programId);
5147

5248
if (!program) return null;
53-
if (filterMine && !isAdmin && (!member || !member.active)) return null;
49+
if (filterMine && !isAdmin && (!isActive || role === 0)) return null;
5450

5551
return (
5652
<TableRow hover>
@@ -289,6 +285,27 @@ function ProgramDetail({ programId }: { programId: number }) {
289285
const [copied, setCopied] = useState(false);
290286
const [copiedUrl, setCopiedUrl] = useState(false);
291287

288+
// Token Operations
289+
const { data: walletBalance } = useTokenBalance(address);
290+
const { deposit: depositTokens, isApproving: isDepApproving, isDepositing: isDepDepositing, isPending: isDepPending, isSuccess: depSuccess, error: depError, reset: resetDep } = useDepositTokens();
291+
const { transfer, isPending: isTransPending, isConfirming: isTransConf, isSuccess: transSuccess, error: transError } = useTransferToSubMember();
292+
const { transferBack, isPending: isTransBackPending, isConfirming: isTransBackConf, isSuccess: transBackSuccess, error: transBackError } = useTransferToParent();
293+
const { withdraw, isPending: isWithPending, isConfirming: isWithConf, isSuccess: withSuccess, error: withError } = useWithdraw();
294+
const [depAmount, setDepAmount] = useState("");
295+
const [depNote, setDepNote] = useState("");
296+
const [depDisclaimer, setDepDisclaimer] = useState(false);
297+
const [transTo, setTransTo] = useState("");
298+
const [transAmount, setTransAmount] = useState("");
299+
const [transLocked, setTransLocked] = useState(true);
300+
const [transLockDays, setTransLockDays] = useState("0");
301+
const [transDisclaimer, setTransDisclaimer] = useState(false);
302+
const [parentTo, setParentTo] = useState("");
303+
const [parentAmount, setParentAmount] = useState("");
304+
const [parentDisclaimer, setParentDisclaimer] = useState(false);
305+
const [withAmount, setWithAmount] = useState("");
306+
const [withDisclaimer, setWithDisclaimer] = useState(false);
307+
const isMember = role > 0 || isAdmin;
308+
292309
const paWalletValid = !paWallet || isValidAddress(paWallet);
293310
const mWalletValid = !mWallet || isValidAddress(mWallet);
294311

@@ -467,6 +484,107 @@ function ProgramDetail({ programId }: { programId: number }) {
467484
</Paper>
468485
)}
469486

487+
{/* Token Actions */}
488+
{isMember && program.active && (
489+
<Paper sx={{ p: 2, mb: 3 }}>
490+
<Typography variant="h6" gutterBottom>Token Actions</Typography>
491+
{walletBalance != null && (
492+
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
493+
Wallet Balance: {formatFula(walletBalance)} FULA
494+
</Typography>
495+
)}
496+
<Grid container spacing={3}>
497+
{/* Deposit */}
498+
<Grid item xs={12} sm={6} md={3}>
499+
<Typography variant="subtitle2" gutterBottom>Deposit</Typography>
500+
<TextField label="Amount (FULA)" value={depAmount} onChange={(e) => setDepAmount(e.target.value)}
501+
fullWidth size="small" type="number" />
502+
<TextField label="Note (max 128)" value={depNote}
503+
onChange={(e) => setDepNote(e.target.value.slice(0, 128))}
504+
fullWidth size="small" sx={{ mt: 1 }} inputProps={{ maxLength: 128 }} />
505+
<OnChainDisclaimer accepted={depDisclaimer} onChange={setDepDisclaimer} />
506+
<Button size="small" variant="contained" fullWidth sx={{ mt: 1 }}
507+
onClick={() => depositTokens(programId, depAmount, 0, depNote)}
508+
disabled={isDepPending || !depAmount || !depDisclaimer}>
509+
{isDepApproving ? <><CircularProgress size={16} sx={{ mr: 0.5 }} /> Approving...</>
510+
: isDepDepositing ? <><CircularProgress size={16} sx={{ mr: 0.5 }} /> Depositing...</>
511+
: "Deposit"}
512+
</Button>
513+
{depSuccess && <Alert severity="success" sx={{ mt: 1 }}>Deposited!</Alert>}
514+
{depError && <Alert severity="error" sx={{ mt: 1 }}>{formatContractError(depError)}</Alert>}
515+
</Grid>
516+
517+
{/* Transfer to Sub-Member */}
518+
{(isAdmin || isPA || role === MemberRoleEnum.TeamLeader) && (
519+
<Grid item xs={12} sm={6} md={3}>
520+
<Typography variant="subtitle2" gutterBottom>Transfer to Sub-Member</Typography>
521+
<TextField label="Recipient Wallet" value={transTo} onChange={(e) => setTransTo(e.target.value)}
522+
fullWidth size="small" error={!!transTo && !isValidAddress(transTo)} />
523+
<TextField label="Amount (FULA)" value={transAmount} onChange={(e) => setTransAmount(e.target.value)}
524+
fullWidth size="small" type="number" sx={{ mt: 1 }} />
525+
<FormControl fullWidth size="small" sx={{ mt: 1 }}>
526+
<InputLabel>Lock</InputLabel>
527+
<Select value={transLocked ? "locked" : "unlocked"}
528+
onChange={(e) => setTransLocked(e.target.value === "locked")} label="Lock">
529+
<MenuItem value="locked">Permanently Locked</MenuItem>
530+
<MenuItem value="unlocked">Unlocked / Time-locked</MenuItem>
531+
</Select>
532+
</FormControl>
533+
{!transLocked && (
534+
<TextField label="Lock Days (0 = unlocked)" value={transLockDays}
535+
onChange={(e) => setTransLockDays(e.target.value)}
536+
fullWidth size="small" type="number" sx={{ mt: 1 }} />
537+
)}
538+
<OnChainDisclaimer accepted={transDisclaimer} onChange={setTransDisclaimer} />
539+
<Button size="small" variant="contained" fullWidth sx={{ mt: 1 }}
540+
onClick={() => transfer(programId, transTo as `0x${string}`, transAmount, transLocked, parseInt(transLockDays) || 0)}
541+
disabled={isTransPending || isTransConf || !transTo || !transAmount || !isValidAddress(transTo) || !transDisclaimer}>
542+
{isTransPending || isTransConf ? <CircularProgress size={16} /> : "Transfer"}
543+
</Button>
544+
{transSuccess && <Alert severity="success" sx={{ mt: 1 }}>Transferred!</Alert>}
545+
{transError && <Alert severity="error" sx={{ mt: 1 }}>{formatContractError(transError)}</Alert>}
546+
</Grid>
547+
)}
548+
549+
{/* Transfer to Parent */}
550+
<Grid item xs={12} sm={6} md={3}>
551+
<Typography variant="subtitle2" gutterBottom>Transfer to Parent</Typography>
552+
{transferLimit != null && Number(transferLimit) > 0 && (
553+
<Alert severity="info" sx={{ mb: 1 }}>Limit: {String(transferLimit)}% of total balance</Alert>
554+
)}
555+
<TextField label="Parent Wallet (empty = direct parent)" value={parentTo}
556+
onChange={(e) => setParentTo(e.target.value)}
557+
fullWidth size="small" error={!!parentTo && !isValidAddress(parentTo)} />
558+
<TextField label="Amount (FULA)" value={parentAmount} onChange={(e) => setParentAmount(e.target.value)}
559+
fullWidth size="small" type="number" sx={{ mt: 1 }} />
560+
<OnChainDisclaimer accepted={parentDisclaimer} onChange={setParentDisclaimer} />
561+
<Button size="small" variant="contained" fullWidth sx={{ mt: 1 }}
562+
onClick={() => transferBack(programId, (parentTo || "0x0000000000000000000000000000000000000000") as `0x${string}`, parentAmount)}
563+
disabled={isTransBackPending || isTransBackConf || !parentAmount || !parentDisclaimer}>
564+
{isTransBackPending || isTransBackConf ? <CircularProgress size={16} /> : "Transfer to Parent"}
565+
</Button>
566+
{transBackSuccess && <Alert severity="success" sx={{ mt: 1 }}>Transferred!</Alert>}
567+
{transBackError && <Alert severity="error" sx={{ mt: 1 }}>{formatContractError(transBackError)}</Alert>}
568+
</Grid>
569+
570+
{/* Withdraw */}
571+
<Grid item xs={12} sm={6} md={3}>
572+
<Typography variant="subtitle2" gutterBottom>Withdraw</Typography>
573+
<TextField label="Amount (FULA)" value={withAmount} onChange={(e) => setWithAmount(e.target.value)}
574+
fullWidth size="small" type="number" />
575+
<OnChainDisclaimer accepted={withDisclaimer} onChange={setWithDisclaimer} />
576+
<Button size="small" variant="contained" fullWidth sx={{ mt: 1 }}
577+
onClick={() => withdraw(programId, withAmount)}
578+
disabled={isWithPending || isWithConf || !withAmount || !withDisclaimer}>
579+
{isWithPending || isWithConf ? <CircularProgress size={16} /> : "Withdraw"}
580+
</Button>
581+
{withSuccess && <Alert severity="success" sx={{ mt: 1 }}>Withdrawn!</Alert>}
582+
{withError && <Alert severity="error" sx={{ mt: 1 }}>{formatContractError(withError)}</Alert>}
583+
</Grid>
584+
</Grid>
585+
</Paper>
586+
)}
587+
470588
{/* Reward Types & Sub-Types */}
471589
{(isAdmin || canManageSubTypes) && program.active && (
472590
<Paper sx={{ p: 2, mb: 3 }}>

src/app/tokens/page.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { useAccount } from "wagmi";
1010
import { readContract } from "wagmi/actions";
1111
import { zeroAddress } from "viem";
1212
import {
13-
useApproveToken, useAddTokens, useTransferToSubMember, useTransferToParent,
13+
useDepositTokens, useTransferToSubMember, useTransferToParent,
1414
useWithdraw, useMemberBalance, useTokenBalance, useRewardTypes, useTransferLimit,
1515
} from "@/hooks/useRewardsProgram";
1616
import { formatFula, toBytes12, isValidAddress, formatContractError, fromBytes16 } from "@/lib/utils";
@@ -44,8 +44,7 @@ export default function TokensPage() {
4444
const [depositRewardType, setDepositRewardType] = useState(0);
4545
const [depositNote, setDepositNote] = useState("");
4646
const [depositDisclaimer, setDepositDisclaimer] = useState(false);
47-
const { approve, isPending: isApproving, isConfirming: isAppConfirming, isSuccess: approveSuccess } = useApproveToken();
48-
const { addTokens, isPending: isDepositing, isConfirming: isDepConfirming, isSuccess: depositSuccess, error: depositError } = useAddTokens();
47+
const { deposit: depositTokens, isApproving, isDepositing, isPending: isDepositPending, isSuccess: depositSuccess, error: depositError } = useDepositTokens();
4948

5049
// Transfer state
5150
const [transferTo, setTransferTo] = useState("");
@@ -147,7 +146,7 @@ export default function TokensPage() {
147146
{/* DEPOSIT */}
148147
<TabPanel value={tab} index={0}>
149148
<Typography variant="body2" color="text.secondary" gutterBottom>
150-
Step 1: Approve FULA tokens. Step 2: Deposit into the program.
149+
Deposit FULA tokens into the program. Approval is handled automatically.
151150
</Typography>
152151
<TextField label="Amount (FULA)" value={depositAmount} onChange={(e) => setDepositAmount(e.target.value)}
153152
fullWidth margin="normal" type="number" />
@@ -168,16 +167,13 @@ export default function TokensPage() {
168167
helperText={`${depositNote.length}/128`} />
169168
<OnChainDisclaimer accepted={depositDisclaimer} onChange={setDepositDisclaimer} />
170169
<Box sx={{ display: "flex", gap: 2, mt: 2 }}>
171-
<Button variant="outlined" onClick={() => approve(depositAmount)}
172-
disabled={isApproving || isAppConfirming || !depositAmount || !depositDisclaimer}>
173-
{isApproving || isAppConfirming ? <CircularProgress size={20} /> : "1. Approve"}
174-
</Button>
175-
<Button variant="contained" onClick={() => addTokens(pid, depositAmount, depositRewardType, depositNote)}
176-
disabled={isDepositing || isDepConfirming || !depositAmount || !depositDisclaimer}>
177-
{isDepositing || isDepConfirming ? <CircularProgress size={20} /> : "2. Deposit"}
170+
<Button variant="contained" onClick={() => depositTokens(pid, depositAmount, depositRewardType, depositNote)}
171+
disabled={isDepositPending || !depositAmount || !depositDisclaimer}>
172+
{isApproving ? <><CircularProgress size={20} sx={{ mr: 1 }} /> Approving...</>
173+
: isDepositing ? <><CircularProgress size={20} sx={{ mr: 1 }} /> Depositing...</>
174+
: "Deposit"}
178175
</Button>
179176
</Box>
180-
{approveSuccess && <Alert severity="info" sx={{ mt: 2 }}>Approval confirmed. Now click Deposit.</Alert>}
181177
{depositSuccess && <Alert severity="success" sx={{ mt: 2 }}>Deposit successful!</Alert>}
182178
{depositError && <Alert severity="error" sx={{ mt: 2 }}>{formatContractError(depositError)}</Alert>}
183179
</TabPanel>

0 commit comments

Comments
 (0)