Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions frontend/src/components/badges/BadgesList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo, useState } from "react";
import { BadgeCheck } from "lucide-react";
import { BadgeCheck, ThumbsUp, ThumbsDown } from "lucide-react";

import {
Card,
Expand All @@ -8,7 +8,9 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { useGetBadges } from "@/hooks/badges/use-get-badges";
import { useVoteBadge } from "@/hooks/badges/use-vote-badge";
import { HARD_CODED_BADGES } from "@/lib/constants/badgeConstants";
import type { Badge } from "@/lib/types/badges";
import { Search } from "lucide-react";
Expand All @@ -18,7 +20,8 @@ import { AiOutlineLoading3Quarters } from "react-icons/ai";
import ErrorDisplay from "@/components/displayError/index";

export function BadgesList(): React.ReactElement {
const { data, isLoading, error } = useGetBadges();
const { data, isLoading, error, refetch } = useGetBadges();
const { vote, isPending } = useVoteBadge();
const [searchQuery, setSearchQuery] = useState("");
const list = (data && data.length > 0 ? data : HARD_CODED_BADGES) as Badge[];

Expand All @@ -28,6 +31,16 @@ export function BadgesList(): React.ReactElement {
return list.filter((b) => b.name.toLowerCase().includes(q));
}, [list, searchQuery]);

const handleVote = async (badgeName: string, isUpvote: boolean) => {
try {
await vote(badgeName, isUpvote);
// Refetch badges after voting
refetch();
} catch (error) {
console.error("Voting failed:", error);
}
};

if (isLoading) {
return (
<div className="flex justify-start">
Expand Down Expand Up @@ -69,7 +82,31 @@ export function BadgesList(): React.ReactElement {
</CardTitle>
<CardDescription>{badge.description}</CardDescription>
</CardHeader>
<CardContent />
<CardContent>
<div className="flex items-center justify-between">
<div className="text-sm text-gray-600">
Score: {badge.voteScore || 0}
</div>
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => handleVote(badge.name, true)}
disabled={isPending}
>
<ThumbsUp className="h-4 w-4" />
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleVote(badge.name, false)}
disabled={isPending}
>
<ThumbsDown className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
))}
</div>
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/hooks/badges/use-add-pointers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useWriteContract, useWaitForTransactionReceipt } from "wagmi";
import { badgeRegistryAbi } from "@/lib/abis/badgeRegistryAbi";
import { BADGE_REGISTRY_ADDRESS } from "@/lib/constants/blockchainConstants";

export function useAddPointers() {
const { writeContract, data: hash, isPending, error } = useWriteContract();

const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash,
});

const addPointers = (fromBadge: string, toBadges: string[]) => {
writeContract({
abi: badgeRegistryAbi,
address: BADGE_REGISTRY_ADDRESS,
functionName: "addPointers",
args: [fromBadge as `0x${string}`, toBadges as `0x${string}`[]],
});
};

return {
addPointers,
isPending,
isConfirming,
isSuccess,
error,
};
}
53 changes: 53 additions & 0 deletions frontend/src/hooks/badges/use-get-badge-details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useMemo } from "react";
import { useReadContract } from "wagmi";
import { badgeRegistryAbi } from "@/lib/abis/badgeRegistryAbi";
import { BADGE_REGISTRY_ADDRESS } from "@/lib/constants/blockchainConstants";
import type { Badge } from "@/lib/types/badges";
import { bytes32ToString } from "@/lib/utils/blockchainUtils";

