Skip to content

Commit

Permalink
Merge pull request #211 from HunnySajid/feat/cred-chain
Browse files Browse the repository at this point in the history
Feat/cred chain
  • Loading branch information
HunnySajid authored Oct 29, 2024
2 parents 6cf6a8e + 39bed69 commit 2799a16
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 22 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"lodash": "^4.17.21",
"nanoid": "^5.0.6",
"react": "^18.2.0",
"react-collapsible": "^2.10.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-intl": "^6.6.2",
Expand Down
57 changes: 41 additions & 16 deletions src/components/credentialCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import { Text, Subtext, Card, Flex, Box } from "@components/ui";
import CredentialIcon from "@components/shared/icons/credential";
import ValidIcon from "@components/shared/icons/valid";
import RevokedIcon from "@components/shared/icons/revoked";
import { CredentialRecursiveChain } from "./credentialRecursiveChain";

export function CredentialCard({
credential,
showExplore,
exploreChain,
idx,
}: {
credential: ICredential;
showExplore?: boolean;
exploreChain?: (id: string) => void;
idx?: number;
}): JSX.Element {
const { formatMessage } = useIntl();

Expand All @@ -27,23 +34,24 @@ export function CredentialCard({
<Box marginBottom={1} fontSize={0}>
<Text $color="text">{credential.schema.description}</Text>
</Box>
<Box marginBottom={1}>
<Text fontSize={0} fontWeight="bold" $color="heading">
<>
{formatMessage({ id: "credential.issuee.label" })}{" "}
<Subtext fontWeight="normal" $color="text">
{credential.issueeName}
</Subtext>
</>
</Text>
</Box>
<Flex flexDirection="row" justifyContent="space-between" fontSize={0}>
<div>
<Text fontWeight="bold" $color="heading">
{formatMessage({ id: "credential.lastUsed.label" })}{" "}
{credential.issueeName ? (
<Box marginBottom={1}>
<Text fontSize={0} fontWeight="bold" $color="heading">
<>
{formatMessage({ id: "credential.issuee.label" })}{" "}
<Subtext fontWeight="normal" $color="text">
{credential.issueeName}
</Subtext>
</>
</Text>
<Text $color="text">November 08, 2023</Text>
</div>
</Box>
) : null}
<Flex
flexDirection="row-reverse"
justifyContent="space-between"
alignItems="center"
fontSize={0}
>
{credential.status?.et === "iss" ? (
<Flex flexDirection="column" alignItems="center" color="green">
<ValidIcon size={6} />
Expand All @@ -58,6 +66,23 @@ export function CredentialCard({
</Flex>
)}
</Flex>
{showExplore && credential?.chains?.length ? (
<CredentialRecursiveChain
credential={credential}
idx={String(idx)}
openMessage={
<Text $cursorPointer fontSize={0} $color="heading">
{"explore >>"}
</Text>
}
closeMessage={
<Text $cursorPointer fontSize={0} $color="heading">
{"close (x)"}
</Text>
}
exploreChain={exploreChain}
/>
) : null}
</>
</Card>
);
Expand Down
57 changes: 54 additions & 3 deletions src/components/credentialList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import toast from "react-hot-toast";
import { UI_EVENTS } from "@config/event-types";
import { sendMessage } from "@src/shared/browser/runtime-utils";
import { CredentialCard } from "@components/credentialCard";
import { Loader, Flex, Box, Text } from "@components/ui";
import { Loader, Flex, Box, Text, IconButton } from "@components/ui";
import BackArrow from "@components/shared/icons/back-arrow";

export function CredentialList(): JSX.Element {
const [ogCredentials, setOGCredentials] = useState([]);
const [credentials, setCredentials] = useState([]);
const [breadcrumbs, setBreadcrumbs] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const { formatMessage } = useIntl();
const fetchCredentials = async () => {
Expand All @@ -20,24 +23,72 @@ export function CredentialList(): JSX.Element {
if (error) {
toast.error(error?.message);
} else {
setCredentials(data.credentials);
const _credentials =
data?.credentials?.filter((_cred) => _cred.issueeName) ?? [];
setCredentials(_credentials);
setOGCredentials(_credentials);
}
};

useEffect(() => {
fetchCredentials();
}, []);

const traverseCredentialChain = (id: string) => {
const indices = id.split(".");
const _breadcrumb = [];
if (!indices?.length || indices?.length === 1) {
setCredentials(ogCredentials);
return;
}

const firstNode = credentials[indices[0]];
let current = firstNode;
_breadcrumb.push(current);

indices.slice(1).forEach((indice) => {
if (!current?.chains?.[indice]) return;
current = current.chains[indice];
_breadcrumb.push(current);
});
setCredentials([current]);
setBreadcrumbs(_breadcrumb);
};
return (
<>
{isLoading ? (
<Flex flexDirection="row" justifyContent="center" alignItems="center">
<Loader size={6} />
</Flex>
) : null}
{breadcrumbs?.length ? (
<Flex $flexGap={1}>
<IconButton
onClick={() => {
setCredentials(ogCredentials);
setBreadcrumbs([]);
}}
>
<BackArrow size={5} />
</IconButton>
<Box>
{breadcrumbs.map((bc, indx) => (
<Text fontSize={0} $color="subtext">
{bc?.schema?.title}{" "}
{indx < breadcrumbs.length - 1 ? <strong>{" > "}</strong> : ""}
</Text>
))}
</Box>
</Flex>
) : null}
{credentials.map((credential, index) => (
<Box marginY={2} marginX={3} key={index}>
<CredentialCard credential={credential} />
<CredentialCard
credential={credential}
showExplore={true}
idx={index}
exploreChain={traverseCredentialChain}
/>
</Box>
))}
{!isLoading && !credentials?.length ? (
Expand Down
89 changes: 89 additions & 0 deletions src/components/credentialRecursiveChain.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useIntl } from "react-intl";
import { ICredential } from "@config/types";
import Collapsible from "react-collapsible";
import { Flex, Box, IconButton } from "@components/ui";
import OpenFile from "@components/shared/icons/open-file";

interface ITriggerItem {
title: string;
onClickTrigger?: (idx: string) => void;
idx: string;
}

function TriggerItem({ title, onClickTrigger, idx }: ITriggerItem) {
return (
<Flex $cursorPointer $flexGap={1}>
{title}{" "}
<IconButton
onClick={(e) => {
if (onClickTrigger) {
onClickTrigger(idx);
}
e?.stopPropagation();
}}
>
<OpenFile size={3} />
</IconButton>
</Flex>
);
}

export function CredentialRecursiveChain({
credential,
openMessage,
closeMessage,
idx,
exploreChain,
}: {
credential: ICredential;
openMessage?: string | React.ReactElement;
closeMessage?: string | React.ReactElement;
idx: string;
exploreChain?: (id: string) => void;
}): JSX.Element {
const { formatMessage } = useIntl();

if (credential?.chains?.length === 0)
return (
<TriggerItem
idx={idx}
title={credential?.schema?.title}
onClickTrigger={exploreChain}
/>
);

return (
<Collapsible
trigger={
openMessage ?? (
<TriggerItem
idx={idx}
title={`> ${credential?.schema?.title}`}
onClickTrigger={exploreChain}
/>
)
}
triggerWhenOpen={
closeMessage ?? (
<TriggerItem
idx={idx}
title={credential?.schema?.title}
onClickTrigger={exploreChain}
/>
)
}
>
{credential?.chains?.map((chain, index) => (
<>
<Box marginY={2} marginX={3} key={index}>
<CredentialRecursiveChain
credential={chain}
idx={`${idx}.${index}`}
exploreChain={exploreChain}
/>
</Box>
</>
))}
</Collapsible>
);
}
3 changes: 2 additions & 1 deletion src/components/selectCredential.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export function SelectCredential(): JSX.Element {
if (error) {
toast.error(error?.message);
} else {
setCredentials(data.credentials);
const _credentials = data?.credentials?.filter(_cred => _cred.issueeName) ?? [];
setCredentials(_credentials);
}
};

Expand Down
40 changes: 40 additions & 0 deletions src/components/shared/icons/back-arrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export default function BackArrow({
size,
scale = 4,
}: {
size: number;
scale?: number;
}) {
return (
<svg
fill="currentColor"
width={size * scale}
height={size * scale}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 22H15C20 22 22 20 22 15V9C22 4 20 2 15 2H9C4 2 2 4 2 9V15C2 20 4 22 9 22Z"
stroke="#292D32"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M9.00002 15.3802H13.92C15.62 15.3802 17 14.0002 17 12.3002C17 10.6002 15.62 9.22021 13.92 9.22021H7.15002"
stroke="#292D32"
stroke-width="1.5"
stroke-miterlimit="10"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M8.57 10.7701L7 9.19012L8.57 7.62012"
stroke="#292D32"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
26 changes: 26 additions & 0 deletions src/components/shared/icons/open-file.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default function OpenFile({
size,
scale = 4,
}: {
size: number;
scale?: number;
}) {
return (
<svg
fill="currentColor"
width={size * scale}
height={size * scale}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<g fill-rule="evenodd" transform="translate(42.667 42.667)">
<path d="M178.0832,42.6666667 L221.594,77.0716667 L191.217,107.448667 L163.24992,85.3333333 L42.6666667,85.3333333 L42.6666667,296.106667 L82.0209067,170.666667 L341.333333,170.666667 L341.333,170.665667 L384,170.665667 L437.333333,170.666667 L372.583253,384 L-2.13162821e-14,384 L-2.13162821e-14,42.6666667 L178.0832,42.6666667 Z M379.79136,213.333333 L113.354027,213.333333 L73.1874133,341.333333 L340.95808,341.333333 L379.79136,213.333333 Z" />

<path
fill-rule="nonzero"
d="M384,7.10542736e-15 L384,149.333333 L341.333333,149.333333 L341.332777,72.836 L264.836777,149.332777 L204.496777,149.333333 L311.162777,42.666 L234.666667,42.6666667 L234.666667,7.10542736e-15 L384,7.10542736e-15 Z"
/>
</g>
</svg>
);
}
2 changes: 1 addition & 1 deletion src/components/ui/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Loader } from "../loader";

interface IButton {
type?: "button" | "reset" | "submit" | undefined;
handleClick?: () => void;
handleClick?: (e?: any) => void;
isLoading?: boolean;
children?: JSX.Element | any;
disabled?: boolean;
Expand Down
3 changes: 2 additions & 1 deletion src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface IIdentifier {
export interface ICredential {
issueeName: string;
ancatc: string[];
chains?: ICredential[];
sad: { a: { i: string }; d: string };
schema: {
title: string;
Expand Down Expand Up @@ -99,4 +100,4 @@ export interface ISession {

export interface ISessionConfig {
sessionOneTime: boolean;
}
}

0 comments on commit 2799a16

Please sign in to comment.