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 all 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 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: 5 additions & 7 deletions src/app/components/PasswordViewAdornment/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {
HiddenIcon,
VisibleIcon,
} from "@bitcoin-design/bitcoin-icons-react/outline";
import { PopiconsEyeLine } from "@popicons/react";
import { useEffect, useState } from "react";
import EyeCrossedIcon from "~/app/icons/EyeCrossedIcon";

type Props = {
onChange: (viewingPassword: boolean) => void;
Expand All @@ -23,16 +21,16 @@ export default function PasswordViewAdornment({ onChange, isRevealed }: Props) {
<button
type="button"
tabIndex={-1}
className="flex justify-center items-center w-10 h-8"
className="flex justify-center items-center w-10 h-8 text-gray-400 dark:text-neutral-600 hover:text-gray-600 hover:dark:text-neutral-400"
onClick={() => {
setRevealed(!_isRevealed);
onChange(!_isRevealed);
}}
>
{_isRevealed ? (
<VisibleIcon className="h-6 w-6" />
<EyeCrossedIcon className="h-6 w-6" />
) : (
<HiddenIcon className="h-6 w-6" />
<PopiconsEyeLine className="h-6 w-6" />
)}
</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>
);
}
27 changes: 10 additions & 17 deletions src/app/components/mnemonic/MnemonicDescription/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import {
KeyIcon,
MnemonicIcon,
PasswordIcon,
SafeIcon,
} from "@bitcoin-design/bitcoin-icons-react/outline";
import { PopiconsDownloadLine, PopiconsKeyLine } from "@popicons/react";
import { useTranslation } from "react-i18next";
import FaceSurpriseIcon from "~/app/icons/FaceSurpriseIcon";

