Skip to content

Commit

Permalink
Attributes UI done
Browse files Browse the repository at this point in the history
  • Loading branch information
quietbits committed Apr 10, 2024
1 parent 894ef23 commit 39f468f
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 33 deletions.
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
144 changes: 140 additions & 4 deletions src/app/(sidebar)/transaction/build/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,156 @@
"use client";

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

import { TabView } from "@/components/TabView";
import { useStore } from "@/store/useStore";
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() {
const { transaction } = useStore();
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 renderAttributes = () => {
return (
<Box gap="md">
<>
<Card>Attributes</Card>
<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");
}}
>
Go To Operations
</Button>
</div>
</>
</Box>
</Card>

<Alert variant="primary" placement="inline">
The transaction builder lets you build a new Stellar transaction.
Expand Down
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",
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

0 comments on commit 39f468f

Please sign in to comment.