Skip to content

Commit f1a5c46

Browse files
authored
Add final design touches to Ledger flow (#231)
1 parent 597b34e commit f1a5c46

File tree

12 files changed

+169
-35
lines changed

12 files changed

+169
-35
lines changed

main/api/utils/transactions.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
CreateTransactionRequest,
33
CurrencyUtils,
44
RawTransactionSerde,
5+
RpcClient,
56
} from "@ironfish/sdk";
67
import { z } from "zod";
78

@@ -29,20 +30,23 @@ export async function createUnsignedTransaction({
2930
const ironfish = await manager.getIronfish();
3031
const rpcClient = await ironfish.rpcClient();
3132

32-
const rawTx = await createRawTransaction({
33-
fromAccount,
34-
toAccount,
35-
assetId,
36-
amount,
37-
fee,
38-
memo,
39-
});
33+
const baseTx = await createTransaction(
34+
{
35+
fromAccount,
36+
toAccount,
37+
assetId,
38+
amount,
39+
fee,
40+
memo,
41+
},
42+
rpcClient,
43+
);
4044

41-
const serializedRawTx = RawTransactionSerde.serialize(rawTx);
4245
const builtTransactionResponse = await rpcClient.wallet.buildTransaction({
43-
rawTransaction: serializedRawTx.toString("hex"),
46+
rawTransaction: baseTx.content.transaction,
4447
account: fromAccount,
4548
});
49+
4650
return builtTransactionResponse.content.unsignedTransaction;
4751
}
4852

@@ -57,6 +61,32 @@ export async function createRawTransaction({
5761
const ironfish = await manager.getIronfish();
5862
const rpcClient = await ironfish.rpcClient();
5963

64+
const createResponse = await createTransaction(
65+
{
66+
fromAccount,
67+
toAccount,
68+
assetId,
69+
amount,
70+
fee,
71+
memo,
72+
},
73+
rpcClient,
74+
);
75+
const bytes = Buffer.from(createResponse.content.transaction, "hex");
76+
return RawTransactionSerde.deserialize(bytes);
77+
}
78+
79+
function createTransaction(
80+
{
81+
fromAccount,
82+
toAccount,
83+
assetId,
84+
amount,
85+
fee,
86+
memo,
87+
}: CreateTransactionInput,
88+
rpcClient: RpcClient,
89+
) {
6090
const params: CreateTransactionRequest = {
6191
account: fromAccount,
6292
outputs: [
@@ -73,7 +103,5 @@ export async function createRawTransaction({
73103
confirmations: undefined,
74104
};
75105

76-
const createResponse = await rpcClient.wallet.createTransaction(params);
77-
const bytes = Buffer.from(createResponse.content.transaction, "hex");
78-
return RawTransactionSerde.deserialize(bytes);
106+
return rpcClient.wallet.createTransaction(params);
79107
}

renderer/components/ConnectLedgerModal/ConnectLedgerModal.tsx

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import { trpcReact } from "@/providers/TRPCProvider";
1212
import { PillButton } from "@/ui/PillButton/PillButton";
1313

1414
import { StepAddAccount } from "./Steps/StepAddAccount";
15+
import { StepApprove } from "./Steps/StepApprove";
1516
import { StepConnect } from "./Steps/StepConnect";
17+
import { StepError } from "./Steps/StepError";
1618

1719
const messages = defineMessages({
1820
headingConnectLedger: {
@@ -36,6 +38,9 @@ const messages = defineMessages({
3638
continue: {
3739
defaultMessage: "Continue",
3840
},
41+
tryAgain: {
42+
defaultMessage: "Try again",
43+
},
3944
});
4045

4146
type LedgerStatus = {
@@ -46,7 +51,7 @@ type LedgerStatus = {
4651
deviceName: string;
4752
};
4853

49-
const STEPS = ["CONNECT_LEDGER", "SELECT_ACCOUNT", "CONFIRM"] as const;
54+
const STEPS = ["CONNECT_LEDGER", "SELECT_ACCOUNT", "APPROVE", "ERROR"] as const;
5055

5156
export function ConnectLedgerModal({
5257
isOpen,
@@ -93,11 +98,14 @@ export function ConnectLedgerModal({
9398
onSuccess: () => {
9499
onClose();
95100
},
101+
onError: () => {
102+
setStep("ERROR");
103+
},
96104
});
97105

98106
useEffect(() => {
99107
if (
100-
step !== "CONNECT_LEDGER" &&
108+
!["CONNECT_LEDGER", "ERROR"].includes(step) &&
101109
(!isLedgerConnected || !isLedgerUnlocked || !isIronfishAppOpen)
102110
) {
103111
setStep("CONNECT_LEDGER");
@@ -124,18 +132,14 @@ export function ConnectLedgerModal({
124132
onConfirmChange={setIsConfirmed}
125133
/>
126134
)}
127-
{step === "CONFIRM" && (
128-
<div>
129-
<h2>
130-
Confirm this action on your device. This is a placeholder while
131-
this step is designed.
132-
</h2>
133-
{importError?.message && (
134-
<p>
135-
<strong>Error:</strong> {importError.message}
136-
</p>
137-
)}
138-
</div>
135+
{step === "APPROVE" && <StepApprove />}
136+
{step === "ERROR" && (
137+
<StepError
138+
errorMessage={
139+
importError?.message ??
140+
"An unknown error occured. Please try again."
141+
}
142+
/>
139143
)}
140144
</ModalBody>
141145

@@ -153,18 +157,25 @@ export function ConnectLedgerModal({
153157
isImporting
154158
}
155159
onClick={() => {
160+
if (step === "ERROR") {
161+
setStep("CONNECT_LEDGER");
162+
return;
163+
}
164+
156165
if (step === "CONNECT_LEDGER") {
157166
setStep("SELECT_ACCOUNT");
158167
return;
159168
}
160169

161170
if (step === "SELECT_ACCOUNT") {
162171
importLedgerAccount();
163-
setStep("CONFIRM");
172+
setStep("APPROVE");
164173
}
165174
}}
166175
>
167-
{formatMessage(messages.continue)}
176+
{formatMessage(
177+
step === "ERROR" ? messages.tryAgain : messages.continue,
178+
)}
168179
</PillButton>
169180
</ModalFooter>
170181
</ModalContent>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Heading, Text, Box } from "@chakra-ui/react";
2+
import Image from "next/image";
3+
import { defineMessages, useIntl } from "react-intl";
4+
5+
import approveImage from "./assets/approve.svg";
6+
7+
const messages = defineMessages({
8+
heading: {
9+
defaultMessage: "Approve on your Ledger",
10+
},
11+
body: {
12+
defaultMessage:
13+
"To proceed with connecting your account to the Iron Fish Node app, please approve the request on your Ledger device.",
14+
},
15+
});
16+
17+
export function StepApprove() {
18+
const { formatMessage } = useIntl();
19+
return (
20+
<>
21+
<Heading mb={4}>{formatMessage(messages.heading)}</Heading>
22+
<Image src={approveImage} alt="" />
23+
<Box mt={6}>
24+
<Text color="muted" fontSize="md">
25+
{formatMessage(messages.body)}
26+
</Text>
27+
</Box>
28+
</>
29+
);
30+
}

renderer/components/ConnectLedgerModal/Steps/StepConnect.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { Heading, HStack, Flex, VStack, Text } from "@chakra-ui/react";
22
import Image from "next/image";
3+
import { useMemo } from "react";
34
import { IoMdCheckmark } from "react-icons/io";
45
import { useIntl, defineMessages } from "react-intl";
56

67
import { COLORS } from "@/ui/colors";
78

89
import connectImage from "./assets/connect.svg";
10+
import connectedImage from "./assets/connected.svg";
11+
import openIronFishImage from "./assets/openIronFish.svg";
12+
import unlockImage from "./assets/unlock.svg";
913

1014
const messages = defineMessages({
1115
headingConnectLedger: {
@@ -43,10 +47,24 @@ export function StepConnect({
4347
isIronfishAppOpen,
4448
}: Props) {
4549
const { formatMessage } = useIntl();
50+
51+
const imageSrc = useMemo(() => {
52+
if (!isLedgerConnected) {
53+
return connectImage;
54+
}
55+
if (!isLedgerUnlocked) {
56+
return unlockImage;
57+
}
58+
if (!isIronfishAppOpen) {
59+
return openIronFishImage;
60+
}
61+
return connectedImage;
62+
}, [isLedgerConnected, isLedgerUnlocked, isIronfishAppOpen]);
63+
4664
return (
4765
<>
4866
<Heading mb={4}>{formatMessage(messages.headingConnectLedger)}</Heading>
49-
<Image src={connectImage} alt="" />
67+
<Image src={imageSrc} alt="" />
5068
<VStack alignItems="stretch" gap={2} mt={6}>
5169
<ListItem
5270
number="1"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Heading, Text, Box } from "@chakra-ui/react";
2+
import Image from "next/image";
3+
import { ReactNode } from "react";
4+
import { defineMessages, useIntl } from "react-intl";
5+
6+
import errorImage from "./assets/error.svg";
7+
8+
const messages = defineMessages({
9+
heading: {
10+
defaultMessage: "An error occured",
11+
},
12+
});
13+
14+
type Props = {
15+
errorMessage: ReactNode;
16+
};
17+
18+
export function StepError({ errorMessage }: Props) {
19+
const { formatMessage } = useIntl();
20+
return (
21+
<>
22+
<Heading mb={4}>{formatMessage(messages.heading)}</Heading>
23+
<Image src={errorImage} alt="" />
24+
<Box mt={6}>
25+
{typeof errorMessage === "string" ? (
26+
<Text color="muted" fontSize="md">
27+
{errorMessage}
28+
</Text>
29+
) : (
30+
errorMessage
31+
)}
32+
</Box>
33+
</>
34+
);
35+
}

renderer/components/ConnectLedgerModal/Steps/assets/approve.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)