Skip to content

Commit

Permalink
[Sign TX] Input Overview View (#826)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeesunikim authored Apr 15, 2024
1 parent fd42e8d commit 5f273b2
Show file tree
Hide file tree
Showing 11 changed files with 406 additions and 67 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"git-info": "rm -rf src/generated/ && mkdir src/generated/ && echo export default \"{\\\"commitHash\\\": \\\"$(git rev-parse --short HEAD)\\\", \\\"version\\\": \\\"$(git describe --tags --always)\\\"};\" > src/generated/gitInfo.ts"
},
"dependencies": {
"@stellar/design-system": "^2.0.0-beta.9",
"@stellar/design-system": "^2.0.0-beta.10",
"@stellar/stellar-sdk": "^11.3.0",
"@tanstack/react-query": "^5.28.8",
"@tanstack/react-query-devtools": "^5.28.8",
Expand Down
198 changes: 197 additions & 1 deletion src/app/(sidebar)/transaction/sign/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,201 @@
"use client";

import { useState } from "react";
import { Alert, Card, Icon, Text, Button } from "@stellar/design-system";
import {
TransactionBuilder,
FeeBumpTransaction,
Transaction,
} from "@stellar/stellar-sdk";

import { useStore } from "@/store/useStore";

import { XdrPicker } from "@/components/FormElements/XdrPicker";
import { TextPicker } from "@/components/FormElements/TextPicker";
import { validate } from "@/validate";

import { FEE_BUMP_TX_FIELDS, TX_FIELDS } from "@/constants/signTransactionPage";

const MIN_LENGTH_FOR_FULL_WIDTH_FIELD = 30;

export default function SignTransaction() {
return <div>Sign Transaction</div>;
const { network } = useStore();
const [txEnv, setTxEnv] = useState<string>("");
const [isTxValid, setIsTxValid] = useState<boolean | undefined>(undefined);
const [txErrMsg, setTxErrMsg] = useState<string>("");
const [txSuccessMsg, setTxSuccessMsg] = useState<string>("");
const [tx, setTx] = useState<FeeBumpTransaction | Transaction | undefined>(
undefined,
);
const [isTxImported, setIsTxImported] = useState<boolean>(false);

const onChange = (value: string) => {
setTxErrMsg("");
setTxSuccessMsg("");
setTxEnv(value);

if (value.length > 0) {
const validatedXDR = validate.xdr(value);

if (validatedXDR.result === "success") {
setIsTxValid(true);
setTxSuccessMsg(validatedXDR.message);
} else {
setIsTxValid(false);
setTxErrMsg(validatedXDR.message);
}
}
};

const onImport = () => {
try {
const transaction = TransactionBuilder.fromXDR(txEnv, network.passphrase);
setIsTxImported(true);
setTx(transaction);
} catch (e) {
setIsTxImported(false);
setTxErrMsg("Unable to import a transaction envelope");
}
};

const rendeImportView = () => {
return (
<>
<Card>
<div className="SignTx__xdr">
<XdrPicker
id="sign-transaction-xdr"
label={
<Text size="xs" as="span" weight="medium">
Import a transaction envelope in XDR format
<span className="SignTx__icon">
<Icon.AlertCircle />
</span>
</Text>
}
value={txEnv || ""}
error={txErrMsg}
success={
<Text size="xs" as="span" addlClassName="success-message">
{txSuccessMsg}
</Text>
}
onChange={(e) => onChange(e.target.value)}
/>

<div className="SignTx__CTA">
<Button
disabled={!txEnv || !isTxValid}
size="md"
variant={"secondary"}
onClick={onImport}
>
Import transaction
</Button>
</div>
</div>
</Card>

<Alert
placement="inline"
variant="primary"
actionLink="https://developers.stellar.org/network/horizon/resources"
actionLabel="Read more about signatures on the developer's site"
>
<Text size="sm" as="p">
The transaction signer lets you add signatures to a Stellar
transaction. Signatures are used in the network to prove that the
account is authorized to perform the operations in the transaction.
</Text>
<Text size="sm" as="p">
For simple transactions, you only need one signature from the
correct account. Some advanced transactions may require more than
one signature if there are multiple source accounts or signing keys.
</Text>
</Alert>
</>
);
};

const renderOverviewView = () => {
if (!tx) {
return null;
}

const REQUIRED_FIELDS = [
{
label: "Signing for",
value: network.passphrase,
},
{
label: "Transaction Envelope XDR",
value: txEnv,
},
{
label: "Transaction Hash",
value: tx.hash().toString("hex"),
},
];

let mergedFields;

if (tx instanceof FeeBumpTransaction) {
mergedFields = [...REQUIRED_FIELDS, ...FEE_BUMP_TX_FIELDS(tx)];
} else {
mergedFields = [...REQUIRED_FIELDS, ...TX_FIELDS(tx)];
}

return (
<>
<Card>
<div className="SignTx__FieldViewer">
{mergedFields.map((field) => {
const className =
field.value.toString().length >= MIN_LENGTH_FOR_FULL_WIDTH_FIELD
? "full-width"
: "half-width";

if (field.label.includes("XDR")) {
return (
<div className={className} key={field.label}>
<XdrPicker
readOnly
id={field.label}
label={field.label}
value={field.value.toString()}
/>
</div>
);
} else {
return (
<div className={className} key={field.label}>
<TextPicker
readOnly
id={field.label}
label={field.label}
value={field.value.toString()}
copyButton={{
position: "right",
}}
/>
</div>
);
}
})}
</div>
</Card>
</>
);
};

return (
<div className="SignTx">
<div className="PageHeader">
<Text size="md" as="h1" weight="medium">
{isTxImported ? "Transaction Overview" : "Sign Transaction"}
</Text>
</div>
{isTxValid && tx ? renderOverviewView() : rendeImportView()}
</div>
);
}
2 changes: 2 additions & 0 deletions src/components/FormElements/PubKeyPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const PubKeyPicker = ({
placeholder = "Ex: GCEXAMPLE5HWNK4AYSTEQ4UWDKHTCKADVS2AHF3UI2ZMO3DPUSM6Q4UG",
value,
error,
readOnly,
onChange,
...props
}: PubKeyPickerProps) => (
Expand All @@ -32,6 +33,7 @@ export const PubKeyPicker = ({
placeholder={placeholder}
value={value}
error={error}
readOnly={readOnly}
onChange={onChange}
{...props}
/>
Expand Down
33 changes: 17 additions & 16 deletions src/components/FormElements/TextPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ interface TextPickerProps extends Omit<InputProps, "fieldSize"> {
label: string;
value: string;
placeholder?: string;
error: string | undefined;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
error?: string | undefined;
readOnly?: boolean;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

export const TextPicker = ({
Expand All @@ -20,18 +21,18 @@ export const TextPicker = ({
value,
error,
onChange,
readOnly,
...props
}: TextPickerProps) => {
return (
<Input
id={id}
fieldSize={fieldSize}
label={label}
labelSuffix={labelSuffix}
value={value}
error={error}
onChange={onChange}
{...props}
/>
);
};
}: TextPickerProps) => (
<Input
id={id}
fieldSize={fieldSize}
label={label}
labelSuffix={labelSuffix}
value={value}
error={error}
onChange={onChange}
readOnly={readOnly}
{...props}
/>
);
42 changes: 23 additions & 19 deletions src/components/FormElements/XdrPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ interface XdrPickerProps extends Omit<TextareaProps, "fieldSize"> {
id: string;
fieldSize?: "sm" | "md" | "lg";
labelSuffix?: string | React.ReactNode;
label: string;
label: string | React.ReactNode;
value: string;
placeholder?: string;
error: string | undefined;
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
error?: string | undefined;
success?: string | React.ReactNode;
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
readOnly?: boolean;
}

export const XdrPicker = ({
Expand All @@ -19,21 +21,23 @@ export const XdrPicker = ({
label,
value,
error,
success,
onChange,
readOnly,
...props
}: XdrPickerProps) => {
return (
<Textarea
id={id}
fieldSize={fieldSize}
label={label}
labelSuffix={labelSuffix}
placeholder="Ex: AAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAAZAAAAAMAAAAGAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAAJAAAAAAAAAAHwXhY2AAAAQCPAo8QwsZe9FA0sz/deMdhlu6/zrk7SgkBG22ApvtpETBhnGkX4trSFDz8sVlKqvweqGUVgvjUyM0AcHxyXZQw="
value={value}
error={error}
rows={5}
onChange={onChange}
{...props}
/>
);
};
}: XdrPickerProps) => (
<Textarea
id={id}
fieldSize={fieldSize}
label={label}
labelSuffix={labelSuffix}
placeholder="Ex: AAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAAZAAAAAMAAAAGAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAAJAAAAAAAAAAHwXhY2AAAAQCPAo8QwsZe9FA0sz/deMdhlu6/zrk7SgkBG22ApvtpETBhnGkX4trSFDz8sVlKqvweqGUVgvjUyM0AcHxyXZQw="
value={value}
error={error}
success={success}
rows={5}
onChange={onChange}
readOnly={readOnly}
{...props}
/>
);
6 changes: 3 additions & 3 deletions src/components/MuxedAccountResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const MuxedAccountResult = ({
label="Base Account G Address"
value={baseAddress || ""}
error=""
readOnly={true}
readOnly
copyButton={{
position: "right",
}}
Expand All @@ -29,7 +29,7 @@ export const MuxedAccountResult = ({
label="Muxed Account ID"
value={muxedId || ""}
error=""
readOnly={true}
readOnly
copyButton={{
position: "right",
}}
Expand All @@ -41,7 +41,7 @@ export const MuxedAccountResult = ({
label="Muxed Account M Address"
value={muxedAddress || ""}
error=""
readOnly={true}
readOnly
copyButton={{
position: "right",
}}
Expand Down
Loading

0 comments on commit 5f273b2

Please sign in to comment.