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

[Sign TX] Input Overview View #826

Merged
merged 10 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
195 changes: 194 additions & 1 deletion src/app/(sidebar)/transaction/sign/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,198 @@
"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}
note={
<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;
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
}

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we always guaranteed to have field.value? Just making sure we don't need to chain these like optional with ?.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!

? "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()}
/>
</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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally I'd like to have a copy by default on click functionality. I didn't want to add too much to the original component though. Should I just create its own component? @quietbits

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, let's use the copy button we already have if we need that functionality here.

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;
note?: string | React.ReactNode;
jeesunikim marked this conversation as resolved.
Show resolved Hide resolved
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
readOnly?: boolean;
}

export const XdrPicker = ({
Expand All @@ -19,21 +21,23 @@ export const XdrPicker = ({
label,
value,
error,
note,
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}
rows={5}
note={note}
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
Loading