Skip to content

Commit

Permalink
Merge pull request #241 from cosmos/formfield-address-amount
Browse files Browse the repository at this point in the history
Improve form utils for address and amount
  • Loading branch information
abefernan authored Jul 26, 2024
2 parents 065ad00 + ab34f97 commit 968cf8e
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 50 deletions.
32 changes: 28 additions & 4 deletions components/forms/CreateTxForm/Fields/FieldAddress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,43 @@ import { Input } from "@/components/ui/input";
import { useChains } from "@/context/ChainsContext";
import { exampleAddress } from "@/lib/displayHelpers";
import { prettyFieldName } from "@/lib/form";
import { assert } from "@cosmjs/utils";
import * as z from "zod";
import type { FieldProps, FieldSchemaInput } from "./types";

export const getFieldAddressSchema = ({ chain }: FieldSchemaInput) => {
if (!chain) {
throw new Error("Could not get field address schema because of missing chain parameter");
const isFieldAddress = (fieldName: string) =>
fieldName.toLowerCase().includes("address") ||
["depositor", "proposer", "voter", "sender", "receiver", "contract", "newAdmin"].includes(
fieldName,
);

const isOptionalFieldAddress = (fieldName: string) => fieldName === "admin";

export const getFieldAddress = (fieldName: string) =>
isFieldAddress(fieldName) || isOptionalFieldAddress(fieldName) ? FieldAddress : null;

export const getFieldAddressSchema = (fieldName: string, { chain }: FieldSchemaInput) => {
assert(chain, "Could not get field address schema because of missing chain parameter");

if (!isFieldAddress(fieldName) && !isOptionalFieldAddress(fieldName)) {
return null;
}

return z
const zodString = z
.string({ invalid_type_error: "Must be a string", required_error: "Required" })
.trim()
.min(1, "Required")
.startsWith(chain.addressPrefix, `Invalid prefix for ${chain.chainDisplayName}`);

if (isFieldAddress(fieldName)) {
return zodString;
}

if (isOptionalFieldAddress(fieldName)) {
return z.optional(zodString);
}

return null;
};

export default function FieldAddress({ form, fieldFormName }: FieldProps) {
Expand Down
36 changes: 24 additions & 12 deletions components/forms/CreateTxForm/Fields/FieldAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,36 @@ import { prettyFieldName } from "@/lib/form";
import * as z from "zod";
import type { FieldProps } from "./types";

export const getFieldAmountSchema = () =>
z.object({
denom: z
.string({ invalid_type_error: "Must be a string", required_error: "Required" })
.trim()
.min(1, "Required"),
amount: z
.number({ invalid_type_error: "Must be a number", required_error: "Required" })
.positive("Must be positive"),
});
const isFieldAmount = (fieldName: string) =>
["amount", "initialDeposit", "value", "token", "funds"].includes(fieldName);

export const getFieldAmount = (fieldName: string) =>
isFieldAmount(fieldName) ? FieldAmount : null;

export const getFieldAmountSchema = (fieldName: string) =>
isFieldAmount(fieldName)
? z.object({
denom: z
.string({ invalid_type_error: "Must be a string", required_error: "Required" })
.trim()
.min(1, "Required"),
amount: z.coerce
.number({ invalid_type_error: "Must be a number", required_error: "Required" })
.positive("Must be positive"),
})
: null;

export default function FieldAmount({ form, fieldFormName }: FieldProps) {
const prettyLabel = prettyFieldName(fieldFormName);

return (
<>
<FormField
control={form.control}
name={`${fieldFormName}.denom`}
render={({ field }) => (
<FormItem>
<FormLabel>{"Denom" + prettyFieldName(fieldFormName)}</FormLabel>
<FormLabel>{`${prettyLabel} denom`}</FormLabel>
<FormControl>
<Input placeholder="Enter denom" {...field} />
</FormControl>
Expand All @@ -36,7 +46,9 @@ export default function FieldAmount({ form, fieldFormName }: FieldProps) {
name={`${fieldFormName}.amount`}
render={({ field }) => (
<FormItem>
<FormLabel>{prettyFieldName(fieldFormName)}</FormLabel>
<FormLabel>
{prettyLabel === "Amount" ? "Amount quantity" : `${prettyLabel} amount`}
</FormLabel>
<FormControl>
<Input placeholder="Enter amount" {...field} />
</FormControl>
Expand Down
2 changes: 2 additions & 0 deletions components/forms/CreateTxForm/Fields/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./FieldAddress";
export * from "./FieldAmount";
13 changes: 11 additions & 2 deletions components/forms/CreateTxForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,16 @@ export default function CreateTxForm() {
{Object.values(msgRegistry)
.filter((msg) => msg.category === category)
.map((msg) => (
<Button key={msg.typeUrl} onClick={() => addMsg(msg.typeUrl)}>
<Button
key={msg.typeUrl}
onClick={() => addMsg(msg.typeUrl)}
disabled={
msg.fields.map((f: string) => getField(f)).some((v: string) => v === null) ||
Object.values(getMsgSchema(msg.fields, { chain }).shape).some(
(v) => v === null,
)
}
>
Add {msg.name.startsWith("Msg") ? msg.name.slice(3) : msg.name}
</Button>
))}
Expand All @@ -83,7 +92,7 @@ export default function CreateTxForm() {
<div key={type.key}>
<h3>{msg.name}</h3>
{msg.fields.map((fieldName: string) => {
const Field = getField(fieldName);
const Field = getField(fieldName) || (() => null);
return (
<Field
key={fieldName}
Expand Down
43 changes: 11 additions & 32 deletions lib/form.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,30 @@
import FieldAddress, {
import {
getFieldAddress,
getFieldAddressSchema,
} from "@/components/forms/CreateTxForm/Fields/FieldAddress";
import FieldAmount, {
getFieldAmount,
getFieldAmountSchema,
} from "@/components/forms/CreateTxForm/Fields/FieldAmount";
} from "@/components/forms/CreateTxForm/Fields";
import { FieldSchemaInput } from "@/components/forms/CreateTxForm/Fields/types";
import { z } from "zod";

export const prettyFieldName = (fieldName: string) => {
const splitName = fieldName.split(/(?=[A-Z])/).join(" ");
const truncatedName = fieldName.slice(fieldName.indexOf(".", "msgs.".length) + 1);
const splitName = truncatedName.split(/(?=[A-Z])/).join(" ");
const capitalizedName = splitName.charAt(0).toUpperCase() + splitName.slice(1);

return capitalizedName;
};

export const getField = (fieldName: string) => {
switch (fieldName) {
case "fromAddress":
case "toAddress":
case "delegatorAddress":
case "validatorAddress":
return FieldAddress;
case "amount":
return FieldAmount;
default:
return () => null;
}
};
export const getField = (fieldName: string) =>
getFieldAddress(fieldName) || getFieldAmount(fieldName) || null;

const getFieldSchema = (fieldName: string) => {
switch (fieldName) {
case "fromAddress":
case "toAddress":
case "delegatorAddress":
case "validatorAddress":
return getFieldAddressSchema;
case "amount":
return getFieldAmountSchema;
default:
throw new Error(`No schema found for ${fieldName} field`);
}
};
const getFieldSchema = (fieldName: string, schemaInput: FieldSchemaInput) =>
getFieldAddressSchema(fieldName, schemaInput) || getFieldAmountSchema(fieldName) || null;

export const getMsgSchema = (fieldNames: readonly string[], schemaInput: FieldSchemaInput) => {
const fieldEntries = fieldNames.map((fieldName) => [
fieldName,
getFieldSchema(fieldName)(schemaInput),
getFieldSchema(fieldName, schemaInput),
]);

return z.object(Object.fromEntries(fieldEntries));
Expand Down

0 comments on commit 968cf8e

Please sign in to comment.