-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Update dependencies, improve swap estimation, and refactor proj…
…ect structure - Updated `react` and `react-dom` to version 18.3.1. - Added `react-tooltip` as a new dependency for enhanced tooltips. - Moved `fullPoolsData` to the appropriate components directory for better project organization. - Removed obsolete files (`doc.html`, `fullPoolsData.ts`) and cleaned up commented-out code. - Implemented a refactor in the SwapResult component to apply tooltips consistently across labels and icons. - Enhanced the gas cost tooltip to dynamically explain single-hop and multi-hop fees to improve user clarity. These changes enhance the code structure, ensure up-to-date dependencies, and improve user experience with more informative tooltips.
- Loading branch information
Showing
11 changed files
with
922 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import React, { useState, useEffect } from "react"; | ||
import fullPoolsData from "@/outputs/fullPoolsData.json"; | ||
import { fetchTokenData, getTokenName } from "@/utils/apis/tokenInfo"; | ||
|
||
interface SelectComponentProps { | ||
apiUrl?: string; | ||
setFrom?: (from: string) => void; | ||
setTo?: (to: string) => void; | ||
outputOptions?: string[]; // New prop for filtered output options | ||
} | ||
|
||
const SelectComponent2: React.FC<SelectComponentProps> = ({ | ||
apiUrl = "http://localhost:3000/api/tokens", | ||
setFrom, | ||
setTo, | ||
outputOptions = [], // Default to empty array if not provided | ||
}) => { | ||
const [fromTokens, setFromTokens] = useState<string[]>([]); | ||
const [toTokens, setToTokens] = useState<string[]>([]); | ||
const [selectedFrom, setSelectedFrom] = useState<string>(""); | ||
const [selectedTo, setSelectedTo] = useState<string>(""); | ||
const [tokenNames, setTokenNames] = useState<{ [key: string]: string }>({}); | ||
|
||
useEffect(() => { | ||
if (setFrom) { | ||
setFrom(selectedFrom); | ||
} | ||
if (setTo) { | ||
setTo(selectedTo); | ||
} | ||
}, [selectedFrom, selectedTo, setFrom, setTo]); | ||
|
||
useEffect(() => { | ||
const fetchAndSetTokenData = async () => { | ||
await fetchTokenData(apiUrl); | ||
|
||
const names = fullPoolsData | ||
.flatMap((pool) => pool.query_result.assets) | ||
.map((asset) => { | ||
const address = | ||
asset.info.token?.contract_addr || asset.info.native_token?.denom; | ||
return address ? [address, getTokenName(address) || ""] : null; | ||
}) | ||
.filter((item): item is [string, string] => item !== null) | ||
.reduce((acc, [address, name]) => { | ||
acc[address] = name; | ||
return acc; | ||
}, {} as { [key: string]: string }); | ||
|
||
setTokenNames(names); | ||
|
||
const fromOptions = fullPoolsData | ||
.flatMap((pool) => pool.query_result.assets) | ||
.map( | ||
(asset) => | ||
asset.info.token?.contract_addr || asset.info.native_token?.denom | ||
) | ||
.filter( | ||
(addressOrDenom): addressOrDenom is string => | ||
addressOrDenom !== undefined | ||
) | ||
.filter((address) => address !== "uscrt"); | ||
|
||
setFromTokens(Array.from(new Set(fromOptions))); | ||
}; | ||
|
||
fetchAndSetTokenData(); | ||
}, [apiUrl]); | ||
|
||
const handleFromSelect = (event: React.ChangeEvent<HTMLSelectElement>) => { | ||
const fromToken = event.target.value; | ||
setSelectedFrom(fromToken); | ||
|
||
if (outputOptions.length > 0) { | ||
setToTokens(outputOptions); | ||
} else { | ||
const toOptions = fullPoolsData | ||
.filter((pool) => | ||
pool.query_result.assets.some( | ||
(asset) => | ||
asset.info.token?.contract_addr === fromToken || | ||
asset.info.native_token?.denom === fromToken | ||
) | ||
) | ||
.flatMap((pool) => | ||
pool.query_result.assets.map((asset) => { | ||
const addr = | ||
asset.info.token?.contract_addr || asset.info.native_token?.denom; | ||
return addr && addr !== fromToken && tokenNames[addr] ? addr : null; | ||
}) | ||
) | ||
.filter((addr): addr is string => addr !== null) | ||
.filter((addr) => addr !== "uscrt"); | ||
|
||
setToTokens(Array.from(new Set(toOptions))); | ||
} | ||
|
||
setSelectedTo(""); // Reset the selected 'to' token | ||
}; | ||
|
||
const handleToSelect = (event: React.ChangeEvent<HTMLSelectElement>) => { | ||
setSelectedTo(event.target.value); | ||
}; | ||
|
||
return ( | ||
<div className="flex flex-col space-y-4"> | ||
<div className="flex flex-col space-y-2"> | ||
<label className="text-white">Select From</label> | ||
<select | ||
value={selectedFrom} | ||
onChange={handleFromSelect} | ||
className="px-4 py-2 border border-gray-700 rounded-lg focus:outline-none focus:ring-2 bg-adamant-app-selectTrigger focus:ring-adamant-accentBg text-white" | ||
> | ||
<option value="">Select From</option> | ||
{fromTokens.map((address) => ( | ||
<option key={address} value={address}> | ||
{tokenNames[address] ?? JSON.stringify(address)} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
<div className="flex flex-col space-y-2"> | ||
<label className="text-white">Select To</label> | ||
<select | ||
disabled={!selectedFrom} | ||
value={selectedTo} | ||
onChange={handleToSelect} | ||
className="px-4 py-2 border border-gray-700 rounded-lg focus:outline-none focus:ring-2 bg-adamant-app-selectTrigger focus:ring-adamant-accentBg text-white" | ||
> | ||
<option value="">Select To</option> | ||
{toTokens.map((address) => ( | ||
<option key={address} value={address}> | ||
{tokenNames[address] ?? JSON.stringify(address)} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default SelectComponent2; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import React from "react"; | ||
import { FiCheckCircle, FiAlertTriangle, FiAlertOctagon } from "react-icons/fi"; | ||
import { Tooltip } from "react-tooltip"; | ||
import "react-tooltip/dist/react-tooltip.css"; | ||
|
||
// Props for each SwapResultItem | ||
interface SwapResultItemProps { | ||
label: string; | ||
value: string; | ||
tooltipId: string; | ||
tooltipContent: string; | ||
valueClassName?: string; | ||
icon?: JSX.Element; | ||
} | ||
|
||
// SwapResultItem functional component | ||
const SwapResultItem: React.FC<SwapResultItemProps> = ({ | ||
label, | ||
value, | ||
tooltipId, | ||
tooltipContent, | ||
valueClassName, | ||
icon, | ||
}) => { | ||
return ( | ||
<div className="flex justify-between items-center gap-8"> | ||
<span | ||
className="text-md font-semibold text-gray-400 border-b leading-[18px] border-gray-600 hover:border-gray-500 cursor-help" | ||
data-tooltip-id={tooltipId} | ||
> | ||
{label}: | ||
</span> | ||
<div className="flex items-center"> | ||
<span | ||
className={`text-lg font-bold ${valueClassName}`} | ||
// data-tooltip-id={tooltipId} // Apply the tooltip to the value text | ||
> | ||
{value} | ||
</span> | ||
{icon && React.cloneElement(icon, { "data-tooltip-id": tooltipId })}{" "} | ||
{/* Apply the tooltip to the icon */} | ||
</div> | ||
<Tooltip id={tooltipId} place="top" content={tooltipContent} /> | ||
</div> | ||
); | ||
}; | ||
|
||
// Main SwapResult component | ||
interface SwapResultProps { | ||
bestRoute: string; | ||
idealOutput: string; | ||
actualOutput: string; | ||
priceImpact: string; | ||
lpFee: string; | ||
gasCost: string; | ||
isMultiHop: boolean; | ||
difference: string; | ||
} | ||
|
||
const SwapResult: React.FC<SwapResultProps> = ({ | ||
bestRoute, | ||
idealOutput, | ||
actualOutput, | ||
priceImpact, | ||
lpFee, | ||
gasCost, | ||
isMultiHop, | ||
difference, | ||
}) => { | ||
// Array to store the data and config for each section | ||
const data = [ | ||
{ | ||
label: "Best Route", | ||
value: bestRoute, | ||
tooltipId: "bestRouteTip", | ||
tooltipContent: | ||
"The optimal route for swapping your tokens to minimize slippage and maximize output.", | ||
valueClassName: "text-blue-400", | ||
}, | ||
{ | ||
label: "Ideal Output", | ||
value: idealOutput, | ||
tooltipId: "idealOutputTip", | ||
tooltipContent: | ||
"The best possible output you could get without any fees or slippage.", | ||
valueClassName: "text-blue-400", | ||
}, | ||
{ | ||
label: "Actual Output", | ||
value: actualOutput, | ||
tooltipId: "actualOutputTip", | ||
tooltipContent: | ||
"The actual amount of tokens you received after fees and slippage.", | ||
valueClassName: "text-green-400", | ||
}, | ||
{ | ||
label: "Difference", | ||
value: difference, | ||
tooltipId: "differenceTip", | ||
tooltipContent: | ||
"The difference between the ideal and actual output, caused by fees and slippage.", | ||
valueClassName: "text-red-400", | ||
}, | ||
{ | ||
label: "Price Impact", | ||
value: priceImpact, | ||
tooltipId: "priceImpactTip", | ||
tooltipContent: | ||
"The effect your trade has on the market price of the token pair.", | ||
valueClassName: priceImpactColor(priceImpact), | ||
icon: priceImpactIcon(priceImpact), | ||
}, | ||
{ | ||
label: "LP Fee", | ||
value: lpFee, | ||
tooltipId: "lpFeeTip", | ||
tooltipContent: | ||
"The fee paid to liquidity providers for facilitating your trade.", | ||
valueClassName: "text-yellow-300", | ||
}, | ||
{ | ||
label: "Gas Cost", | ||
value: gasCost, | ||
tooltipId: "gasCostTip", | ||
tooltipContent: isMultiHop | ||
? "This gas cost includes multiple hops. Each hop incurs a separate gas fee. The total gas cost is the sum of all hops." | ||
: "This gas cost covers a single swap between two tokens in one liquidity pool.", | ||
valueClassName: "text-gray-300", | ||
icon: <FiCheckCircle className="ml-2 w-5 h-5 cursor-help" />, // Removed tooltipId here, will be added dynamically | ||
}, | ||
]; | ||
|
||
return ( | ||
<div className="p-6 bg-gray-800 rounded-lg shadow-md text-white space-y-4 max-w-md mx-auto"> | ||
<div className="text-center"> | ||
<h2 className="text-4xl font-bold text-green-400">Swap Estimate</h2> | ||
</div> | ||
<div className="flex flex-col space-y-2"> | ||
{data.map((item, index) => ( | ||
<SwapResultItem | ||
key={index} | ||
label={item.label} | ||
value={item.value} | ||
tooltipId={item.tooltipId} | ||
tooltipContent={item.tooltipContent} | ||
valueClassName={item.valueClassName} | ||
icon={item.icon} | ||
/> | ||
))} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
// Utility functions for dynamic styling | ||
const priceImpactColor = (priceImpact: string) => { | ||
const impact = parseFloat(priceImpact); | ||
if (impact < 1) return "text-green-400"; | ||
if (impact < 3) return "text-yellow-400"; | ||
return "text-red-400"; | ||
}; | ||
|
||
const priceImpactIcon = (priceImpact: string) => { | ||
const impact = parseFloat(priceImpact); | ||
if (impact < 1.1) return <FiCheckCircle className="ml-2 w-5 h-5" />; | ||
if (impact < 3) return <FiAlertTriangle className="ml-2 w-5 h-5" />; | ||
return <FiAlertOctagon className="ml-2 w-5 h-5" />; | ||
}; | ||
|
||
export default SwapResult; |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.