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

[Build Transaction] Attributes view UI only (without input values and validation) #825

Merged
merged 4 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 5 additions & 3 deletions src/app/(sidebar)/endpoints/[[...pages]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { SdsLink } from "@/components/SdsLink";
import { NextLink } from "@/components/NextLink";
import { formComponentTemplate } from "@/components/formComponentTemplate";
import { PrettyJson } from "@/components/PrettyJson";
import { InputSideElement } from "@/components/InputSideElement";

import { useStore } from "@/store/useStore";
import { isEmptyObject } from "@/helpers/isEmptyObject";
Expand Down Expand Up @@ -448,12 +449,13 @@ export default function Endpoints() {
readOnly
disabled
leftElement={
<div
className="Endpoints__input__requestType"
<InputSideElement
variant="text"
placement="left"
data-testid="endpoints-url-method"
>
{pageData.requestMethod}
</div>
</InputSideElement>
}
/>
<Button
Expand Down
188 changes: 187 additions & 1 deletion src/app/(sidebar)/transaction/build/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,191 @@
"use client";

import { Alert, Button, Card } from "@stellar/design-system";

import { TabView } from "@/components/TabView";
import { PubKeyPicker } from "@/components/FormElements/PubKeyPicker";
import { Box } from "@/components/layout/Box";
import { SdsLink } from "@/components/SdsLink";
import { PositiveIntPicker } from "@/components/FormElements/PositiveIntPicker";
import { MemoPicker } from "@/components/FormElements/MemoPicker";
import { TimeBoundsPicker } from "@/components/FormElements/TimeBoundsPicker";
import { InputSideElement } from "@/components/InputSideElement";

import { useStore } from "@/store/useStore";
import { Routes } from "@/constants/routes";
import { useAccountSequenceNumber } from "@/query/useAccountSequenceNumber";

export default function BuildTransaction() {
return <div>Build Transaction</div>;
const { transaction, network } = useStore();
const { activeTab } = transaction.build;
const { updateBuildActiveTab } = transaction;

const {
// data,
// error,
refetch: fetchSequenceNumber,
} = useAccountSequenceNumber({
// TODO: pass source account
publicKey: "",
horizonUrl: network.horizonUrl,
});

// useEffect(() => {
// if (data || error) {
// // TODO: set data ?? ""
// // TODO: set error?.toString() ?? ""
// }
// }, [data, error]);

// TODO: add info links
// TODO: use store values
// TODO: validation
const renderParams = () => {
return (
<Box gap="md">
<>
<Card>
<Box gap="lg">
<>
<PubKeyPicker
id="source_account"
label="Source Account"
// TODO: handle value
value={""}
// TODO: handle error
error={""}
// TODO: handle change
onChange={() => {}}
note={
<>
If you don’t have an account yet, you can create and fund
a test net account with the{" "}
<SdsLink href={Routes.ACCOUNT_CREATE}>
account creator
</SdsLink>
.
</>
}
/>

<PositiveIntPicker
id="sequence_number"
label="Transaction Sequence Number"
placeholder="Ex: 559234806710273"
// TODO: handle value
value={""}
// TODO: handle error
error={""}
// TODO: handle change
onChange={() => {}}
note="The transaction sequence number is usually one higher than current account sequence number."
rightElement={
<InputSideElement
variant="button"
onClick={() => fetchSequenceNumber()}
placement="right"
// TODO: disable if no valid source account
// disabled={}
>
Fetch next sequence
</InputSideElement>
}
/>

<PositiveIntPicker
id="base_fee"
label="Base Fee"
// TODO: handle value
value=""
// TODO: handle error
error=""
// TODO: handle change
onChange={() => {}}
note={
<>
The{" "}
<SdsLink href="https://developers.stellar.org/docs/learn/glossary#base-fee">
network base fee
</SdsLink>{" "}
is currently set to 100 stroops (0.00001 lumens). Based on
current network activity, we suggest setting it to 100
stroops. Final transaction fee is equal to base fee times
number of operations in this transaction.
</>
}
/>

<MemoPicker
id="memo"
// TODO: handle value
value={undefined}
labelSuffix="optional"
// TODO: handle error
error=""
// TODO: handle change
onChange={() => {}}
/>

<TimeBoundsPicker
// TODO: handle value
value={undefined}
labelSuffix="optional"
// TODO: handle error
error={undefined}
// TODO: handle change
onChange={() => {}}
/>

<div>
{/* TODO: add validation */}
<Button
size="md"
variant="secondary"
onClick={() => {
updateBuildActiveTab("operations");
}}
>
Add Operations
</Button>
</div>
</>
</Box>
</Card>

<Alert variant="primary" placement="inline">
The transaction builder lets you build a new Stellar transaction.
This transaction will start out with no signatures. To make it into
the ledger, this transaction will then need to be signed and
submitted to the network.
</Alert>
</>
</Box>
);
};

const renderOperations = () => {
return <Card>Operations</Card>;
};

return (
<div>
<TabView
heading={{ title: "Build Transaction" }}
tab1={{
id: "params",
label: "Params",
content: renderParams(),
}}
tab2={{
id: "operations",
label: "Operations",
content: renderOperations(),
}}
activeTabId={activeTab}
onTabChange={(id) => {
updateBuildActiveTab(id);
}}
/>
</div>
);
}
87 changes: 87 additions & 0 deletions src/components/FormElements/MemoPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from "react";
import { MemoType, MemoValue } from "@stellar/stellar-sdk";
import { Input } from "@stellar/design-system";

import { RadioPicker } from "@/components/RadioPicker";
import { ExpandBox } from "@/components/ExpandBox";

type MemoPickerValue = { type: MemoType; value: MemoValue | undefined };

type MemoPickerProps = {
id: string;
value: MemoPickerValue | undefined;
onChange: (
optionId: string | undefined,
optionValue?: MemoPickerValue,
) => void;
labelSuffix?: string | React.ReactNode;
error: string | undefined;
};

export const MemoPicker = ({
id,
value,
onChange,
labelSuffix,
error,
}: MemoPickerProps) => {
const memoValuePlaceholder = (type?: MemoType) => {
if (!type) {
return "";
}

switch (type) {
case "id":
return "Unsigned 64-bit integer";
case "hash":
case "return":
return "32-byte hash in hexadecimal format (64 [0-9a-f] characters)";
case "text":
return "UTF-8 string of up to 28 bytes";
case "none":
default:
return "";
}
};

return (
<div className="RadioPicker__inset">
<RadioPicker
id={id}
selectedOption={value?.type}
label="Memo"
labelSuffix={labelSuffix}
onChange={onChange}
options={[
{ id: "none", label: "None", value: { type: "none", value: "" } },
{ id: "text", label: "Text", value: { type: "text", value: "" } },
{ id: "id", label: "ID", value: { type: "id", value: "" } },
{ id: "hash", label: "Hash", value: { type: "hash", value: "" } },
{
id: "return",
Copy link
Contributor

Choose a reason for hiding this comment

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

first time hearing return (I see that's also what our current lab has)

label: "Return",
value: { type: "return", value: "" },
},
]}
/>

<ExpandBox
isExpanded={Boolean(value?.type && value.type !== "none")}
offsetTop="sm"
>
<Input
fieldSize="md"
id="memo_value"
placeholder={memoValuePlaceholder(value?.type)}
value={value?.value?.toString() || ""}
onChange={(e) => {
if (value?.type) {
onChange(value.type, { type: value.type, value: e.target.value });
}
}}
error={error}
/>
</ExpandBox>
</div>
);
};
89 changes: 89 additions & 0 deletions src/components/FormElements/TimeBoundsPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Box } from "@/components/layout/Box";
import { SdsLink } from "@/components/SdsLink";
import { InputSideElement } from "@/components/InputSideElement";
import { PositiveIntPicker } from "./PositiveIntPicker";

type TimeBoundValue = { min: string; max: string };

type TimeBoundsPickerProps = {
value: TimeBoundValue | undefined;
labelSuffix?: string | React.ReactNode;
onChange: (value: TimeBoundValue) => void;
error: { min: string | false; max: string | false } | undefined;
};

export const TimeBoundsPicker = ({
value = { min: "", max: "" },
labelSuffix,
onChange,
error,
}: TimeBoundsPickerProps) => {
return (
<Box gap="sm">
<>
<Box gap="xs">
<>
<PositiveIntPicker
id="timebounds-min"
label="Time Bounds"
labelSuffix={labelSuffix}
placeholder="Lower time bound unix timestamp. Ex: 1479151713"
value={value.min}
error={error?.min ? `Lower time bound: ${error.min}` : ""}
onChange={(e) => {
onChange({ ...value, min: e.target.value });
}}
/>
<PositiveIntPicker
id="timebounds-max"
label=""
placeholder="Upper time bound unix timestamp. Ex: 1479151713"
value={value.max}
error={error?.max ? `Upper time bound: ${error.max}` : ""}
onChange={(e) => {
onChange({ ...value, max: e.target.value });
}}
rightElement={
<InputSideElement
variant="button"
placement="right"
onClick={() => {
onChange({
...value,
max: (
Math.ceil(new Date().getTime() / 1000) +
5 * 60
).toString(),
});
}}
>
Set to 5 min from now
</InputSideElement>
}
/>
</>
</Box>
<Box gap="xs" addlClassName="FieldNote FieldNote--note FieldNote--md">
<>
<div>
Enter{" "}
<SdsLink href="http://www.epochconverter.com/">
unix timestamp
</SdsLink>{" "}
values of time bounds when this transaction will be valid.
</div>

<div>
For regular transactions, it is highly recommended to set the
upper time bound to get a{" "}
<SdsLink href="https://github.com/stellar/stellar-core/issues/1811">
final result
</SdsLink>{" "}
of a transaction in a defined time.
</div>
</>
</Box>
</>
</Box>
);
};
Loading
Loading