diff --git a/apps/dapp/src/modules/auction/curator-fee-manager.tsx b/apps/dapp/src/modules/auction/curator-fee-manager.tsx index b440adcf..ed5b8eeb 100644 --- a/apps/dapp/src/modules/auction/curator-fee-manager.tsx +++ b/apps/dapp/src/modules/auction/curator-fee-manager.tsx @@ -1,13 +1,13 @@ -import { Button, InfoLabel, Badge, Tooltip, cn } from "@repo/ui"; +import React from "react"; +import { Button, InfoLabel, Text, Badge, Tooltip, cn } from "@repo/ui"; import { useCuratorFees } from "./hooks/use-curator-fees"; import { useChainId } from "wagmi"; -import React from "react"; import { useFees } from "./hooks/use-fees"; -import { parsePercent } from "utils/number"; import { AuctionType } from "@repo/types"; import { getAuctionHouse } from "utils/contracts"; import { activeChains } from "@repo/env/src/chains"; import { auctionMetadata } from "./metadata"; +import { z } from "zod"; type CuratorFeeManagerProps = { chainId?: number; @@ -15,6 +15,7 @@ type CuratorFeeManagerProps = { modules: AuctionType[]; className?: string; }; + export function CuratorFeeManager({ auctionType, ...props @@ -30,14 +31,30 @@ export function CuratorFeeManager({ data: { maxCuratorFee }, } = useFees(chainId, ah.address, auctionType); - const [fee, setFee] = React.useState(""); - const curatorFees = useCuratorFees(chainId, parseFloat(fee), auctionType); - const parsedAmount = parseFloat(fee); + const [fee, setFee] = React.useState(); + const curatorFees = useCuratorFees( + chainId, + parseFloat(fee ?? "0"), + auctionType, + ); + + const feeSchema = React.useMemo(() => { + return z.coerce + .number({ invalid_type_error: "Must be a valid number" }) + .optional() + .refine((data) => !data || (data ?? 0) <= (maxCuratorFee ?? 0), { + message: `Max fee is ${maxCuratorFee}%`, + }) + .refine((data) => !data || data !== curatorFees.fee); + }, [maxCuratorFee]); + + const result = feeSchema.safeParse(fee); + const error = !result.success && result.error.errors[0]; //Clears tx state when moving between chains after updating a fee React.useEffect(() => { curatorFees.reset(); - setFee(""); + setFee(undefined); }, [chainId, auctionType]); return ( @@ -48,32 +65,34 @@ export function CuratorFeeManager({ ", ", )}.`} > - { - parsePercent(e); - setFee(e.target.value); - }} - onBlur={(e) => { - if (!isFinite(parseFloat(e.target.value))) { - setFee(curatorFees.fee + "%"); - } - }} - reverse - /> +
+
+ setFee(e.target.value)} + maxLength={5} + editable + reverse + /> + +
+ {error && ( + + {error.message} + + )} +
- - ) { const int = e.target.value.slice(0, e.target.value.length - 1); /* If there is no number (just the percent sign), rewrite it so it persists and move the cursor just before it.*/ + if (int.includes("%")) { e.target.value = "%"; e.target.setSelectionRange(0, 0); diff --git a/packages/ui/src/components/info-label.tsx b/packages/ui/src/components/info-label.tsx index 913bfc30..d1960729 100644 --- a/packages/ui/src/components/info-label.tsx +++ b/packages/ui/src/components/info-label.tsx @@ -8,7 +8,7 @@ export type InfoLabelProps = { editable?: boolean; className?: string; inputClassName?: string; -} & React.ComponentProps<"input">; +} & React.InputHTMLAttributes; const valueSizeMap = { sm: "text-base", @@ -16,28 +16,30 @@ const valueSizeMap = { lg: "text-3xl", } as const; -export function InfoLabel(props: InfoLabelProps) { +export function InfoLabel({ + label, + reverse, + editable, + valueSize, + className, + inputClassName, + ...props +}: InfoLabelProps) { return ( -
- {props.editable ? ( +
+ {editable ? ( ) : ( -

+

{props.value}

)} -

{props.label}

+

{label}

); }