Skip to content

Commit

Permalink
refactor(dashboard-for-dapps): extract common code
Browse files Browse the repository at this point in the history
  • Loading branch information
ditoglez committed Dec 17, 2024
1 parent e28b231 commit 58a6640
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 189 deletions.
1 change: 1 addition & 0 deletions apps/dashboard-for-dapps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"dependencies": {
"@chakra-ui/react": "^3.0.2",
"@idos-network/codecs": "workspace:*",
"@idos-network/idos-sdk": "workspace:*",
"@idos-network/ui-kit": "workspace:*",
"@stablelib/base64": "^1.0.1",
Expand Down
Empty file.
63 changes: 63 additions & 0 deletions apps/dashboard-for-dapps/src/components/secret-key-prompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Stack } from "@chakra-ui/react";
import {
Button,
DialogActionTrigger,
DialogBody,
DialogCloseTrigger,
DialogContent,
DialogFooter,
DialogHeader,
DialogRoot,
DialogTitle,
Field,
PasswordInput,
} from "@idos-network/ui-kit";
import { useRef, useState } from "react";

export function SecretKeyPrompt({
open,
toggle,
onSubmit,
}: {
open: boolean;
toggle: (value?: boolean) => void;
onSubmit: (key: string) => void;
}) {
const ref = useRef<HTMLInputElement>(null);
const [key, setKey] = useState("");

const handleSave = () => {
onSubmit(key);
toggle(false);
};

return (
<DialogRoot
open={open}
placement="center"
onOpenChange={() => {
toggle(false);
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Enter your secret key</DialogTitle>
</DialogHeader>
<DialogBody>
<Stack gap="4">
<Field label="Secret key:">
<PasswordInput ref={ref} onChange={(e) => setKey(e.target.value)} />
</Field>
</Stack>
</DialogBody>
<DialogFooter>
<DialogActionTrigger asChild>
<Button variant="outline">Cancel</Button>
</DialogActionTrigger>
<Button onClick={handleSave}>Save</Button>
</DialogFooter>
<DialogCloseTrigger />
</DialogContent>
</DialogRoot>
);
}
6 changes: 6 additions & 0 deletions apps/dashboard-for-dapps/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useLocalStorage } from "@uidotdev/usehooks";

export const useSecretKey = () => {
const [secretKey, setSecretKey] = useLocalStorage("SECRET_KEY", "");
return [secretKey, setSecretKey] as const;
};
161 changes: 22 additions & 139 deletions apps/dashboard-for-dapps/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,13 @@ import {
Spinner,
Stack,
Text,
chakra,
} from "@chakra-ui/react";
import type { idOSCredential } from "@idos-network/idos-sdk";
import {
Button,
DataListItem,
DataListRoot,
DialogActionTrigger,
DialogBody,
DialogCloseTrigger,
DialogContent,
DialogFooter,
DialogHeader,
DialogRoot,
DialogTitle,
DrawerActionTrigger,
DrawerBackdrop,
DrawerCloseTrigger,
Expand All @@ -31,23 +24,20 @@ import {
DrawerHeader,
DrawerRoot,
EmptyState,
Field,
PasswordInput,
RefreshButton,
SearchField,
} from "@idos-network/ui-kit";
import * as Base64Codec from "@stablelib/base64";
import * as Utf8Codec from "@stablelib/utf8";
import { skipToken, useQuery } from "@tanstack/react-query";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useDebounce, useLocalStorage, useToggle } from "@uidotdev/usehooks";
import ascii85 from "ascii85";
import { useDebounce, useToggle } from "@uidotdev/usehooks";
import { matchSorter } from "match-sorter";
import { useMemo, useRef, useState } from "react";
import nacl from "tweetnacl";
import { useMemo, useState } from "react";
import { useAccount } from "wagmi";

import { SecretKeyPrompt } from "@/components/secret-key-prompt";
import { useSecretKey } from "@/hooks";
import { useIdOS } from "@/idOS.provider";
import { changeCase, decrypt, openImageInNewTab } from "@/utils";

export const Route = createFileRoute("/")({
component: Index,
Expand All @@ -58,14 +48,6 @@ export const Route = createFileRoute("/")({
},
});

function transformBase85Image(src: string) {
const prefix = "data:image/jpeg;base85,";

return `data:image/png;base64,${Base64Codec.encode(
ascii85.decode(src.substring(prefix.length)),
)}`;
}

const useFetchGrants = () => {
const idOS = useIdOS();
const { address } = useAccount();
Expand All @@ -89,6 +71,7 @@ const useFetchGrants = () => {
})),
});
};

type GrantsWithFormattedLockedUntil = NonNullable<ReturnType<typeof useFetchGrants>["data"]>;

const useFetchCredential = (id: string) => {
Expand All @@ -99,115 +82,13 @@ const useFetchCredential = (id: string) => {
});
};

function decrypt(b64FullMessage: string, b64SenderPublicKey: string, secretKey: string) {
const fullMessage = Base64Codec.decode(b64FullMessage);
const senderPublicKey = Base64Codec.decode(b64SenderPublicKey);

const nonce = fullMessage.slice(0, nacl.box.nonceLength);
const message = fullMessage.slice(nacl.box.nonceLength, fullMessage.length);

const decrypted = nacl.box.open(message, nonce, senderPublicKey, Base64Codec.decode(secretKey));

if (decrypted == null) {
return "";
}

return Utf8Codec.decode(decrypted);
}

