Skip to content
Open
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
145 changes: 90 additions & 55 deletions apps/dapp/src/modules/auction/auction-bid-input.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { FormField, FormItemWrapperSlim } from "@repo/ui";
import { Text, FormField, FormItemWrapperSlim, UsdToggle } from "@repo/ui";
import { useFormContext } from "react-hook-form";
import { PropsWithAuction } from "@axis-finance/types";
import { TokenAmountInput } from "modules/token/token-amount-input";
import { trimCurrency } from "utils/currency";
import { useState } from "react";
import { formatUnits, parseUnits } from "viem";
import { BidForm } from "./auction-purchase";
import { PriceSlider } from "./price-slider";

export function AuctionBidInput({
auction,
Expand All @@ -18,6 +19,7 @@ export function AuctionBidInput({
disabled?: boolean;
} & PropsWithAuction) {
const form = useFormContext<BidForm>();
const empData = auction.encryptedMarginalPrice!;

const [formAmount] = form.watch(["quoteTokenAmount"]);

Expand Down Expand Up @@ -49,62 +51,23 @@ export function AuctionBidInput({
setMinAmountOutFormatted(trimCurrency(minAmountOutDecimal));
};

function handleSliderChange(values: number[]) {
const [amount] = values;
form.setValue("bidPrice", amount.toString());
}

return (
<div className="text-foreground flex flex-col gap-y-2">
<div className="bg-secondary flex justify-between rounded-sm pt-1">
<div className="">
<FormField
name="quoteTokenAmount"
control={form.control}
render={({ field }) => (
<FormItemWrapperSlim>
<TokenAmountInput
{...field}
disabled={disabled}
label="Spend Amount"
balance={formatUnits(balance, auction.quoteToken.decimals)}
limit={
limit
? trimCurrency(
formatUnits(limit, auction.quoteToken.decimals),
)
: undefined
}
token={auction.quoteToken}
onChange={(e) => {
field.onChange(e);

// Display USD value of input amount
const rawAmountIn = e as string;
// Update amount out value
handleAmountOutChange(
parseUnits(rawAmountIn, auction.quoteToken.decimals),
);
}}
onClickMaxButton={() => {
// Take the minimum of the balance and the limit
let maxSpend = balance;
if (limit) {
maxSpend = balance < limit ? balance : limit;
}

const maxSpendStr = formatUnits(
maxSpend,
auction.quoteToken.decimals,
);

form.setValue("quoteTokenAmount", maxSpendStr);
// Force re-validation
form.trigger("quoteTokenAmount");

// Update amount out value
handleAmountOutChange(maxSpend);
}}
/>
</FormItemWrapperSlim>
)}
/>
</div>
<div
className="bg-secondary flex items-center justify-between rounded-sm pt-1"
onClick={(e) => e.preventDefault()}
>
<Text className="text-[14px] uppercase tracking-wide" mono>
{" "}
How much should {auction.baseToken.name} cost?
</Text>

<UsdToggle currencySymbol={auction.quoteToken.symbol} />
</div>

<div className="bg-secondary flex justify-between rounded-sm pt-1">
Expand Down Expand Up @@ -159,6 +122,78 @@ export function AuctionBidInput({
/>
</div>
</div>
<div className="pb-6">
<PriceSlider
min={+empData.minPrice}
max={100} //TODO: add max amount to ipfs
maxAmountDisplay={"100"}
minAmountDisplay={auction.formatted?.minPrice}
onValueChange={handleSliderChange}
/>
</div>

<div className="">
<FormField
name="quoteTokenAmount"
control={form.control}
render={({ field }) => (
<FormItemWrapperSlim>
<TokenAmountInput
{...field}
disabled={disabled}
label="Spend Amount"
balance={formatUnits(balance, auction.quoteToken.decimals)}
limit={
limit
? trimCurrency(
formatUnits(limit, auction.quoteToken.decimals),
)
: undefined
}
token={auction.quoteToken}
onChange={(e) => {
field.onChange(e);

// Display USD value of input amount
const rawAmountIn = e as string;
// Update amount out value
handleAmountOutChange(
parseUnits(rawAmountIn, auction.quoteToken.decimals),
);
}}
onClickMaxButton={() => {
// Take the minimum of the balance and the limit
let maxSpend = balance;
if (limit) {
maxSpend = balance < limit ? balance : limit;
}

const maxSpendStr = formatUnits(
maxSpend,
auction.quoteToken.decimals,
);

form.setValue("quoteTokenAmount", maxSpendStr);
// Force re-validation
form.trigger("quoteTokenAmount");

// Update amount out value
handleAmountOutChange(maxSpend);
}}
/>
</FormItemWrapperSlim>
)}
/>
</div>

{showAmountOut && (
<div className="mt-1.5 rounded border border-neutral-500 p-2">
<Text color="secondary">
If successful, you will receive at least: {minAmountOutFormatted}
{auction.baseToken.symbol}
</Text>
</div>
)}
</div>
);
}
2 changes: 1 addition & 1 deletion apps/dapp/src/modules/auction/auction-purchase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export function AuctionPurchase({ auction, ...props }: AuctionPurchaseProps) {
// TODO display "waiting" in modal when the tx is waiting to be signed by the user

return (
<div className="mx-auto lg:min-w-[477px]">
<div className="mx-auto lg:min-w-[377px]">
{canBid ? (
<FormProvider {...form}>
<form onSubmit={(e) => e.preventDefault()}>
Expand Down
23 changes: 23 additions & 0 deletions apps/dapp/src/modules/auction/auction-sealed-bid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { PriceSlider } from "./price-slider";
import { TokenAmountInput } from "modules/token/token-amount-input";
import { PropsWithAuction } from "@axis-finance/types";

export function AuctionSealedBid({ auction }: PropsWithAuction) {
const quoteSymbol = auction.quoteToken.symbol;
const label = `${quoteSymbol} for ${auction.baseToken.symbol}`;

return (
<div className="space-y-8">
{" "}
<div className="space-y-2">
<TokenAmountInput
disableMaxButton
tokenLabel={label}
token={auction.quoteToken}
/>
<PriceSlider min={20} />
</div>
<TokenAmountInput token={auction.quoteToken} />
</div>
);
}
116 changes: 116 additions & 0 deletions apps/dapp/src/modules/auction/price-slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as React from "react";
import * as SliderPrimitive from "@radix-ui/react-slider";
import { useFormContext } from "react-hook-form";

import { cn } from "@/utils";
import { BidForm } from "./auction-purchase";

type PriceSliderProps = {
minAmountDisplay?: string;
maxAmountDisplay?: string;
};

const PriceSlider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> & PriceSliderProps
>(({ className, ...props }, ref) => {
const [price, setPrice] = React.useState<string>();
const [sliderValue, setSliderValue] = React.useState<number[]>();

const { watch } = useFormContext<BidForm>();
const [formPrice] = watch(["bidPrice", "baseTokenAmount"]);

//Updates slider position and value when the price is typed in
React.useEffect(() => {
if (formPrice && formPrice !== price) {
setPrice(formPrice);
setSliderValue([+formPrice]);
}
}, [formPrice]);

function handleChange(value: number[]) {
const [price] = value;
if (!props.min || price >= props.min) {
setPrice(price.toString());
setSliderValue(value);
}
}

const fadeLeft = price && isBelowLowerBound(+price, props.min, props.max);
const fadeRight = price && isAboveUpperBound(+price, props.min, props.max);

return (
<div
className={cn(
"price-slider-gradient z-10 flex h-4 justify-center",
className,
)}
>
<div className={"relative w-[70%] "}>
<div
className={cn(
"text-background absolute transition-all",
fadeLeft && "opacity-0",
)}
>
<div className="relative -top-2 m-0 text-[20px] leading-normal">
|
</div>
<div className="text-foreground relative -left-0.5 -top-4">
{props.minAmountDisplay}
</div>
</div>
<SliderPrimitive.Root
ref={ref}
value={sliderValue}
onValueChange={handleChange}
className={cn(
"relative flex w-full cursor-pointer touch-none select-none items-center ",
)}
{...props}
>
<SliderPrimitive.Track className="relative h-4 w-full grow overflow-hidden ">
<SliderPrimitive.Range className="absolute h-full">
{" "}
</SliderPrimitive.Range>
</SliderPrimitive.Track>

<SliderPrimitive.Thumb
asChild
className="focus-visible:ring-ring bg-background block h-4 w-4 rounded-full shadow transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50"
>
<div className="w-1" />
</SliderPrimitive.Thumb>
</SliderPrimitive.Root>
</div>
<div
className={cn(
"text-background relative -left-1 transition-all",
fadeRight && "opacity-0",
)}
>
<div className="relative -top-2 m-0 text-[20px] leading-normal">|</div>
<div className="text-foreground relative -left-0.5 -top-4">
{props.maxAmountDisplay}
</div>
</div>
</div>
);
});

const TOLERANCE = 0.04; //4%

function isBelowLowerBound(value?: number, min = 0, max = 0): boolean {
if (!value) return false;
const lowerThreshold = min + (max - min) * TOLERANCE;
return value <= lowerThreshold;
}

function isAboveUpperBound(value?: number, min = 0, max = 0): boolean {
if (!value) return false;
const upperThreshold = max - (max - min) * TOLERANCE;
return value >= upperThreshold;
}

PriceSlider.displayName = SliderPrimitive.Root.displayName;
export { PriceSlider };
8 changes: 5 additions & 3 deletions apps/dapp/src/modules/token/token-amount-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Format } from "./format";

type TokenAmountInputProps = React.HTMLProps<HTMLInputElement> & {
/** the input's label */
label: string;
label?: string;
/** the input's token label, defaults to the token's symbol */
tokenLabel?: string;
/** the input's token type */
Expand All @@ -32,7 +32,7 @@ type TokenAmountInputProps = React.HTMLProps<HTMLInputElement> & {
/** the prefix to add to the amount */
amountPrefix?: string;

onChange: NumberInputProps["onChange"];
onChange?: NumberInputProps["onChange"];
};

export const TokenAmountInput = React.forwardRef<
Expand All @@ -54,16 +54,18 @@ export const TokenAmountInput = React.forwardRef<
disableMaxButton,
onClickMaxButton,
amountPrefix,
className,
...props
},
ref,
) => {
return (
<div
className={cn(
"hover:bg-surface-secondary border-primary bg-surface-tertiary group rounded border-2 p-4 transition-all",
"hover:bg-surface-secondary bg-surface-tertiary group rounded border-2 border-transparent p-4 transition-all",
error && "border-feedback-alert",
disabled && "opacity-50",
className,
)}
>
<div className="flex">
Expand Down
3 changes: 2 additions & 1 deletion apps/dapp/src/pages/create-auction-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,7 @@ export default function CreateAuctionPage() {
// Define the options listed in the callback select dropdown
const callbackOptions = React.useMemo(() => {
form.resetField("callbacksType");

const existingCallbacks = getExistingCallbacks(chainId);

// Define the Baseline callback options
Expand Down Expand Up @@ -1615,7 +1616,7 @@ export default function CreateAuctionPage() {
});

return existingCallbacks;
}, [chainId, form]);
}, [chainId]);

const handlePreview = () => {
form.trigger();
Expand Down
Loading
Loading