Skip to content

Commit

Permalink
modal in works, before delete modal
Browse files Browse the repository at this point in the history
  • Loading branch information
phipsae committed Dec 2, 2023
1 parent 2f93eff commit 972a357
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 18 deletions.
55 changes: 55 additions & 0 deletions packages/nextjs/components/scaffold-eth/AddressPrivateKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";

type TAddressProps = {
address?: string;
disableAddressLink?: boolean;
format?: "short" | "long";
size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl";
};

export const AddressPrivateKey = ({ address, disableAddressLink, format, size = "base" }: TAddressProps) => {
const [addressCopied, setAddressCopied] = useState(false);

let displayAddress = address?.slice(0, 5) + "..." + address?.slice(-4);

if (format === "long" && address) {
displayAddress = address;
}

return (
<>
<div className="flex items-center">
{disableAddressLink ? (
<span className={`ml-1.5 text-${size} font-normal`}>{displayAddress}</span>
) : (
<div className="ml-1.5 font-normal">{displayAddress}</div>
)}
{addressCopied ? (
<CheckCircleIcon
className="ml-1.5 text-xl font-normal text-sky-600 h-5 w-5 cursor-pointer"
aria-hidden="true"
/>
) : (
address && (
<CopyToClipboard
text={address}
onCopy={() => {
setAddressCopied(true);
setTimeout(() => {
setAddressCopied(false);
}, 800);
}}
>
<DocumentDuplicateIcon
className="ml-1.5 text-xl font-normal text-sky-600 h-5 w-5 cursor-pointer"
aria-hidden="true"
/>
</CopyToClipboard>
)
)}
</div>
</>
);
};
142 changes: 142 additions & 0 deletions packages/nextjs/components/wallet/AccountSwitcher/AccountSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useCallback, useEffect, useState } from "react";
import { DeleteAccount } from "./DeleteAccount";
import { PrivateKeyAccount, generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { AddressAdapted } from "~~/components/scaffold-eth/AddressAdapted";
import { AddressPrivateKey } from "~~/components/scaffold-eth/AddressPrivateKey";
import { useSharedState } from "~~/sharedStateContext";

interface AccountWithPrivateKey {
account: PrivateKeyAccount;
privateKey: string;
}

export const AccountSwitcher = () => {
// const [accounts, setAccounts] = useState<AccountWithPrivateKey[]>([]);
// const [selectedAccount, setSelectedAccount] = useState<PrivateKeyAccount>();
const { accounts, setAccounts, selectedAccount, setSelectedAccount } = useSharedState();
const [revealedPrivateKey, setRevealedPrivateKey] = useState("");
const [showPrivateKey, setShowPrivateKey] = useState(false);

const generateAccount = useCallback(() => {
const privateKey = generatePrivateKey();
const newAccount = privateKeyToAccount(privateKey);

const newAccountWithKey: AccountWithPrivateKey = {
account: newAccount,
privateKey: privateKey,
};
setAccounts([...accounts, newAccountWithKey]);
return newAccountWithKey;
}, [accounts, setAccounts]);

const handleRowClick = (account: PrivateKeyAccount, privateKey: string) => {
setSelectedAccount(account);
setRevealedPrivateKey(privateKey);
console.log("selected Account", selectedAccount);
};

const togglePrivateKey = () => {
setShowPrivateKey(!showPrivateKey);
};

useEffect(() => {
if (accounts.length === 0) {
// Generate the initial account
const initialAccount = generateAccount();

// Set the initial account as the selected account
setSelectedAccount(initialAccount.account);

// Set the private key of the initial account
setRevealedPrivateKey(initialAccount.privateKey);
}
}, [accounts, generateAccount, setSelectedAccount]); // Dependency array includes 'accounts'

useEffect(() => {
// Set 'showPrivateKey' to false whenever 'selectedAccount' changes
setShowPrivateKey(false);
}, [selectedAccount]);

const openModal = () => {
const modal = document.getElementById("my_modal_2") as HTMLDialogElement | null;
if (modal) {
modal.showModal();
} else {
console.error("Modal element not found!");
}
};

return (
<>
<div className="overflow-x-auto">
<table className="table w-full">
{/* head */}
<thead>
<tr>
<th>#</th>
<th>Account</th>
<th>Delete</th>
</tr>
</thead>
</table>
<div style={{ maxHeight: "300px", overflowY: "auto" }}>
<table className="table w-full ">
<tbody>
{/* row */}
{accounts.map(account => (
<tr
key={account.account.address}
className={`cursor-pointer ${
selectedAccount?.address === account.account.address ? "bg-gray-100 font-bold" : "bg-base-200"
}`}
>
<th>
<input
type="checkbox"
className="checkbox"
checked={account.account.address === selectedAccount?.address}
onChange={() => handleRowClick(account.account, account.privateKey)}
/>
</th>
<td>
{/* {account.account.address?.slice(0, 5) + "..." + account.account.address?.slice(-4)} */}
<AddressAdapted address={account.account.address} format="short" />
</td>
<td>
<button className="btn" onClick={() => openModal()}>
open modal
</button>
<DeleteAccount address={account.account.address} />
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<button
className="btn btn-primary h-[2.2rem] min-h-[2.2rem] mt-3 w-full"
onClick={() => {
generateAccount();
console.log(accounts);
}}
>
Generate new account
</button>
<button
className="btn btn-error h-[2.2rem] min-h-[2.2rem] w-full mt-3"
onClick={() => {
togglePrivateKey();
}}
>
{showPrivateKey ? "Hide private key" : "Reveal private key"}
</button>
{showPrivateKey && (
<div className="flex flex-row justify-center mt-5">
Private Key:
<AddressPrivateKey address={revealedPrivateKey} />
</div>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useSharedState } from "~~/sharedStateContext";

interface DeleteAccountProps {
address: string;
}

export const DeleteAccount = ({ address }: DeleteAccountProps) => {
const { accounts, setAccounts } = useSharedState();

const deleteAccount = (address: string) => {
const filteredAccounts = accounts.filter(acc => acc.account.address !== address);
setAccounts(filteredAccounts);
};
return (
<>
<dialog id="my_modal_2" className="modal">
<div className="modal-box">
<h3 className="font-bold text-lg">Hello!</h3>
<p className="py-4">Press ESC key or click the button below to close</p>
<div className="modal-action">
<form method="dialog">
<button
className="btn btn-error"
onClick={() => {
deleteAccount(address);
}}
>
Delete
</button>

<button className="btn">Close</button>
</form>
</div>
</div>
</dialog>
</>
);
};
Empty file.
73 changes: 58 additions & 15 deletions packages/nextjs/pages/wallet.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { NextPage } from "next";
import { privateKeyToAccount } from "viem/accounts";
import { ExclamationTriangleIcon } from "@heroicons/react/20/solid";
import { AccountSwitcher } from "~~/components/wallet/AccountSwitcher/AccountSwitcher";
import { NetworkMenu } from "~~/components/wallet/NetworkMenu";
import { TokenOverview } from "~~/components/wallet/TokenOverview/TokenOverview";
import { SelectedTokenTransaction } from "~~/components/wallet/Transaction/SelectedTokenTransaction";
Expand All @@ -8,27 +10,68 @@ import { useSharedState } from "~~/sharedStateContext";

const Wallet: NextPage = () => {
const account = privateKeyToAccount(`0x${process.env.NEXT_PUBLIC_PRIVATE_KEY_WALLET}`);
const { selectedChain, selectedTokenAddress, selectedTokenName, selectedTokenImage } = useSharedState();
const { selectedChain, selectedTokenAddress, selectedTokenName, selectedTokenImage, selectedAccount } =
useSharedState();

const openModal = (modalName: string) => {
const modal = document.getElementById(modalName) as HTMLDialogElement | null;
if (modal) {
modal.showModal();
} else {
console.error("Modal element not found!");
}
};

return (
<>
<div className="container mx-auto flex flex-col mt-5">
<button className="btn" onClick={() => openModal("account_switcher")}>
open modal
</button>
<dialog id="account_switcher" className="modal">
<div className="modal-box">
<h3 className="font-bold text-lg">
<AccountSwitcher />
</h3>
<div className="py-4">
<div className="flex flex-row items-start gap-5">
<div>
<ExclamationTriangleIcon className="h-20 w-20 text-start" />
</div>
<div>
<span className="font-bold">
Warning: Never disclose your private key. Anyone with your private keys can steal any assets held in
your account.
</span>
</div>
</div>
</div>
<div className="modal-action">
<form method="dialog">
<button className="btn">Close</button>
</form>
</div>
</div>
</dialog>

<NetworkMenu />
<div className="flex flex-row gap-5">
<div className="flex flex-col flex-1 mt-5 border p-5">
<WalletOverview
account={account}
chain={selectedChain}
tokenAddress={selectedTokenAddress}
tokenName={selectedTokenName}
tokenImage={selectedTokenImage}
/>
<SelectedTokenTransaction
account={account}
networkName={selectedChain}
tokenAddress={selectedTokenAddress}
/>
</div>
{selectedAccount && (
<div className="flex flex-col flex-1 mt-5 border p-5">
<WalletOverview
account={selectedAccount}
chain={selectedChain}
tokenAddress={selectedTokenAddress}
tokenName={selectedTokenName}
tokenImage={selectedTokenImage}
/>
<SelectedTokenTransaction
account={account}
networkName={selectedChain}
tokenAddress={selectedTokenAddress}
/>
</div>
)}
<div className="flex flex-col flex-1 mt-5 border p-5 ">
<div className="text-center mb-5">
<span className="block text-2xl font-bold">💸 Token Overview</span>
Expand Down
20 changes: 17 additions & 3 deletions packages/nextjs/sharedStateContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ReactNode, createContext, useContext, useState } from "react";
import { PrivateKeyAccount } from "viem";

interface SharedStateContextProps {
selectedChain: string;
Expand All @@ -13,6 +14,10 @@ interface SharedStateContextProps {
setSelectedBlockExplorer: (value: string) => void;
isConfirmed: boolean;
setIsConfirmed: (value: boolean) => void;
accounts: AccountWithPrivateKey[];
setAccounts: (value: AccountWithPrivateKey[]) => void;
selectedAccount: PrivateKeyAccount | undefined;
setSelectedAccount: (value: PrivateKeyAccount) => void;
}

const SharedStateContext = createContext<SharedStateContextProps | undefined>(undefined);
Expand All @@ -22,10 +27,10 @@ export const SharedStateProvider: React.FC<{ children: ReactNode }> = ({ childre
const [selectedTokenAddress, setSelectedTokenAddress] = useState<string>("nativeToken");
const [selectedTokenName, setSelectedTokenName] = useState<string>("ETH");
const [selectedTokenImage, setSelectedTokenImage] = useState<string>("/ETH.png");
const [selectedBlockExplorer, setSelectedBlockExplorer] = useState<string>("/ETH.png");
const [selectedBlockExplorer, setSelectedBlockExplorer] = useState<string>("https://etherscan.io/");
const [isConfirmed, setIsConfirmed] = useState<boolean>(false);

// "https://etherscan.io/"
const [accounts, setAccounts] = useState<AccountWithPrivateKey[]>([]);
const [selectedAccount, setSelectedAccount] = useState<PrivateKeyAccount>();

return (
<SharedStateContext.Provider
Expand All @@ -42,6 +47,10 @@ export const SharedStateProvider: React.FC<{ children: ReactNode }> = ({ childre
setSelectedTokenImage,
selectedBlockExplorer,
setSelectedBlockExplorer,
accounts,
setAccounts,
selectedAccount,
setSelectedAccount,
}}
>
{children}
Expand All @@ -56,3 +65,8 @@ export const useSharedState = () => {
}
return context;
};

interface AccountWithPrivateKey {
account: PrivateKeyAccount;
privateKey: string;
}

0 comments on commit 972a357

Please sign in to comment.