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: add account detail pages header #2538

Merged
merged 18 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions src/app/components/AccountDetailLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import {
CaretLeftIcon,
CrossIcon,
ExportIcon,
} from "@bitcoin-design/bitcoin-icons-react/filled";
import Header from "@components/Header";
import IconButton from "@components/IconButton";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Modal from "react-modal";
import QRCode from "react-qr-code";
import { Outlet, useNavigate, useParams } from "react-router-dom";
import Avatar from "~/app/components/Avatar";
import Loading from "~/app/components/Loading";
import TextField from "~/app/components/form/TextField";
import api, { GetAccountRes } from "~/common/lib/api";
import msg from "~/common/lib/msg";
import { Account } from "~/types";

type AccountAction = Pick<Account, "id" | "name">;

function AccountDetailLayout() {
const navigate = useNavigate();

const { t } = useTranslation("translation", {
keyPrefix: "accounts.account_view",
});
const [account, setAccount] = useState<GetAccountRes>();
const { id } = useParams() as { id: string };
const [exportLoading, setExportLoading] = useState(false);
const [exportModalIsOpen, setExportModalIsOpen] = useState(false);
const [lndHubData, setLndHubData] = useState({
login: "",
password: "",
url: "",
lnAddress: "",
});

useEffect(() => {
(async () => {
const account = await api.getAccount(id);
setAccount(account);
})();
}, [id, navigate]);
reneaaron marked this conversation as resolved.
Show resolved Hide resolved

async function exportAccount({ id, name }: AccountAction) {
reneaaron marked this conversation as resolved.
Show resolved Hide resolved
setExportLoading(true);
setExportModalIsOpen(true);
setLndHubData(
await msg.request("accountDecryptedDetails", {
name,
id,
})
);
setExportLoading(false);
}
function closeExportModal() {
setExportModalIsOpen(false);
}

return (
<>
<Header
title={t("title1")}
headerLeft={
<IconButton
onClick={() => navigate(-1)}
icon={<CaretLeftIcon className="w-4 h-4" />}
/>
}
/>
{account && (
<div className="border-b border-gray-200 dark:border-neutral-500">
<div className="flex-col justify-center p-4 flex items-center bg-white dark:bg-surface-02dp">
<Avatar name={account.id} size={96} />
<div className="flex flex-col overflow-hidden w-full text-center">
<h2
title={account.name}
className="text-xl font-semibold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap leading-1 my-2"
>
{account.name}
</h2>
<div
title={account.connector}
className="text-gray-500 dark:text-gray-400 mb-2 flex justify-center items-center"
>
{account.connector}
{exportAccount && account.connector === "lndhub" && (
<>
<div className="mx-2 font-black text-sm">&middot;</div>
<div
className="text-sm font-medium flex items-center text-gray-500 hover:text-black transition-color duration-200 dark:hover:text-white cursor-pointer"
onClick={() =>
exportAccount({
id: account.id,
name: account.name,
})
}
>
<p>{t("actions.export")}</p>
<ExportIcon className="h-6 w-6" />
</div>
</>
)}
</div>
</div>
</div>
<Modal
ariaHideApp={false}
closeTimeoutMS={200}
isOpen={exportModalIsOpen}
onRequestClose={closeExportModal}
contentLabel={t("export.screen_reader")}
overlayClassName="bg-black bg-opacity-25 fixed inset-0 flex justify-center items-center p-5"
className="rounded-lg bg-white w-full max-w-lg"
>
<div className="p-5 flex justify-between dark:bg-surface-02dp">
<h2 className="text-2xl font-bold dark:text-white">
{t("export.title")}
</h2>
<button onClick={closeExportModal}>
<CrossIcon className="w-6 h-6 dark:text-white" />
</button>
</div>

{exportLoading && (
<div className="p-5 flex justify-center items-center space-x-2 dark:text-white">
<Loading />
<span>{t("export.waiting")}</span>
</div>
)}
{!exportLoading && (
<div className="p-5 border-t border-b border-gray-200 dark:bg-surface-02dp dark:border-neutral-500">
{lndHubData.lnAddress && (
<div className="dark:text-white mb-6">
<p>
<strong>{t("export.your_ln_address")}</strong>
</p>
{lndHubData.lnAddress && <p>{lndHubData.lnAddress}</p>}
</div>
)}
<div className="flex justify-center space-x-3 items-center dark:text-white">
<div className="flex-1">
<p>
<strong>{t("export.tip_mobile")}</strong>
</p>
<p>{t("export.scan_qr")}</p>
</div>
<div className="float-right">
<QRCode
value={`lndhub://${lndHubData.login}:${lndHubData.password}@${lndHubData.url}/`}
level="M"
size={130}
/>
</div>
</div>
<div className="mt-6">
<TextField
id="uri"
label={t("export.export_uri")}
type="text"
readOnly
value={`lndhub://${lndHubData.login}:${lndHubData.password}@${lndHubData.url}/`}
/>
</div>
</div>
)}
</Modal>
</div>
)}
<Outlet />
</>
);
}