function SecretKeyPrompt({
open,
toggle,
onSubmit,
}: {
open: boolean;
toggle: (value?: boolean) => void;
onSubmit: (key: string) => void;
}) {
const ref = useRef<HTMLInputElement>(null);
const [key, setKey] = useState("");

const handleSave = () => {
onSubmit(key);
toggle(false);
};

return (
<DialogRoot
open={open}
placement="center"
onOpenChange={() => {
toggle(false);
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Enter your secret key</DialogTitle>
</DialogHeader>
<DialogBody>
<Stack gap="4">
<Field label="Secret key:">
<PasswordInput ref={ref} onChange={(e) => setKey(e.target.value)} />
</Field>
</Stack>
</DialogBody>
<DialogFooter>
<DialogActionTrigger asChild>
<Button variant="outline">Cancel</Button>
</DialogActionTrigger>
<Button onClick={handleSave}>Save</Button>
</DialogFooter>
<DialogCloseTrigger />
</DialogContent>
</DialogRoot>
);
}

function openImageInNewTab(base64Image: string) {
const newWindow = window.open();

if (newWindow) {
newWindow.document.write(`
<html>
<head>
<title>Document Image</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #000;
}
img {
max-width: 100%;
max-height: 100vh;
object-fit: contain;
}
</style>
</head>
<body>
<img src="${base64Image}" alt="Document Image">
</body>
</html>
`);
newWindow.document.close();
}
}

function changeCase(str: string) {
return str.replace(/_/g, " ");
}

function CredentialDetails({
credentialId,
open,
toggle,
}: { credentialId: string; open: boolean; toggle: (value?: boolean) => void }) {
const credential = useFetchCredential(credentialId);
const [secretKey] = useLocalStorage("SECRET_KEY", "");
const [secretKey] = useSecretKey();

if (!credential.data || !secretKey) return null;

Expand All @@ -225,7 +106,7 @@ function CredentialDetails({
string,
string,
][]
).map(([key, value]) => [key, transformBase85Image(value)]);
).map(([key, value]) => [key, value]);

return (
<DrawerRoot
Expand Down Expand Up @@ -331,21 +212,22 @@ function CredentialDetails({
<List.Item
flexShrink="0"
key={key}
role="button"
transition="transform 0.2s"
cursor="pointer"
_hover={{ transform: "scale(1.02)" }}
onClick={() => openImageInNewTab(value)}
>
<Image
src={value}
alt="Identification document front"
rounded="md"
loading="lazy"
width="120px"
height="120px"
title="Click to open the image in full size"
/>
<chakra.button className="button">
<Image
src={value}
alt="Identification document front"
rounded="md"
loading="lazy"
width="120px"
height="120px"
title="Click to open the image in full size"
/>
</chakra.button>
</List.Item>
))}
</List.Root>
Expand All @@ -370,7 +252,7 @@ function SearchResults({ results }: { results: GrantsWithFormattedLockedUntil })
const [credentialId, setCredentialId] = useState("");
const [openSecretKeyPrompt, toggleSecretKeyPrompt] = useToggle();
const [openCredentialDetails, toggleCredentialDetails] = useToggle();
const [secretKey, setSecretKey] = useLocalStorage("SECRET_KEY", "");
const [secretKey, setSecretKey] = useSecretKey();

if (!results.length) {
return <EmptyState title="No results found" bg="gray.900" rounded="lg" />;
Expand Down Expand Up @@ -533,6 +415,7 @@ function Index() {
})
}
/>

<RefreshButton
aria-label="Refresh grants list"
title="Refresh grants list"
Expand Down
56 changes: 56 additions & 0 deletions apps/dashboard-for-dapps/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { base64Decode, utf8Decode } from "@idos-network/codecs";
import nacl from "tweetnacl";

export function decrypt(b64FullMessage: string, b64SenderPublicKey: string, secretKey: string) {
const fullMessage = base64Decode(b64FullMessage);
const senderPublicKey = base64Decode(b64SenderPublicKey);

const nonce = fullMessage.slice(0, nacl.box.nonceLength);
const message = fullMessage.slice(nacl.box.nonceLength, fullMessage.length);

const decrypted = nacl.box.open(message, nonce, senderPublicKey, base64Decode(secretKey));

if (decrypted == null) {
return "";
}

return utf8Decode(decrypted);
}

export function openImageInNewTab(base64Image: string) {
const newWindow = window.open();

if (newWindow) {
newWindow.document.write(`
<html>
<head>
<title>Document Image</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #000;
}
img {
max-width: 100%;
max-height: 100vh;
object-fit: contain;
}
</style>
</head>
<body>
<img src="${base64Image}" alt="Document Image">
</body>
</html>
`);
newWindow.document.close();
}
}

export function changeCase(str: string) {
return str.replace(/_/g, " ");
}
Loading

0 comments on commit 58a6640

Please sign in to comment.