Skip to content

Commit

Permalink
Merge pull request #6 from harshmittal1750/feature/manage-issued-atte…
Browse files Browse the repository at this point in the history
…stations

Issue 1 done: feature : Manage issued attestations
  • Loading branch information
ribeirojose authored Jul 29, 2024
2 parents 7a833f2 + 518e183 commit d6b7cf8
Show file tree
Hide file tree
Showing 14 changed files with 9,972 additions and 11,936 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"start": "next start",
"lint": "next lint",
"format": "prettier --write \"**/*.{md,html,mjml,json,yml,css,scss,ts,tsx,js,jsx}\"",
"lint:fix": "eslint '**/*.{ts,tsx}' --fix"
"lint:fix": "eslint '**/*.{ts,tsx}' --fix",
"tsc": "tsc"
},
"dependencies": {
"@ethereum-attestation-service/eas-contracts": "^1.7.1",
Expand Down Expand Up @@ -70,4 +71,4 @@
"typescript": "^5.5.3",
"utf-8-validate": "^6.0.4"
}
}
}
89 changes: 89 additions & 0 deletions src/app/_components/ManageAttestation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use client";
import { NextPage } from "next";
import useGetAttestations from "@/hooks/useGetAttestations";
import {
Table,
TableCaption,
TableHeader,
TableRow,
TableHead,
TableBody,
TableCell,
} from "@/components/ui/table";
import RevokeAttestation from "./RevokeAttestation";
import { useCallback, useMemo } from "react";
import { formatKey } from "@/lib/formatKey";
import { formatTime } from "@/lib/formatTime";
const ManageAttestation: NextPage = () => {
const { attestations, loading, error } = useGetAttestations();

const renderTableHeader = useMemo(() => {
if (attestations && attestations.length > 0) {
return (
<TableRow>
<TableHead className="font-semibold">Action</TableHead>
{Object.keys(attestations[0]).map((key) => (
<TableHead className="font-semibold" key={key}>
{formatKey(key)}
</TableHead>
))}
</TableRow>
);
}
return <></>;
}, [attestations]);

const renderTableRows = useCallback(() => {
return attestations?.map((attestation: any) => (
<TableRow key={attestation.id}>
<TableCell className="text-right">
<RevokeAttestation
uid={attestation.id}
schemaId={attestation.schema.id}
isRevoked={attestation.revoked}
/>
</TableCell>
{Object.entries(attestation).map(([key, value], index) => (
<TableCell
key={index}
className={
index === Object.entries(attestation).length - 1
? "text-right"
: ""
}
>
{key === "revocationTime" || key === "expirationTime"
? formatTime(Number(value), key)
: key === "schema"
? (value as any).id // Ensure value is treated as an object
: String(value)}
</TableCell>
))}
</TableRow>
));
}, [attestations]);

if (loading) {
return <p>Loading...</p>;
}

if (error) {
return <p>{error.message}</p>;
}

return (
<div>
<Table>
<TableCaption>
{attestations
? "A list of your attestations"
: "Please connect wallet"}
</TableCaption>
<TableHeader>{renderTableHeader}</TableHeader>
<TableBody>{renderTableRows()}</TableBody>
</Table>
</div>
);
};

export default ManageAttestation;
38 changes: 38 additions & 0 deletions src/app/_components/RevokeAttestation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Button } from "@/components/ui/button";
import useRevokeAttestation from "@/hooks/useRevokeOnChainAttestation";

const RevokeAttestation = ({
uid,
schemaId,
isRevoked,
}: {
uid: string;
schemaId: string;
isRevoked: boolean;
}) => {
const { revokeAttestation, loading, error, success } = useRevokeAttestation();

const handleRevoke = () => {
revokeAttestation(uid, schemaId);
};

return (
<div>
<Button
variant={"outline"}
className="bg-red-500 text-white"
onClick={handleRevoke}
disabled={loading || isRevoked}
>
Revoke
</Button>
{loading && <p>Loading...</p>}
{error && <p className="text-red-500">{error}</p>}
{success && (
<p className="text-green-500">Attestation revoked successfully</p>
)}
</div>
);
};

export default RevokeAttestation;
14 changes: 13 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
SELF_ATTESTATION_SCHEMA_UID,
THIRD_PARTY_ATTESTATION_SCHEMA_UID,
} from "@/hooks/useAttestationCreation";
import ManageAttestation from "./_components/ManageAttestation";

const App: React.FC = () => {
const { fetchOrInitializeSchema } = usePGPKeyServer();
Expand All @@ -39,12 +40,13 @@ const App: React.FC = () => {
</Card>

<Tabs defaultValue="self-attest">
<TabsList className="grid w-full grid-cols-3">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="self-attest">Self-Attest PGP Key</TabsTrigger>
<TabsTrigger value="third-party-attest">
Third-Party Attestation
</TabsTrigger>
<TabsTrigger value="query">Query Trust Score</TabsTrigger>
<TabsTrigger value="manage">Manage Attestations</TabsTrigger>
</TabsList>
<TabsContent value="self-attest">
<Card>
Expand Down Expand Up @@ -76,6 +78,16 @@ const App: React.FC = () => {
</CardContent>
</Card>
</TabsContent>
<TabsContent value="manage">
<Card>
<CardHeader>
<CardTitle>Manage Attestations</CardTitle>
</CardHeader>
<CardContent>
<ManageAttestation />
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
</Layout>
Expand Down
10 changes: 7 additions & 3 deletions src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { type ReactNode, useState } from "react";
import { type State, WagmiProvider } from "wagmi";

import { getConfig } from "@/wagmi";
import { Provider } from "urql";
import { client } from "@/lib/urql-client";

export function Providers(props: {
children: ReactNode;
Expand All @@ -15,9 +17,11 @@ export function Providers(props: {

return (
<WagmiProvider config={config} initialState={props.initialState}>
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
<Provider value={client}>
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
</Provider>
</WagmiProvider>
);
}
117 changes: 117 additions & 0 deletions src/components/ui/table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import * as React from "react"

import { cn } from "@/lib/utils"

const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"

const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"

const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"

const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"

const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"

const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"

const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"

const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"

export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
19 changes: 19 additions & 0 deletions src/hooks/useGetAttestations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";

import { ATTESTATIONS_FOR_SPECIFIC_ATTESTER } from "@/lib/graphql/queries";
import { useQuery } from "urql";
import { useAccount } from "wagmi";

const useGetAttestations = () => {
const { address } = useAccount();
const [result] = useQuery({
query: ATTESTATIONS_FOR_SPECIFIC_ATTESTER,
variables: { attester: address || "" },
pause: !address,
});

const { data, fetching, error } = result;
return { attestations: data?.attestations, loading: fetching, error };
};

export default useGetAttestations;
Loading

0 comments on commit d6b7cf8

Please sign in to comment.