export default AccountDetailLayout;
28 changes: 13 additions & 15 deletions src/app/router/Options/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Unlock from "@screens/Unlock";
import { useTranslation } from "react-i18next";
import { HashRouter, Navigate, Outlet, Route, Routes } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import AccountDetailLayout from "~/app/components/AccountDetailLayout";
import ScrollToTop from "~/app/components/ScrollToTop";
import Providers from "~/app/context/Providers";
import RequireAuth from "~/app/router/RequireAuth";
Expand Down Expand Up @@ -80,20 +81,18 @@ function Options() {
<Route path="lnurlAuth" element={<LNURLAuth />} />
<Route path="settings" element={<Settings />} />
<Route path="accounts">
<Route path=":id" element={<AccountDetail />} />
<Route
path=":id/secret-key/backup"
element={<BackupSecretKey />}
/>
<Route
path=":id/secret-key/generate"
element={<GenerateSecretKey />}
/>
<Route
path=":id/secret-key/import"
element={<ImportSecretKey />}
/>
<Route path=":id/nostr" element={<NostrSettings />} />
<Route index element={<Accounts />} />
<Route path=":id" element={<AccountDetailLayout />}>
<Route index element={<AccountDetail />} />
<Route path="secret-key/backup" element={<BackupSecretKey />} />
<Route
path="secret-key/generate"
element={<GenerateSecretKey />}
/>
<Route path="secret-key/import" element={<ImportSecretKey />} />
<Route path="nostr" element={<NostrSettings />} />
</Route>

<Route
path="new"
element={
Expand Down Expand Up @@ -121,7 +120,6 @@ function Options() {
{renderRoutes(connectorRoutes)}
</Route>
</Route>
<Route index element={<Accounts />} />
</Route>
<Route
path="test-connection"
Expand Down
10 changes: 7 additions & 3 deletions src/app/screens/Accounts/BackupSecretKey/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,33 @@ import SecretKeyDescription from "~/app/components/mnemonic/SecretKeyDescription
import api from "~/common/lib/api";

function BackupSecretKey() {
const [mnemonic, setMnemonic] = useState<string | undefined>();
const { t } = useTranslation("translation", {
keyPrefix: "accounts.account_view.mnemonic",
});

const [mnemonic, setMnemonic] = useState<string | undefined>();
const [loading, setLoading] = useState<boolean>(true);

const { id } = useParams();

const fetchData = useCallback(async () => {
try {
setLoading(true);
const accountMnemonic = await api.getMnemonic(id as string);
setMnemonic(accountMnemonic);
} catch (e) {
console.error(e);
if (e instanceof Error) toast.error(`Error: ${e.message}`);
} finally {
setLoading(false);
rolznz marked this conversation as resolved.
Show resolved Hide resolved
}
}, [id]);

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

return !mnemonic ? (
return loading ? (
<div className="flex justify-center mt-5">
<Loading />
</div>
Expand All @@ -43,7 +48,6 @@ function BackupSecretKey() {
{t("backup.title")}
</h1>
<SecretKeyDescription />

<MnemonicInputs mnemonic={mnemonic} readOnly />
</ContentBox>
</Container>
Expand Down
Loading