Skip to content

Commit

Permalink
✨ Add initial multisig address page
Browse files Browse the repository at this point in the history
  • Loading branch information
doitian committed Feb 23, 2024
1 parent 7b55b47 commit e8d216b
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 44 deletions.
45 changes: 45 additions & 0 deletions src/AddressPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import DeleteButton from "./components/DeleteButton.js";
import { Button } from "flowbite-react";

export default function AddressPage({ address, deleteAddress, navigate }) {
return (
<section>
<h2 className="text-lg mb-4">
Multisig <code className="break-all">{address.args}</code>
</h2>
<p className="mb-4">
Requiring {address.threshold}{" "}
{address.threshold === 1 ? "signature" : "signatures"} from{" "}
{address.signers.length}{" "}
{address.signers.length === 1 ? "signer" : "signers"}:
</p>

<ol className="mb-4 list-decimal list-outside ml-4">
{address.signers.map((signer, i) => (
<li key={`signer-${i}`} className="leading-6 break-all">
<code>{signer}</code>
</li>
))}
</ol>

{address.required > 0 ? (
<p className="mb-4">
Signatures from the first {address.required}{" "}
{address.required === 1 ? "signer" : "signers"} are required.
</p>
) : null}

<div className="flex flex-row gap-2 flex-wrap">
<Button as="a" href={`#/addresses/duplicate/${address.args}`}>
Duplicate
</Button>
<DeleteButton
onClick={() => {
deleteAddress(address.args);
navigate("#/");
}}
/>
</div>
</section>
);
}
77 changes: 51 additions & 26 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Spinner } from "flowbite-react";
import { Suspense, useCallback, useTransition } from "react";
import { useHash } from "react-use";
import AddressPage from "./AddressPage.js";
import IndexPage from "./IndexPage.js";
import NewAddressPage from "./NewAddressPage.js";
import Layout from "./Layout.js";
import { Spinner } from "flowbite-react";
import NewAddressPage from "./NewAddressPage.js";
import usePersistReducer from "./reducer.js";

function App() {
Expand All @@ -14,7 +15,9 @@ function App() {
);
}

const PREFIX_DUPLICATE_ADDRESS = "#/addresses/duplicate/";
function findAddress(state, args) {
return state.addresses.find((address) => address.args === args);
}

