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: new ui for mnemonic onboarding #2991

Merged
merged 30 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
db61180
feat: new ui for mnemonic onboarding
pavanjoshi914 Jan 12, 2024
67e724e
feat: new mnemonic screen ui
pavanjoshi914 Jan 12, 2024
bb9bd89
chore: code improvement
pavanjoshi914 Jan 19, 2024
2fc0b2f
feat: new design for generate masterkey and import master key page
pavanjoshi914 Jan 29, 2024
dff5d52
feat: shift backup confirmation to the mnemonic inputs
pavanjoshi914 Jan 31, 2024
779cbe8
chore: consistent input field width
pavanjoshi914 Jan 31, 2024
5b8d0fb
Merge branch 'master' into master-key-ui-improvements
reneaaron Jan 31, 2024
fe99762
gMerge remote-tracking branch 'upstream/master' into master-key-ui-im…
pavanjoshi914 Feb 1, 2024
a58e1e1
Merge remote-tracking branch 'upstream/master' into master-key-ui-imp…
pavanjoshi914 Feb 1, 2024
1cba358
feat: change password adornment icon
pavanjoshi914 Feb 1, 2024
ff9cdaa
fix: optimize svg
pavanjoshi914 Feb 10, 2024
85dc717
Merge remote-tracking branch 'upstream/master' into master-key-ui-imp…
pavanjoshi914 Feb 10, 2024
20ca612
chore: icon optmizations
pavanjoshi914 Feb 10, 2024
659cf27
fix: code quality checks
pavanjoshi914 Feb 12, 2024
f475426
fix: replace try / catch
reneaaron Feb 20, 2024
b6710c6
Merge branch 'master' into master-key-ui-improvements
reneaaron Feb 22, 2024
b3558e7
fix: revert images
reneaaron Feb 22, 2024
8a69041
fix: updated images
reneaaron Feb 22, 2024
cc36288
fix: update flex layout
reneaaron Feb 22, 2024
fb01611
fix: cleanup icons
reneaaron Feb 22, 2024
f4e3cd2
Merge branch 'master' into master-key-ui-improvements
reneaaron Feb 22, 2024
38990c1
Merge branch 'master' into master-key-ui-improvements
pavanjoshi914 Feb 23, 2024
a324788
Merge remote-tracking branch 'upstream/master' into master-key-ui-imp…
pavanjoshi914 Feb 23, 2024
9a960c5
feat: set backup parameter from backup screen
pavanjoshi914 Feb 23, 2024
6e51bc4
fix: hover color
pavanjoshi914 Feb 23, 2024
aced5c6
fix: add backup check to account settings
reneaaron Feb 26, 2024
561037b
fix: strict check
reneaaron Feb 26, 2024
8468310
fix: removed backup success message
reneaaron Feb 26, 2024
a533358
Merge branch 'master' into master-key-ui-improvements
reneaaron Feb 27, 2024
f3d16f9
fix: reset translations
reneaaron Feb 27, 2024
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"@lightninglabs/lnc-web": "^0.2.4-alpha",
"@noble/curves": "^1.1.0",
"@noble/secp256k1": "^2.0.0",
"@popicons/react": "^0.0.8",
"@popicons/react": "^0.0.9",
"@scure/bip32": "^1.3.3",
"@scure/bip39": "^1.2.1",
"@tailwindcss/forms": "^0.5.4",
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/ContentBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";

export function ContentBox({ children }: React.PropsWithChildren<object>) {
return (
<div className="mt-6 bg-white rounded-md p-8 border border-gray-200 dark:border-neutral-700 dark:bg-surface-01dp flex flex-col gap-6">
<div className="mt-6 bg-white rounded-2xl p-10 border border-gray-200 dark:border-neutral-700 dark:bg-surface-01dp flex flex-col gap-6">
{children}
</div>
);
Expand Down
12 changes: 6 additions & 6 deletions src/app/components/PasswordViewAdornment/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
HiddenIcon,
VisibleIcon,
} from "@bitcoin-design/bitcoin-icons-react/outline";
import { PopiconsEyeLine } from "@popicons/react";
import { useEffect, useState } from "react";

