Skip to content

Commit c0874d5

Browse files
committed
features
1 parent a88b59e commit c0874d5

File tree

2 files changed

+93
-11
lines changed

2 files changed

+93
-11
lines changed

src/app/balance/page.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,13 @@ function MemberProgramRow({ memberID, programId }: { memberID: string; programId
6767

6868
/* -- Claim Member Section -- */
6969

70-
function ClaimMemberSection({ memberID, programCount }: { memberID: string; programCount: number }) {
70+
function ClaimMemberSection({ memberID, programCount, initialProgramId, initialEditCode }: {
71+
memberID: string; programCount: number; initialProgramId?: string; initialEditCode?: string;
72+
}) {
7173
const { address, isConnected } = useAccount();
7274
const { claimMember, isPending, isConfirming, isSuccess, error } = useClaimMember();
73-
const [editCode, setEditCode] = useState("");
74-
const [claimProgramId, setClaimProgramId] = useState("1");
75+
const [editCode, setEditCode] = useState(initialEditCode || "");
76+
const [claimProgramId, setClaimProgramId] = useState(initialProgramId || "1");
7577
const [disclaimer, setDisclaimer] = useState(false);
7678

7779
useEffect(() => {
@@ -248,6 +250,8 @@ function BalanceContent() {
248250
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
249251
const searchParams = useSearchParams();
250252
const memberParam = searchParams.get("member") || "";
253+
const claimParam = searchParams.get("claim") || "";
254+
const codeParam = searchParams.get("code") || "";
251255
const [memberID, setMemberID] = useState(memberParam);
252256
const [searchID, setSearchID] = useState(memberParam);
253257

@@ -339,7 +343,8 @@ function BalanceContent() {
339343
{memberExists && !memberWallet && (
340344
<>
341345
<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} />
346+
<ClaimMemberSection memberID={searchID} programCount={count}
347+
initialProgramId={claimParam} initialEditCode={codeParam} />
343348
</>
344349
)}
345350

src/app/programs/page.tsx

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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 &quot;{displayMemberId}&quot; 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 }) {
640672
function 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

Comments
 (0)