Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(isle): view credential details #594

Merged
merged 8 commits into from
Mar 6, 2025
139 changes: 103 additions & 36 deletions apps/isle/src/features/permissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { DeleteIcon } from "@/components/icons/delete";
import { ViewIcon } from "@/components/icons/view";
import { BreadcrumbLink, BreadcrumbRoot, Button } from "@/components/ui";
import { useIsleStore } from "@/store";
import type { idOSCredential } from "@idos-network/core";
import { RequestPermission } from "./request-permission";

// @todo: On grants with a timelock, show the End-Date of the Timelock and prevent to revoke access
Expand Down Expand Up @@ -191,7 +192,7 @@ function GrantRevocation({ grant, onDismiss, onSuccess }: GrantRevocationProps)
);
}

function Breadcrumbs() {
function Breadcrumbs({ goHome }: { goHome: () => void }) {
return (
<BreadcrumbRoot
size="lg"
Expand All @@ -211,6 +212,7 @@ function Breadcrumbs() {
_dark: "neutral.50",
_light: "neutral.950",
}}
onClick={goHome}
>
Permissions
</BreadcrumbLink>
Expand All @@ -230,42 +232,87 @@ function Breadcrumbs() {
}

//@ts-ignore
function CredentialDetails() {
return (
<Stack gap="6">
<Breadcrumbs />
<Stack gap="3">
<Flex justifyContent="space-between">
<Flex alignItems="center" gap="2.5">
<Image />
<Text fontWeight="semibold" color={{ _dark: "neutral.50", _light: "neutral.950" }}>
Common
</Text>
function CredentialDetails({ goHome, onRevoke }: { goHome: () => void; onRevoke: () => void }) {
const node = useIsleStore((state) => state.node);
const [credential, setCredential] = useState<idOSCredential | null>(null);
const [status, setStatus] = useState<"idle" | "pending" | "success" | "error">("idle");

useEffect(() => {
if (!node) return;
node.on("update-view-credential-details-status", (data) => {
setStatus(data.status);
if (data.status === "success") {
setCredential(data.credential ?? null);
}
});
}, [node]);

if (status === "pending") {
return (
<Center flexDir="column" gap="6">
<Spinner size="xl" />
</Center>
);
}

if (status === "error") {
return (
<Center flexDir="column" gap="6">
<Heading fontSize="lg" fontWeight="semibold" textAlign="center">
Error while viewing credential details.
</Heading>
</Center>
);
}

if (status === "success") {
return (
<Stack gap="6" w="full" maxW="full" overflow="auto">
<Breadcrumbs goHome={goHome} />
<Stack gap="3">
<Flex justifyContent="space-between">
<Flex alignItems="center" gap="2.5">
<Image />
<Text fontWeight="semibold" color={{ _dark: "neutral.50", _light: "neutral.950" }}>
Common
</Text>
</Flex>
<AuthorizedIcon color="aquamarine.400" />
</Flex>
<AuthorizedIcon color="aquamarine.400" />
</Flex>
<CredentialContent />
<Flex maxW="324px" maxH="340px" overflow="auto">
<CredentialContent content={credential?.content} />
</Flex>
</Stack>
<Button onClick={onRevoke}>
Revoke Access
<Icon w={5} h={5} color="neutral.950">
<DeleteIcon />
</Icon>
</Button>
</Stack>
<Button>
Revoke Access
<Icon w={5} h={5} color="neutral.950">
<DeleteIcon />
</Icon>
</Button>
</Stack>
);
);
}
}

function CredentialContent() {
function CredentialContent({ content }: { content: string | undefined }) {
// @todo: decide which data to show
const parsedContent: Record<string, string> = content ? JSON.parse(content) : {};
return (
<Stack bg={{ _dark: "neutral.800", _light: "neutral.200" }} borderRadius="xl" gap="0">
{Object.entries({ name: "John" }).map(([key, value], index) => (
<Stack
bg={{ _dark: "neutral.800", _light: "neutral.200" }}
borderRadius="xl"
gap="0"
w="full"
maxW="full"
overflow="auto"
>
{Object.entries(parsedContent).map(([key, value], index) => (
<Flex
key={key}
py="3.5"
px="4"
gap="5"
borderColor={{ _dark: "neutral.700", _light: "neutral.300" }}
borderBottomWidth={index === Object.keys({}).length - 1 ? 0 : 1}
borderBottomWidth={index === Object.keys(parsedContent).length - 1 ? 0 : 1}
borderStyle="solid"
>
<Text
Expand All @@ -282,7 +329,7 @@ function CredentialContent() {
fontWeight="medium"
color={{ _dark: "neutral.50", _light: "neutral.950" }}
>
{value}
{JSON.stringify(value)}
</Text>
</Flex>
))}
Expand All @@ -303,12 +350,14 @@ interface AccessGrantWithGrantee {
dataId: string;
type: string;
grantee: GranteeInfo;
mode: "view" | "revoke";
lockedUntil: number;
originalCredentialId: string;
}

export function Permissions() {
const accessGrants = useIsleStore((state) => state.accessGrants);
const [grantToRevoke, setGrantToRevoke] = useState<AccessGrantWithGrantee | null>(null);
const [grant, setGrant] = useState<AccessGrantWithGrantee | null>(null);
const [permissionStatus, setPermissionStatus] = useState<
"idle" | "request-permission" | "pending" | "success" | "error"
>("idle");
Expand Down Expand Up @@ -341,12 +390,20 @@ export function Permissions() {
return Array.from(accessGrants?.entries() ?? []);
}, [accessGrants]);

if (grantToRevoke) {
if (grant?.mode === "view") {
return (
<CredentialDetails
onRevoke={() => setGrant({ ...grant, mode: "revoke" })}
goHome={() => setGrant(null)}
/>
);
}
if (grant?.mode === "revoke") {
return (
<GrantRevocation
grant={grantToRevoke}
onSuccess={() => setGrantToRevoke(null)}
onDismiss={() => setGrantToRevoke(null)}
grant={grant}
onSuccess={() => setGrant(null)}
onDismiss={() => setGrant(null)}
/>
);
}
Expand Down Expand Up @@ -470,7 +527,16 @@ export function Permissions() {
size="xs"
colorPalette="green"
rounded="full"
onClick={() => {}}
onClick={() => {
setGrant({
grantee,
...grant,
mode: "view",
});
node?.post("view-credential-details", {
id: grant.originalCredentialId,
});
}}
>
<ViewIcon w="5" h="5" color="neutral.400" />
</IconButton>
Expand All @@ -482,9 +548,10 @@ export function Permissions() {
colorPalette="green"
rounded="full"
onClick={() => {
setGrantToRevoke({
setGrant({
grantee,
...grant,
mode: "revoke",
});
}}
>
Expand Down
8 changes: 7 additions & 1 deletion apps/isle/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ interface NodeState {
theme?: IsleTheme;
accessGrants: Map<
{ granteePublicKey: string; meta: { name: string; logo: string; url: string } },
{ id: string; dataId: string; type: string; lockedUntil: number }[]
{
id: string;
dataId: string;
type: string;
originalCredentialId: string;
lockedUntil: number;
}[]
> | null;
initializeNode: () => () => void;
connectWallet: () => void;
Expand Down
25 changes: 25 additions & 0 deletions examples/consumer-and-issuer/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { goTry } from "go-try";
import { useEffect, useRef, useState } from "react";
import { useAccount, useSignMessage } from "wagmi";

import invariant from "tiny-invariant";

export default function Home() {
const isleRef = useRef<ReturnType<typeof createIsleController> | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
Expand All @@ -27,6 +29,10 @@ export default function Home() {

isleRef.current = createIsleController({
container: container.id,
enclaveOptions: {
container: "#idOS-enclave",
url: "https://localhost:5173/",
},
credentialRequirements: {
acceptedIssuers: [
{
Expand Down Expand Up @@ -127,6 +133,25 @@ export default function Home() {
});
}
});

isle.on("view-credential-details", async ({ data }) => {
isle?.send("update-view-credential-details-status", {
status: "pending",
});

try {
const credential = await isle?.viewCredentialDetails(data.id);
isle?.send("update-view-credential-details-status", {
status: "success",
credential,
});
} catch (error) {
isle?.send("update-view-credential-details-status", {
status: "error",
error: error as Error,
});
}
});
}, [address, signMessageAsync]);

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions packages/@controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"version": "0.0.1",
"dependencies": {
"@idos-network/core": "workspace:*",
"@idos-network/idos-sdk": "workspace:*",
"@sanity/comlink": "^3.0.1",
"@wagmi/core": "^2.16.4",
"ethers": "catalog:",
Expand Down
Loading