Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
0a264aa
Handle empty switch state
feyyazcigim Oct 28, 2025
3730250
Add sell action empty state
feyyazcigim Oct 28, 2025
fb570dc
Add PodLineGraph component
feyyazcigim Oct 29, 2025
2e0c860
Add graph title
feyyazcigim Oct 29, 2025
57c4498
Implement sell list UI
feyyazcigim Oct 29, 2025
b87b0c7
Add partially select with slider to PodLineGraph
feyyazcigim Oct 29, 2025
4ce96ca
Implement sell/fill
feyyazcigim Oct 30, 2025
04e566d
Implement buy/order flow
feyyazcigim Oct 30, 2025
58f68ca
Implement buy/fill UI
feyyazcigim Oct 30, 2025
b15c5ea
Fix sell/list calculations
feyyazcigim Oct 30, 2025
3693f7c
Fix sell/list willing amount decimal
feyyazcigim Oct 30, 2025
36eb579
Batch listing and success component for sell/list
feyyazcigim Oct 30, 2025
d26980a
Add effective temperature to sell/list
feyyazcigim Oct 30, 2025
bfa0601
Add effective temperature to sell/fill
feyyazcigim Oct 30, 2025
06ef584
Implement batch sell to sell/fill
feyyazcigim Oct 31, 2025
8d52d64
Add SmartApprovalButton
feyyazcigim Oct 31, 2025
7bcefd5
Improve graph interactions on buy/order
feyyazcigim Oct 31, 2025
a7e7c60
Add success component to buy/order
feyyazcigim Oct 31, 2025
9d1de8c
Improve UI on buy/fill
feyyazcigim Oct 31, 2025
2b5caaa
Implement contrat interactions for buy/fill
feyyazcigim Oct 31, 2025
efd2bda
Change tab trigger - buy/sell pods
feyyazcigim Oct 31, 2025
e69caee
Add buy/sell redirect from chart
feyyazcigim Oct 31, 2025
cc4fbf2
Add content header to Market page
feyyazcigim Oct 31, 2025
136bf4b
Add graph label parameter
feyyazcigim Oct 31, 2025
f399da3
Add multiple select on graph
feyyazcigim Oct 31, 2025
1affa18
Add advanced settings to create listing
feyyazcigim Oct 31, 2025
38174d6
Fix ComboInputFiels
feyyazcigim Oct 31, 2025
a2704a9
Edit Market header
feyyazcigim Oct 31, 2025
37071c8
Clean up code by removing unnecessary comment lines
feyyazcigim Nov 3, 2025
ff7bfba
Replace duplicated Buy/Sell buttons with Tabs and preserve scroll wit…
feyyazcigim Nov 3, 2025
d0b553f
Make nextPlot access bounds-aware for readability
feyyazcigim Nov 3, 2025
dc9716c
Extract PlotGroup/Overlay and fix context-aware coloring
feyyazcigim Nov 3, 2025
7710006
Replace inline width styles with Tailwind
feyyazcigim Nov 3, 2025
8bbffcd
Memoize grid points and axis labels to avoid redundant recalculations
feyyazcigim Nov 3, 2025
d95b2a2
Extract onPlotGroupSelect into memoized handler for readability
feyyazcigim Nov 3, 2025
33dc443
Extract duplicate balance mode switch-case into reusable helper function
feyyazcigim Nov 3, 2025
56e88eb
Fix listing table redirect
feyyazcigim Nov 3, 2025
a9baa53
Refactor and resolve QA
feyyazcigim Nov 9, 2025
48a0338
Make Market Action Menu sticky, and add users pod line to Market Page
feyyazcigim Nov 9, 2025
f26c340
Add pagination to Market Table
feyyazcigim Nov 9, 2025
d87a4f0
Add slider to ComboInputField and implement optional marks
feyyazcigim Nov 10, 2025
531805f
Fix slider styling
feyyazcigim Nov 10, 2025
c6f3f90
Select plot group from Market
feyyazcigim Nov 12, 2025
412fd0e
Change place in line multi slider to slider on FillListing
feyyazcigim Nov 12, 2025
aeacec2
Add overlay to MarketChart
feyyazcigim Nov 13, 2025
8ff7eda
Listing overlay
feyyazcigim Nov 13, 2025
0e9d9e2
Implement pod score and color feature
feyyazcigim Nov 14, 2025
4b77596
Fix lint errors
feyyazcigim Nov 22, 2025
fd685e9
Fix overlay calculations
feyyazcigim Nov 23, 2025
7ed7eea
Fix chart overlay resize issue
feyyazcigim Nov 24, 2025
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
180 changes: 163 additions & 17 deletions src/components/ComboInputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,32 @@ import PlotSelect from "./PlotSelect";
import TextSkeleton from "./TextSkeleton";
import TokenSelectWithBalances, { TransformTokenLabelsFunction } from "./TokenSelectWithBalances";
import { Button } from "./ui/Button";
import { Input } from "./ui/Input";
import { Skeleton } from "./ui/Skeleton";
import { Slider } from "./ui/Slider";

