Open Klapeyron opened 4 months ago
@autopilot
To align prices by the decimal point across various components, we need to ensure that the rendering logic and CSS are adjusted accordingly. This involves:
Create a CSS class that aligns text by the decimal point. This can be done using a combination of display: inline-block
and text-align: right
.
.decimal-align {
display: inline-block;
text-align: right;
width: 100%;
}
Ensure that the formatting functions in utils/numberToCurrencyFormat.ts
produce strings where the decimal points are aligned.
utils/numberToCurrencyFormat.ts
export const numberToCurrencyFormat = (amount: number, decimalPlaces: number, maxIntegerDigits: number = 10) => {
const parts = amount
.toLocaleString('en', {
minimumFractionDigits: decimalPlaces,
maximumFractionDigits: decimalPlaces,
useGrouping: false,
})
.split('.');
const integerPart = parts[0].padStart(maxIntegerDigits, ' ');
const decimalPart = parts[1] || '0'.repeat(decimalPlaces);
return `${integerPart}.${decimalPart}`;
};
export const toFixedNoRounding = (num: number, decimalPlaces: number) => {
const regex = new RegExp(`^-?\\d+(?:\\.\\d{0,${decimalPlaces}})?`);
const match = num.toString().match(regex);
if (match) {
const parts = match[0].split('.');
const integerPart = parts[0];
const decimalPart = (parts[1] || '').padEnd(decimalPlaces, '0');
return `${integerPart}.${decimalPart}`;
}
return num.toFixed(decimalPlaces);
};
components/Market/OrderBook/RecentTradesItem.tsx
import { chosenMarketAtom } from '@/store/store';
import { RecentPositionType } from '@/types/positionTypes';
import { getPrecision } from '@/utils/getPrecision';
import { format, parseISO } from 'date-fns';
import { useAtom } from 'jotai';
interface RecentTradesItemProps {
position: RecentPositionType;
}
export const RecentTradesItem = ({ position }: RecentTradesItemProps) => {
const [chosenMarket] = useAtom(chosenMarketAtom);
const precision = getPrecision(Number(chosenMarket?.tickSize));
const date = parseISO(position.timestamp);
return (
<div className='py-1 flex items-center justify-between text-tetriary'>
<p>{format(date, 'd MMMM HH:mm:ss')}</p>
<div className='flex items-center'>
<div className='w-[64px] text-right'>
<p className='decimal-align'>{Number(position.createPrice).toFixed(precision)}</p>
</div>
<div className='w-[64px] text-right'>
<p>{Number(position.quantity)}</p>
</div>
</div>
</div>
);
};
components/Market/TradingHub/TradingHubOrders/TradingHubOrdersItem.tsx
import { OrderType } from '@/types/orderTypes';
import { SideLabel } from '../SideLabel';
import { useCancelOrder } from '@/blockchain/hooks/useCancelOrder';
import { format, parseISO } from 'date-fns';
import { getMarkeDetails } from '@/utils/getMarketDetails';
import { useAccount } from 'wagmi';
import { useEffect, useState } from 'react';
import { fetchOrderCollateral } from '@/utils/fetchOrderCollateral';
import { numberToCurrencyFormat } from '@/utils/numberToCurrencyFormat';
interface TradingHubOrdersItemProps {
order: OrderType;
}
export const TradingHubOrdersItem = ({ order }: TradingHubOrdersItemProps) => {
const { write: writeCancelOrder } = useCancelOrder(order.market.id, order.id);
const date = parseISO(order.timestamp);
const marketDetails = getMarkeDetails(order.market.ticker);
const { address } = useAccount();
const [state, setState] = useState<number>();
return (
<tr className={`text-sm odd:bg-[#23252E] text-[#7F828F] overflow-x-scroll sm:text-xs`}>
<td className='pl-3 py-2'>
<SideLabel side={order.side} />
</td>
<td>{format(date, 'd MMMM yyyy HH:mm:ss')}</td>
<td>{marketDetails?.name}</td>
<td className="price-column">{numberToCurrencyFormat(order.price, 2)}</td>
<td>{Number(order.quantity)}</td>
<td className='text-right pr-3'>
<button
className={`font-bold text-xs text-[#D26D6C] transition ease-in-out hover:text-[#C53F3A] duration-300`}
onClick={() => writeCancelOrder()}
>
CANCEL
</button>
</td>
</tr>
);
};
components/Market/OrderBook/OrderBookItem.tsx
import { useEffect, useState } from 'react';
import { OrderSide } from './OrderBook';
import { OrderBookOrder } from '@/types/orderTypes';
import { fetchOrderCollateral } from '@/utils/fetchOrderCollateral';
import { useAccount } from 'wagmi';
import { useAtom } from 'jotai';
import { chosenMarketAtom } from '@/store/store';
import { getPrecision } from '@/utils/getPrecision';
interface OrderBookItem {
empty: boolean;
side: OrderSide;
data?: OrderBookOrder;
}
export const OrderBookItem = ({ empty, side, data }: OrderBookItem) => {
const [chosenMarket] = useAtom(chosenMarketAtom);
const precision = getPrecision(Number(chosenMarket?.tickSize));
const formatPrice = (price: number) => {
const [integerPart, fractionalPart] = price.toFixed(precision).split('.');
return { integerPart, fractionalPart };
};
return (
<div
className={`w-full bg-opacity-[15%] text-opacity-50 py-2 ${
side === OrderSide.LONG
? 'bg-green-900 text-tetriary'
: 'bg-[#C53F3A] text-tetriary'
}`}
>
{!empty && data && (
<div className='w-full justify-between h-full flex px-4 text-xs'>
<div className='text-right min-w-[60px] flex'>
{(() => {
const { integerPart, fractionalPart } = formatPrice(Number(data.price));
return (
<>
<span>{integerPart}</span>
<span>.{fractionalPart}</span>
</>
);
})()}
</div>
<div>{data.quantity}</div>
</div>
)}
</div>
);
};
components/Market/TradingHub/TradingHubPositions/TradingHubPositionsItem.tsx
import { PositionWithSide } from '@/types/positionTypes';
import { useState } from 'react';
import { SideLabel } from '../SideLabel';
import { truncateAddress } from '@/utils/truncateAddress';
import { getOpponentMarginData } from '@/utils/getOpponentMarginData';
import { opponentsMarginsAtom } from '../../Market';
import { useAtom } from 'jotai';
import { LiquidationStatusTab } from '../../LiquidationStatusTab';
import { LiquidationStatusType } from '@/blockchain/hooks/useUserMargin';
import { Tooltip } from 'react-tooltip';
import { useMarkToMarket } from '@/blockchain/hooks/useMarkToMarket';
import { ClosePositionModal } from './ClosePositionModal';
import { currencySymbol } from '@/blockchain/constants';
import { getMarkeDetails } from '@/utils/getMarketDetails';
import { FaMessage } from 'react-icons/fa6';
import { tradingHubStateAtom } from '@/store/store';
interface TradingHubPositionsItemProps {
position: PositionWithSide;
isNotAggregated?: boolean;
}
export const TradingHubPositionsItem = ({
position,
isNotAggregated,
}: TradingHubPositionsItemProps) => {
const oraclePrice = position.market.oraclePrice;
const opponent = position.side === 'LONG' ? position.short : position.long;
const calculatedProfitOrLoss =
position.side === 'LONG'
? Number(position.quantityLeft) *
Number(position.market.contractUnit) *
(Number(oraclePrice.toString()) -
Number(position.createPriceLong.toString()))
: Number(position.quantityLeft) *
Number(position.market.contractUnit) *
(Number(position.createPriceShort.toString()) -
Number(oraclePrice.toString()));
const { write: writeMarkToMarket } = useMarkToMarket(
position.market.id,
position.id
);
const [opponentsMargin] = useAtom(opponentsMarginsAtom);
const marginData = getOpponentMarginData(
opponentsMargin,
opponent,
position.market.id
);
const [isModalOpened, setIsModalOpened] = useState<boolean>(false);
const handleCloseModal = () => {
setIsModalOpened(false);
};
const marketDetails = getMarkeDetails(position.market.ticker);
const [_, setTradingHubState] = useAtom(tradingHubStateAtom);
const formatPrice = (price: number) => {
const [integerPart, decimalPart] = price.toFixed(2).split('.');
return { integerPart, decimalPart };
};
const createPrice = position.side === 'LONG'
? formatPrice(Number(position.createPriceLong))
: formatPrice(Number(position.createPriceShort));
const currentPrice = formatPrice(Number(position.price));
return (
<tr
className={`text-sm even:bg-[#23252E] text-[#7F828F] overflow-x-scroll
}`}
>
<td className='px-6 sm:pr-0 sm:pl-3 py-3'>
<SideLabel side={position.side} />
</td>
{isNotAggregated && (
<td className='text-[10px] sm:text-xs px-6 sm:px-0'>
{marketDetails?.name}
</td>
)}
<td className='text-[10px] sm:text-xs px-6 sm:px-0'>
{Number(position.quantityLeft)}
</td>
<td className='text-[10px] sm:text-xs px-6 sm:px-0 decimal-align' data-decimal={createPrice.decimalPart}>
{createPrice.integerPart}
</td>
<td className='text-[10px] sm:text-xs px-6 sm:px-0 decimal-align' data-decimal={currentPrice.decimalPart}>
{currentPrice.integerPart}
</td>
<td
className={`${
calculatedProfitOrLoss < 0
? 'text-red-500'
: 'text-[#73D391] font-semibold'
} text-[10px] sm:text-xs px-6 sm:px-0`}
>
{calculatedProfitOrLoss.toFixed(2)}{' '}
<span className={`text-[10px] sm:text-xs`}>{currencySymbol}</span>
</td>
<td className='align-middle px-6 sm:px-0 '>
<div className='flex items-center space-x-2'>
<p className='text-[10px] sm:text-xs'>{truncateAddress(opponent)}</p>
<LiquidationStatusTab
status={marginData?.liquidationStatus! as LiquidationStatusType}
small
/>
{/* CHAT LOGIC */}
{/* <button onClick={() => setTradingHubState('chat')}>
<FaMessage className='text-sm' />
</button> */}
{/* */}
</div>
</td>
<td className=' text-right pr-3 hidden sm:table-cell'>
<a
data-tooltip-id='m2m-tooltip'
data-tooltip-html='Mark-to-Market (MTM): Instantly updates your</br> asset values based on current market conditions.</br> On our peer-to-peer market, this action is </br>executed on demand, ensuring transparency without</br> daily automatic adjustments.'
>
<button
className='mr-4 text-xs font-semibold text-[#4ECB7D] hover:underline'
onClick={() => writeMarkToMarket()}
>
MTM
</button>
</a>
<button
onClick={() => setIsModalOpened(true)}
className={`font-bold text-xs hover:underline transition ease-in-out text-[#C53F3A] duration-300`}
>
CLOSE
</button>
</td>
<td className='sm:hidden sm:pr-3 sm:pl-0 text-right px-6 '>
{' '}
<a
data-tooltip-id='m2m-tooltip'
data-tooltip-html='Mark-to-Market (MTM): Instantly updates your</br> asset values based on current market conditions.</br> On our peer-to-peer market, this action is </br>executed on demand, ensuring transparency without</br> daily automatic adjustments.'
>
<button
className='mr-4 text-[10px] sm:text-xs font-semibold text-[#4ECB7D] hover:underline '
onClick={() => writeMarkToMarket()}
>
MTM
</button>
</a>
</td>
<td className='sm:hidden sm:pr-3 sm:pl-0 px-6 '>
<button
onClick={() => setIsModalOpened(true)}
className={`font-bold text-[10px] sm:text-xs hover:underline transition ease-in-out text-[#C53F3A] duration-300`}
>
CLOSE
</button>
</td>
<Tooltip id='m2m-tooltip' />
<ClosePositionModal
handleCloseModal={handleCloseModal}
isModalOpened={isModalOpened}
position={position}
/>
</tr>
);
};
This approach ensures that prices are consistently aligned by the decimal point across different components, improving readability and consistency throughout the application.
Click here to create a Pull Request with the proposed solution
Files used for this task:
{format(date, 'd MMMM HH:mm:ss')}
{Number(position.createPrice).toFixed(precision)}
{Number(position.quantity)}
{truncateAddress(opponent)}
Is your feature request related to a problem? Please describe. Would be great to align prices in all the places by decimal point
Describe the solution you'd like
@autopilot