export function useGetBadgeDetails(badgeName?: string) {
const address = BADGE_REGISTRY_ADDRESS;

const badgeQuery = useReadContract({
abi: badgeRegistryAbi,
address,
functionName: "getBadge",
args: badgeName ? [badgeName as `0x${string}`] : undefined,
query: {
enabled: Boolean(address) && Boolean(badgeName),
},
});

const pointersQuery = useReadContract({
abi: badgeRegistryAbi,
address,
functionName: "getPointers",
args: badgeName ? [badgeName as `0x${string}`] : undefined,
query: {
enabled: Boolean(address) && Boolean(badgeName),
},
});

const data: Badge | undefined = useMemo(() => {
const badgeResult = badgeQuery.data as
| [`0x${string}`, `0x${string}`, `0x${string}`, bigint]
| undefined;
const pointersResult = pointersQuery.data as `0x${string}`[] | undefined;

if (!badgeResult) return undefined;

const [nameBytes, descriptionBytes, creator, voteScore] = badgeResult;
return {
name: bytes32ToString(nameBytes),
description: bytes32ToString(descriptionBytes),
creator,
voteScore: Number(voteScore),
pointers: pointersResult?.map(bytes32ToString),
};
}, [badgeQuery.data, pointersQuery.data]);

const isLoading = badgeQuery.isLoading || pointersQuery.isLoading;
const error = badgeQuery.error || pointersQuery.error;

return { data, isLoading, error, refetch: badgeQuery.refetch };
}
6 changes: 4 additions & 2 deletions frontend/src/hooks/badges/use-get-badges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ export function useGetBadges(): {

const data: Badge[] | undefined = useMemo(() => {
const results = badgesQuery.data as
| [`0x${string}`, `0x${string}`, `0x${string}`][]
| [`0x${string}`, `0x${string}`, `0x${string}`, bigint][]
| undefined;
if (!results) return undefined;
return results.map(([nameBytes, descriptionBytes]) => ({
return results.map(([nameBytes, descriptionBytes, creator, voteScore]) => ({
name: bytes32ToString(nameBytes),
description: bytes32ToString(descriptionBytes),
creator,
voteScore: Number(voteScore),
}));
}, [badgesQuery.data]);

Expand Down
28 changes: 28 additions & 0 deletions frontend/src/hooks/badges/use-vote-badge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useWriteContract, useWaitForTransactionReceipt } from "wagmi";
import { badgeRegistryAbi } from "@/lib/abis/badgeRegistryAbi";
import { BADGE_REGISTRY_ADDRESS } from "@/lib/constants/blockchainConstants";

export function useVoteBadge() {
const { writeContract, data: hash, isPending, error } = useWriteContract();

const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash,
});

const vote = (badgeName: string, isUpvote: boolean) => {
writeContract({
abi: badgeRegistryAbi,
address: BADGE_REGISTRY_ADDRESS,
functionName: "vote",
args: [badgeName as `0x${string}`, isUpvote],
});
};

return {
vote,
isPending,
isConfirming,
isSuccess,
error,
};
}
57 changes: 57 additions & 0 deletions frontend/src/lib/abis/badgeRegistryAbi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,45 @@ export const badgeRegistryAbi = [
{ name: "", type: "bytes32" },
{ name: "", type: "bytes32" },
{ name: "", type: "address" },
{ name: "", type: "int256" },
],
},
{
type: "function",
name: "getBadge",
stateMutability: "view",
inputs: [{ name: "name", type: "bytes32" }],
outputs: [
{ name: "", type: "bytes32" },
{ name: "", type: "bytes32" },
{ name: "", type: "address" },
{ name: "", type: "int256" },
],
},
{
type: "function",
name: "getPointers",
stateMutability: "view",
inputs: [{ name: "name", type: "bytes32" }],
outputs: [{ name: "", type: "bytes32[]" }],
},
{
type: "function",
name: "getVoteScore",
stateMutability: "view",
inputs: [{ name: "name", type: "bytes32" }],
outputs: [{ name: "", type: "int256" }],
},
{
type: "function",
name: "hasVoted",
stateMutability: "view",
inputs: [
{ name: "badgeName", type: "bytes32" },
{ name: "voter", type: "address" },
],
outputs: [{ name: "", type: "bool" }],
},
{
type: "function",
name: "createBadge",
Expand All @@ -28,4 +65,24 @@ export const badgeRegistryAbi = [
],
outputs: [],
},
{
type: "function",
name: "addPointers",
stateMutability: "nonpayable",
inputs: [
{ name: "fromBadge", type: "bytes32" },
{ name: "toBadges", type: "bytes32[]" },
],
outputs: [],
},
{
type: "function",
name: "vote",
stateMutability: "nonpayable",
inputs: [
{ name: "badgeName", type: "bytes32" },
{ name: "isUpvote", type: "bool" },
],
outputs: [],
},
] as const;
10 changes: 10 additions & 0 deletions frontend/src/lib/constants/badgeConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,33 @@ export const HARD_CODED_BADGES: Badge[] = [
{
name: "Open Source Contributor",
description: "Contributed code to The Guild Genesis repositories.",
creator: "0x0000000000000000000000000000000000000000",
voteScore: 0,
},
{
name: "Smart Contract Deployer",
description:
"Successfully deployed a smart contract to testnet or mainnet.",
creator: "0x0000000000000000000000000000000000000000",
voteScore: 0,
},
{
name: "Community Mentor",
description: "Helped onboard and mentor new members of the community.",
creator: "0x0000000000000000000000000000000000000000",
voteScore: 0,
},
{
name: "Bug Hunter",
description:
"Reported and helped resolve significant issues or vulnerabilities.",
creator: "0x0000000000000000000000000000000000000000",
voteScore: 0,
},
{
name: "Docs Champion",
description: "Improved documentation or tutorials for the project.",
creator: "0x0000000000000000000000000000000000000000",
voteScore: 0,
},
];
3 changes: 3 additions & 0 deletions frontend/src/lib/types/badges.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export type Badge = {
name: string;
description: string;
creator: string;
voteScore: number;
pointers?: string[]; // optional for backward compatibility
};
Loading
Loading