const ETH_GAS_RESERVE = TokenValue.fromHuman("0.0003333333333", 18); // Reserve $1 of gas if eth is $3k

/**
* Convert slider value (0-100) to TokenValue
*/
const sliderToTokenValue = (sliderValue: number, maxAmount: TokenValue): TokenValue => {
const percentage = sliderValue / 100;
return maxAmount.mul(percentage);
};

/**
* Convert TokenValue to slider value (0-100)
*/
const tokenValueToSlider = (tokenValue: TokenValue, maxAmount: TokenValue): number => {
if (maxAmount.eq(0)) return 0;
return tokenValue.div(maxAmount).mul(100).toNumber();
};

const TextAdornment = ({ text, className }: { text: string; className?: string }) => {
return <div className={cn("text-black pinto-sm-light mr-2", className)}>{text}</div>;
};

export interface ComboInputProps extends InputHTMLAttributes<HTMLInputElement> {
// Token mode props
setToken?: Dispatch<SetStateAction<Token>> | ((token: Token) => void);
Expand Down Expand Up @@ -75,6 +97,10 @@ export interface ComboInputProps extends InputHTMLAttributes<HTMLInputElement> {

// Token select props
transformTokenLabels?: TransformTokenLabelsFunction;

// Slider props
enableSlider?: boolean;
sliderMarkers?: number[];
}

function ComboInputField({
Expand Down Expand Up @@ -112,6 +138,8 @@ function ComboInputField({
selectKey,
transformTokenLabels,
placeholder,
enableSlider,
sliderMarkers,
}: ComboInputProps) {
const tokenData = useTokenData();
const { balances } = useFarmerBalances();
Expand Down Expand Up @@ -166,30 +194,52 @@ function ComboInputField({
: selectedTokenPrice.mul(disableInput ? amountAsTokenValue : internalAmount)
: undefined;

// Helper to get balance from farmerTokenBalance based on balanceFrom mode
const getFarmerBalanceByMode = useCallback(
(farmerBalance: typeof farmerTokenBalance, mode: FarmFromMode | undefined): TokenValue => {
if (!farmerBalance) return TokenValue.ZERO;
switch (mode) {
case FarmFromMode.EXTERNAL:
return farmerBalance.external || TokenValue.ZERO;
case FarmFromMode.INTERNAL:
return farmerBalance.internal || TokenValue.ZERO;
default:
return farmerBalance.total || TokenValue.ZERO;
}
},
[],
);

const maxAmount = useMemo(() => {
if (mode === "plots" && selectedPlots) {
return selectedPlots.reduce((total, plot) => total.add(plot.pods), TokenValue.ZERO);
}

if (customMaxAmount) {
return customMaxAmount;
}

// Get base balance first
let baseBalance = TokenValue.ZERO;
if (tokenAndBalanceMap && selectedToken) {
return tokenAndBalanceMap.get(selectedToken) ?? TokenValue.ZERO;
baseBalance = tokenAndBalanceMap.get(selectedToken) ?? TokenValue.ZERO;
} else if (farmerTokenBalance) {
baseBalance = getFarmerBalanceByMode(farmerTokenBalance, balanceFrom);
}

if (!farmerTokenBalance) return TokenValue.ZERO;

switch (balanceFrom) {
case FarmFromMode.EXTERNAL:
return farmerTokenBalance.external || TokenValue.ZERO;
case FarmFromMode.INTERNAL:
return farmerTokenBalance.internal || TokenValue.ZERO;
default:
return farmerTokenBalance.total || TokenValue.ZERO;
// If customMaxAmount is provided and greater than 0, use the minimum of base balance and customMaxAmount
if (customMaxAmount?.gt(0)) {
return TokenValue.min(baseBalance, customMaxAmount);
}
}, [mode, selectedPlots, customMaxAmount, tokenAndBalanceMap, selectedToken, balanceFrom, farmerTokenBalance]);

// Otherwise use base balance
return baseBalance;
}, [
mode,
selectedPlots,
customMaxAmount,
tokenAndBalanceMap,
selectedToken,
balanceFrom,
farmerTokenBalance,
getFarmerBalanceByMode,
]);

const balance = useMemo(() => {
if (mode === "plots" && selectedPlots) {
Expand All @@ -200,8 +250,9 @@ function ComboInputField({
return tokenAndBalanceMap.get(selectedToken) ?? TokenValue.ZERO;
}
}
return maxAmount;
}, [mode, selectedPlots, tokenAndBalanceMap, selectedToken, maxAmount]);
// Always use farmerTokenBalance for display, not maxAmount (which may be limited by customMaxAmount)
return getFarmerBalanceByMode(farmerTokenBalance, balanceFrom);
}, [mode, selectedPlots, tokenAndBalanceMap, selectedToken, farmerTokenBalance, balanceFrom, getFarmerBalanceByMode]);

/**
* Clamp the input amount to the max amount ONLY IF clamping is enabled
Expand Down Expand Up @@ -247,6 +298,18 @@ function ComboInputField({
[connectedAccount, setError],
);

/**
* Reset amount when token changes
*/
useEffect(() => {
setInternalAmount(TokenValue.ZERO);
setDisplayValue("0");
if (setAmount) {
setAmount("0");
lastInternalAmountRef.current = "0";
}
}, [selectedToken, setAmount]);

/**
* Clamp the internal amount to the max amount
* - ONLY when the selected token changes
Expand Down Expand Up @@ -413,6 +476,58 @@ function ComboInputField({
return sortedPlots.map((plot) => truncateHex(plot.idHex)).join(", ");
}, [selectedPlots]);

// Calculate slider value from internal amount
const sliderValue = useMemo(() => {
if (!enableSlider || maxAmount.eq(0)) return 0;
return tokenValueToSlider(internalAmount, maxAmount);
}, [enableSlider, internalAmount, maxAmount]);

// Handle slider value changes
const handleSliderChange = useCallback(
(values: number[]) => {
if (disableInput || !enableSlider) return;

const sliderVal = values[0] ?? 0;
const tokenVal = sliderToTokenValue(sliderVal, maxAmount);

setIsUserInput(true);
setInternalAmount(tokenVal);
setDisplayValue(tokenVal.toHuman());
handleSetError(tokenVal.gt(maxAmount));
},
[disableInput, enableSlider, maxAmount, handleSetError],
);

// Handle percentage input changes
const handlePercentageChange = useCallback(
(value: string) => {
if (disableInput || !enableSlider) return;

// Allow empty string or valid number input
if (value === "") {
const tokenVal = TokenValue.ZERO;
setIsUserInput(true);
setInternalAmount(tokenVal);
setDisplayValue(tokenVal.toHuman());
return;
}

// Sanitize and validate percentage input (0-100)
const numValue = parseFloat(value);
if (Number.isNaN(numValue)) return;

// Clamp between 0 and 100
const clampedPct = Math.max(0, Math.min(100, numValue));
const tokenVal = sliderToTokenValue(clampedPct, maxAmount);

setIsUserInput(true);
setInternalAmount(tokenVal);
setDisplayValue(tokenVal.toHuman());
handleSetError(tokenVal.gt(maxAmount));
},
[disableInput, enableSlider, maxAmount, handleSetError],
);

return (
<>
<div
Expand Down Expand Up @@ -545,6 +660,37 @@ function ComboInputField({
</div>
</div>
)}
{enableSlider && (
<div className="mt-2">
<div className="flex flex-row items-center gap-3">
<div className="flex-1">
<Slider
min={0}
max={100}
step={0.1}
value={[sliderValue]}
onValueChange={handleSliderChange}
markers={sliderMarkers}
disabled={disableInput || maxAmount.eq(0)}
/>
</div>
<div className="flex items-center gap-1">
<Input
type="text"
inputMode="numeric"
value={sliderValue ? formatter.noDec(sliderValue) : ""}
onChange={(e) => handlePercentageChange(e.target.value)}
onFocus={(e) => e.target.select()}
placeholder={formatter.noDec(sliderValue)}
outlined
containerClassName="w-[6rem]"
disabled={disableInput || maxAmount.eq(0)}
endIcon={<TextAdornment text="%" className="bg-white" />}
/>
</div>
</div>
</div>
)}
</div>
</div>
</>
Expand Down
Loading