type Props = {
Expand Down Expand Up @@ -30,9 +27,12 @@ export default function PasswordViewAdornment({ onChange, isRevealed }: Props) {
}}
>
{_isRevealed ? (
<VisibleIcon className="h-6 w-6" />
<img
src="assets/icons/popicons/eye-crossed.svg"
className="h-6 w-6 dark:invert"
/>
) : (
<HiddenIcon className="h-6 w-6" />
<PopiconsEyeLine className="h-5 w-5" />
)}
</button>
);
Expand Down
58 changes: 58 additions & 0 deletions src/app/components/mnemonic/MnemonicBackupDescription/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
PopiconsLifebuoyLine,
PopiconsShieldLine,
PopiconsTriangleExclamationLine,
} from "@popicons/react";
import { useTranslation } from "react-i18next";
import { classNames } from "~/app/utils";

function MnemonicInstructions() {
const { t } = useTranslation("translation", {
keyPrefix: "accounts.account_view.mnemonic",
});

return (
<>
<div className="flex flex-col gap-4">
<ListItem
icon={<PopiconsLifebuoyLine />}
title={t("description.recovery_phrase")}
type="info"
/>
<ListItem
icon={<PopiconsShieldLine />}
title={t("description.secure_recovery_phrase")}
type="info"
/>
<ListItem
icon={<PopiconsTriangleExclamationLine />}
title={t("description.warning")}
type="warn"
/>
</div>
</>
);
}

export default MnemonicInstructions;

type ListItemProps = {
icon: React.ReactNode;
title: string;
type: "warn" | "info";
};

function ListItem({ icon, title, type }: ListItemProps) {
return (
<div
className={classNames(
type == "warn" && "text-orange-700 dark:text-orange-200",
type == "info" && "text-gray-600 dark:text-neutral-400",
"flex gap-2 items-center text-sm"
)}
>
<div className="shrink-0">{icon}</div>
<span>{title}</span>
</div>
);
}
32 changes: 16 additions & 16 deletions src/app/components/mnemonic/MnemonicDescription/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import {
KeyIcon,
MnemonicIcon,
PasswordIcon,
SafeIcon,
} from "@bitcoin-design/bitcoin-icons-react/outline";
import { PopiconsDownloadLine, PopiconsKeyLine } from "@popicons/react";
import { useTranslation } from "react-i18next";
import { useTheme } from "~/app/utils";