function MnemonicDescription() {
const { t } = useTranslation("translation", {
Expand All @@ -13,14 +9,13 @@ function MnemonicDescription() {

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={<FaceSurpriseIcon />} 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 @@ -32,11 +27,9 @@ 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="flex gap-2 items-center text-gray-600 dark:text-neutral-400 text-sm">
<div className="shrink-0">{icon}</div>
<span>{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,26 +2,30 @@ 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) {
Expand All @@ -32,7 +36,7 @@ export default function MnemonicInputs({
}

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 @@ -42,18 +46,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="w-6 text-gray-600 dark:text-neutral-400">
{i + 1}.
</span>
<div className="relative">
<div className="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 @@ -92,6 +96,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>
);
}
27 changes: 27 additions & 0 deletions src/app/icons/EyeCrossedIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SVGProps } from "react";

const EyeCrossedIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
width={20}
height={20}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M10 6.21314C8.8144 6.21314 7.83961 6.61698 7.16667 7.34639C6.50383 8.06485 6.20655 9.01986 6.20655 10C6.20655 10.9801 6.50383 11.9352 7.16667 12.6536C7.83961 13.383 8.8144 13.7869 10 13.7869C11.1856 13.7869 12.1604 13.383 12.8333 12.6536C13.4962 11.9352 13.7934 10.9801 13.7934 10C13.7934 9.01986 13.4962 8.06485 12.8333 7.34639C12.1604 6.61698 11.1856 6.21314 10 6.21314ZM8.36461 8.44777C8.68478 8.10074 9.19961 7.83891 10 7.83891C10.8004 7.83891 11.3152 8.10074 11.6354 8.44777C11.9657 8.80575 12.1648 9.33776 12.1648 10C12.1648 10.6622 11.9657 11.1943 11.6354 11.5522C11.3152 11.8993 10.8004 12.1611 10 12.1611C9.19961 12.1611 8.68478 11.8993 8.36461 11.5522C8.03435 11.1943 7.83515 10.6622 7.83515 10C7.83515 9.33776 8.03435 8.80575 8.36461 8.44777Z"
fill="currentColor"
/>
<path
d="M10 2C6.11297 2 3.58698 3.31971 2.04049 5.02671C0.5183 6.7069 0 8.70232 0 10C0 11.2977 0.5183 13.2931 2.04049 14.9733C3.58698 16.6803 6.11297 18 10 18C13.887 18 16.413 16.6803 17.9595 14.9733C19.4817 13.2931 20 11.2977 20 10C20 8.70232 19.4817 6.7069 17.9595 5.02671C16.413 3.31971 13.887 2 10 2ZM3.17925 6.02303C4.40408 4.67107 6.49692 3.49832 10 3.49832C13.5031 3.49832 15.5959 4.67107 16.8208 6.02303C18.0699 7.4018 18.4753 9.03181 18.4753 10C18.4753 10.9682 18.0699 12.5982 16.8208 13.977C15.5959 15.3289 13.5031 16.5017 10 16.5017C6.49692 16.5017 4.40408 15.3289 3.17925 13.977C1.93013 12.5982 1.52468 10.9682 1.52468 10C1.52468 9.03181 1.93013 7.4018 3.17925 6.02303Z"
fill="currentColor"
/>
<path
d="M18.0474 18.291C18.2636 18.0756 18.3077 17.7531 18.1796 17.4939L3.78294 2.99892L2.99024 2.20361C2.72032 1.93281 2.28193 1.93203 2.01105 2.20187C1.74017 2.4717 1.73938 2.90998 2.0093 3.18078L17.0682 18.2892C17.3381 18.56 17.7765 18.5608 18.0474 18.291Z"
fill="currentColor"
/>
</svg>
);

export default EyeCrossedIcon;
32 changes: 32 additions & 0 deletions src/app/icons/FaceSurpriseIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { SVGProps } from "react";

const FaceSurpriseIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
width={20}
height={20}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19C14.9706 19 19 14.9706 19 10C19 5.02944 14.9706 1 10 1ZM2.51285 10C2.51285 5.86496 5.86496 2.51285 10 2.51285C14.135 2.51285 17.4872 5.86496 17.4872 10C17.4872 14.135 14.135 17.4872 10 17.4872C5.86496 17.4872 2.51285 14.135 2.51285 10Z"
fill="currentColor"
/>
<path
d="M12 13C12 14.1046 11.1046 15 10 15C8.89543 15 8 14.1046 8 13C8 11.8954 8.89543 11 10 11C11.1046 11 12 11.8954 12 13Z"
fill="currentColor"
/>
<path
d="M8.46333 8.38462C8.46333 8.9379 8.01481 9.38643 7.46152 9.38643C6.90824 9.38643 6.45972 8.9379 6.45972 8.38462C6.45972 7.83134 6.90824 7.38281 7.46152 7.38281C8.01481 7.38281 8.46333 7.83134 8.46333 8.38462Z"
fill="currentColor"
/>
<path
d="M13.5391 8.38462C13.5391 8.93722 13.0911 9.38519 12.5385 9.38519C11.9859 9.38519 11.5379 8.93722 11.5379 8.38462C11.5379 7.83202 11.9859 7.38405 12.5385 7.38405C13.0911 7.38405 13.5391 7.83202 13.5391 8.38462Z"
fill="currentColor"
/>
</svg>
);
export default FaceSurpriseIcon;
44 changes: 33 additions & 11 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,8 +19,9 @@ function BackupMnemonic() {

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

const { id } = useParams();
const { id } = useParams() as { id: string };

const fetchData = useCallback(async () => {
try {
Expand All @@ -38,7 +39,19 @@ function BackupMnemonic() {
fetchData();
}, [fetchData]);

// TODO: set isMnemonicBackupDone, once new ui for screen is merged
async function completeBackupProcess() {
if (!hasConfirmedBackup) {
toast.error(t("error_confirm"));
return false;
}

await api.editAccount(id, {
isMnemonicBackupDone: true,
});

navigate(`/accounts/${id}`);
}

return loading ? (
<div className="flex justify-center mt-5">
<Loading />
Expand All @@ -50,15 +63,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
4 changes: 3 additions & 1 deletion src/app/screens/Accounts/Detail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ function AccountDetail() {
const [account, setAccount] = useState<GetAccountRes | null>(null);
const [accountName, setAccountName] = useState("");
const [hasMnemonic, setHasMnemonic] = useState(false);
const [isMnemonicBackupDone, setIsMnemonicBackupDone] = useState(false);
const [nostrPublicKey, setNostrPublicKey] = useState("");
const [hasImportedNostrKey, setHasImportedNostrKey] = useState(false);

Expand Down Expand Up @@ -80,6 +81,7 @@ function AccountDetail() {
setAccount(response);
setAccountName(response.name);
setHasMnemonic(response.hasMnemonic);
setIsMnemonicBackupDone(response.isMnemonicBackupDone);
setHasImportedNostrKey(response.hasImportedNostrKey);

if (response.nostrEnabled) {
Expand Down Expand Up @@ -312,7 +314,7 @@ function AccountDetail() {
</h2>

<div className="shadow bg-white rounded-md sm:overflow-hidden p-6 dark:bg-surface-01dp flex flex-col gap-4">
{hasMnemonic && (
{hasMnemonic && !isMnemonicBackupDone && (
<Alert type="warn">{t("mnemonic.backup.warning")}</Alert>
)}

Expand Down
Loading
Loading