function Router() {
const [page, setPage] = useHash();
Expand All @@ -23,30 +26,39 @@ function Router() {

const navigate = useCallback((url) => startTransition(() => setPage(url)));

let content;
switch (page) {
case "":
case "#/":
content = <IndexPage {...{ navigate, state, deleteAddress }} />;
break;
case "#/addresses/new":
content = <NewAddressPage {...{ navigate, addAddress }} />;
break;
default:
if (page.startsWith(PREFIX_DUPLICATE_ADDRESS)) {
content = (
<NewAddressPage
{...{
navigate,
addAddress,
template: page.substring(PREFIX_DUPLICATE_ADDRESS.length),
}}
/>
const fallbackRoute = () => <NotFound {...{ page, navigate }} />;
const staticRoutes = {
"#/": () => <IndexPage {...{ navigate, state, deleteAddress }} />,
"#/addresses/new": () => <NewAddressPage {...{ navigate, addAddress }} />,
};
staticRoutes[""] = staticRoutes["#/"];
const dynamicRoutes = [
[
"#/addresses/duplicate/",
(args) => (
<NewAddressPage
{...{ navigate, addAddress, template: findAddress(state, args) }}
/>
),
],
[
"#/addresses/",
(args) => {
const address = findAddress(state, args);
return address ? (
<AddressPage {...{ address, deleteAddress, navigate }} />
) : (
fallbackRoute()
);
} else {
content = <NotFound {...{ page, navigate }} />;
}
}
},
],
];
const content = dispatchRoute(
page,
staticRoutes,
dynamicRoutes,
fallbackRoute,
);

return <Layout isPending={isPending}>{content}</Layout>;
}
Expand All @@ -70,4 +82,17 @@ function NotFound({ navigate, page }) {
);
}

function dispatchRoute(page, staticRoutes, dynamicRoutes, fallbackRoute) {
if (page in staticRoutes) {
return staticRoutes[page]();
}
for (const [prefix, creator] of dynamicRoutes) {
if (page.startsWith(prefix)) {
const path = page.slice(prefix.length).split("/");
return creator(...path);
}
}
return fallbackRoute();
}

export default App;
6 changes: 3 additions & 3 deletions src/IndexPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import DeleteButton from "./components/DeleteButton.js";
function AddressesList({ navigate, addresses, deleteAddress }) {
return (
<section>
<h2 className="text-lg border-b-2 leading-8 mb-4">Mulgisig Addresses</h2>
<h2 className="text-lg border-b-2 mb-4">Mulgisig Addresses</h2>
<ul className="mb-4">
{addresses.map((address) => (
<li
key={`address-#{address.args}`}
className="font-mono flex flex-row items-center p-2 hover:bg-slate-100"
className="font-mono flex flex-row gap-2 items-center p-2 hover:bg-slate-100"
>
<a className="grow" href={`#/addresses/${address.args}`}>
<a className="grow break-all" href={`#/addresses/${address.args}`}>
{address.args}
</a>
<DeleteButton onClick={() => deleteAddress(address.args)} />
Expand Down
20 changes: 11 additions & 9 deletions src/Layout.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export default function Layout({ children, isPending }) {
return (
<div className="max-w-5xl mx-auto p-3 md:p-6">
<header
style={{
opacity: isPending ? 0.7 : 1,
}}
className="font-serif text-xl leading-8 mb-8"
>
<a href="#/">CKB Multisig CoBuild PoC</a>
</header>
<main>{children}</main>
<div>
<header
style={{
opacity: isPending ? 0.7 : 1,
}}
className="font-serif text-xl leading-8 mb-8"
>
<a href="#/">CKB Multisig CoBuild PoC</a>
</header>
<main className="box-border">{children}</main>
</div>
</div>
);
}
6 changes: 3 additions & 3 deletions src/NewAddressPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function check(draft, formData) {
}
}

export default function NewAddressPage({ addAddress, navigate }) {
export default function NewAddressPage({ addAddress, navigate, template }) {
const [state, dispatch] = useImmerReducer(
(draft, action) => {
switch (action.type) {
Expand All @@ -64,7 +64,7 @@ export default function NewAddressPage({ addAddress, navigate }) {
},
{
error: null,
data: {
data: template ?? {
args: "",
threshold: 1,
required: 0,
Expand Down Expand Up @@ -93,7 +93,7 @@ export default function NewAddressPage({ addAddress, navigate }) {

return (
<form onSubmit={submit} className="flex flex-col gap-4">
<h2>Add Multisig Address</h2>
<h2 className="text-lg">Add Multisig Address</h2>
<div className="flex flex-row gap-4">
<div className="w-1/2">
<div className="mb-2 block">
Expand Down
6 changes: 3 additions & 3 deletions src/components/DeleteButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button } from "flowbite-react";
import { useState } from "react";
import { HiOutlineTrash, HiOutlineX } from "react-icons/hi";

export default function DeleteButton({ onClick, ...props }) {
export default function DeleteButton({ onClick, className, ...props }) {
const [confirming, setConfirming] = useState(false);

if (!confirming) {
Expand All @@ -12,14 +12,14 @@ export default function DeleteButton({ onClick, ...props }) {
onClick={() => setConfirming(true)}
outline
color="failure"
className="bg-slate-300"
className={`bg-slate-300 ${className}`}
>
<HiOutlineTrash className="w-5 h-5 mr-2" /> Delete
</Button>
);
} else {
return (
<Button.Group outline>
<Button.Group outline className={className}>
<Button {...props} onClick={onClick} color="failure">
<HiOutlineTrash className="h-5 w-5 mr-2" />
Confirm
Expand Down

0 comments on commit e8d216b

Please sign in to comment.