function MnemonicDescription() {
const { t } = useTranslation("translation", {
keyPrefix: "accounts.account_view.mnemonic",
});

const theme = useTheme();

return (
<>
<div className="flex flex-col gap-2">
<ListItem icon={<KeyIcon />} title={t("backup.items.keys")} />
<div className="flex flex-col gap-4">
<ListItem icon={<PopiconsKeyLine />} title={t("new.items.keys")} />
<ListItem
icon={<img src={`assets/images/face_surprise_${theme}.png`} />}
title={t("new.items.usage")}
/>
<ListItem
icon={<MnemonicIcon />}
title={t("backup.items.recovery_phrase")}
icon={<PopiconsDownloadLine />}
title={t("new.items.recovery_phrase")}
/>
<ListItem icon={<PasswordIcon />} title={t("backup.items.words")} />
<ListItem icon={<SafeIcon />} title={t("backup.items.storage")} />
</div>
</>
);
Expand All @@ -33,10 +33,10 @@ type ListItemProps = { icon: React.ReactNode; title: string };
function ListItem({ icon, title }: ListItemProps) {
return (
<div className="flex gap-2 items-center">
<div className="shrink-0 w-8 h-8 text-gray-600 dark:text-neutral-400">
{icon}
</div>
<span className="text-gray-600 dark:text-neutral-400">{title}</span>
<div className="shrink-0 text-gray-600 dark:text-neutral-400">{icon}</div>
<span className="text-gray-600 text-sm dark:text-neutral-400">
pavanjoshi914 marked this conversation as resolved.
Show resolved Hide resolved
{title}
</span>
</div>
);
}
32 changes: 28 additions & 4 deletions src/app/components/mnemonic/MnemonicInputs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,38 @@ import { wordlist } from "@scure/bip39/wordlists/english";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import PasswordViewAdornment from "~/app/components/PasswordViewAdornment";
import Checkbox from "~/app/components/form/Checkbox";
import Input from "~/app/components/form/Input";

type MnemonicInputsProps = {
mnemonic?: string;
setMnemonic?(mnemonic: string): void;
readOnly?: boolean;
isConfirmed?: (confirmed: boolean) => void;
};

export default function MnemonicInputs({
mnemonic,
setMnemonic,
readOnly,
children,
isConfirmed,
}: React.PropsWithChildren<MnemonicInputsProps>) {
const { t } = useTranslation("translation", {
keyPrefix: "accounts.account_view.mnemonic",
});
const [revealedIndex, setRevealedIndex] = useState<number | undefined>(
undefined
);
const [hasConfirmedBackup, setHasConfirmedBackup] = useState(false);

const words = mnemonic?.split(" ") || [];
while (words.length < 12) {
words.push("");
}

return (
<div className="border border-gray-200 dark:border-neutral-700 rounded-lg p-6 flex flex-col gap-4 items-center justify-center">
<div className="border border-gray-200 dark:border-neutral-700 rounded-2xl p-6 flex flex-col gap-4 items-center justify-center">
<h3 className="text-lg font-semibold dark:text-white">
{t("inputs.title")}
</h3>
Expand All @@ -39,18 +43,18 @@ export default function MnemonicInputs({
const inputId = `mnemonic-word-${i}`;
return (
<div key={i} className="flex justify-center items-center gap-2">
<span className="text-gray-600 dark:text-neutral-400 text-right">
<span className="flex w-6 text-gray-600 dark:text-neutral-400">
{i + 1}.
</span>
<div className="relative">
<div className="flex w-full">
<Input
id={inputId}
autoFocus={!readOnly && i === 0}
onFocus={() => setRevealedIndex(i)}
onBlur={() => setRevealedIndex(undefined)}
readOnly={readOnly}
block={false}
className="w-32 text-center"
className="w-full text-center"
list={readOnly ? undefined : "wordlist"}
value={isRevealed ? word : word.length ? "•••••" : ""}
onChange={(e) => {
Expand Down Expand Up @@ -89,6 +93,26 @@ export default function MnemonicInputs({
</datalist>
)}
{children}

{isConfirmed && (
<div className="flex items-center justify-center mt-4">
<Checkbox
id="has_backed_up"
name="Backup confirmation checkbox"
checked={hasConfirmedBackup}
onChange={(event) => {
setHasConfirmedBackup(event.target.checked);
if (isConfirmed) isConfirmed(event.target.checked);
}}
/>
<label
htmlFor="has_backed_up"
className="cursor-pointer ml-2 block text-sm text-gray-900 font-medium dark:text-white"
>
{t("confirm")}
</label>
</div>
)}
</div>
);
}
39 changes: 30 additions & 9 deletions src/app/screens/Accounts/BackupMnemonic/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useNavigate, useParams } from "react-router-dom";
import Button from "~/app/components/Button";
import { ContentBox } from "~/app/components/ContentBox";
import toast from "~/app/components/Toast";
import MnemonicDescription from "~/app/components/mnemonic/MnemonicDescription";
import MnemonicInstructions from "~/app/components/mnemonic/MnemonicBackupDescription";
import MnemonicInputs from "~/app/components/mnemonic/MnemonicInputs";
import api from "~/common/lib/api";

Expand All @@ -19,6 +19,7 @@ function BackupMnemonic() {

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

const { id } = useParams();

Expand All @@ -38,6 +39,17 @@ function BackupMnemonic() {
fetchData();
}, [fetchData]);

async function completeBackupProcess() {
try {
if (!hasConfirmedBackup) {
throw new Error(t("error_confirm"));
}
navigate(-1);
} catch (e) {
if (e instanceof Error) toast.error(e.message);
}
}

return loading ? (
<div className="flex justify-center mt-5">
<Loading />
Expand All @@ -49,15 +61,24 @@ function BackupMnemonic() {
<h1 className="font-bold text-2xl dark:text-white">
{t("backup.title")}
</h1>
<MnemonicDescription />
<MnemonicInputs mnemonic={mnemonic} readOnly />
</ContentBox>
<div className="flex justify-center my-6 gap-4">
<Button
label={tCommon("actions.back")}
onClick={() => navigate(-1)}
<MnemonicInstructions />
<MnemonicInputs
mnemonic={mnemonic}
readOnly
isConfirmed={(hasConfirmedBackup) => {
setHasConfirmedBackup(hasConfirmedBackup);
}}
/>
</div>

<div className="flex justify-center mt-6 w-64 mx-auto">
<Button
label={tCommon("actions.finish")}
primary
flex
onClick={completeBackupProcess}
/>
</div>
</ContentBox>
</Container>
</div>
);
Expand Down
42 changes: 17 additions & 25 deletions src/app/screens/Accounts/GenerateMnemonic/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Alert from "~/app/components/Alert";
import Button from "~/app/components/Button";
import { ContentBox } from "~/app/components/ContentBox";
import toast from "~/app/components/Toast";
import Checkbox from "~/app/components/form/Checkbox";
import MnemonicInstructions from "~/app/components/mnemonic/MnemonicBackupDescription";
import MnemonicInputs from "~/app/components/mnemonic/MnemonicInputs";
import api from "~/common/lib/api";

Expand Down Expand Up @@ -46,7 +46,7 @@ function GenerateMnemonic() {
async function saveGeneratedSecretKey() {
try {
if (!hasConfirmedBackup) {
throw new Error(t("generate.error_confirm"));
throw new Error(t("error_confirm"));
}
if (!mnemonic) {
throw new Error("No mnemonic available");
Expand Down Expand Up @@ -79,36 +79,28 @@ function GenerateMnemonic() {
<h1 className="font-bold text-2xl dark:text-white">
{t("generate.title")}
</h1>

<MnemonicInstructions />
{hasNostrPrivateKey && (
<Alert type="info">{t("existing_nostr_key_notice")}</Alert>
)}
<MnemonicInputs mnemonic={mnemonic} readOnly></MnemonicInputs>
<MnemonicInputs
mnemonic={mnemonic}
readOnly
isConfirmed={(hasConfirmedBackup) => {
setHasConfirmedBackup(hasConfirmedBackup);
}}
></MnemonicInputs>

<div className="flex items-center justify-center">
<Checkbox
id="has_backed_up"
name="Backup confirmation checkbox"
checked={hasConfirmedBackup}
onChange={(event) => {
setHasConfirmedBackup(event.target.checked);
}}
<div className="flex justify-center mt-6 gap-4">
<Button label={tCommon("actions.cancel")} onClick={cancel} />
<Button
label={t("backup.save")}
primary
onClick={saveGeneratedSecretKey}
/>
<label
htmlFor="has_backed_up"
className="cursor-pointer ml-2 block text-sm text-gray-900 font-medium dark:text-white"
>
{t("generate.confirm")}
</label>
</div>
</ContentBox>
<div className="flex justify-center mt-8 mb-16 gap-4">
<Button label={tCommon("actions.cancel")} onClick={cancel} />
<Button
label={t("backup.save")}
primary
onClick={saveGeneratedSecretKey}
/>
</div>
</Container>
</div>
);
Expand Down
Loading
Loading