Skip to content
Merged
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
11 changes: 3 additions & 8 deletions apps/telegram-ecash-escrow/src/app/shopping/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export default function Shopping() {

<Section>
<Typography className="title-offer" variant="body1" component="div">
<span>Goods & Services</span>
<span>Offers</span>
{isShowSortIcon && (
<SortIcon
style={{ cursor: 'pointer', color: `${isSorted ? '#0076C4' : ''}` }}
Expand All @@ -201,11 +201,7 @@ export default function Shopping() {
</span>
)}
</Typography>
<div
id="scrollableDiv"
className="offer-list"
style={{ overflow: 'auto', maxHeight: 'calc(100vh - 250px)' }}
>
<div className="offer-list">
{!isLoadingFilter ? (
<InfiniteScroll
dataLength={dataFilter.length}
Expand All @@ -217,11 +213,10 @@ export default function Shopping() {
<Skeleton variant="text" />
</>
}
scrollableTarget="scrollableDiv"
scrollThreshold={'100px'}
>
{dataFilter.map(item => {
return <OfferItem key={item.id} timelineItem={item as TimelineQueryItem} />;
return <OfferItem key={item.id} timelineItem={item as TimelineQueryItem} hidePaymentMethods />;
})}
</InfiniteScroll>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ const OfferDetailInfo = ({ timelineItem, post, isShowBuyButton = false, isItemTi

const isOwner = (postData ?? post)?.accountId === selectedAccountId;

// Format fiat price without decimals and with thousands separators for display
const formatFiatPrice = (price: number | string | undefined): string => {
if (price == null || price === '') return '';
const num = typeof price === 'string' ? parseFloat(price) : price;
if (isNaN(num)) return String(price);
return new Intl.NumberFormat('en-GB', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(Math.round(num));
};

const handleClickAction = e => {
e.stopPropagation();
dispatch(openActionSheet('OfferActionSheet', { post: postData }));
Expand Down Expand Up @@ -184,7 +195,7 @@ const OfferDetailInfo = ({ timelineItem, post, isShowBuyButton = false, isItemTi
(offerData?.tickerPriceGoodsServices ?? DEFAULT_TICKER_GOODS_SERVICES) !==
DEFAULT_TICKER_GOODS_SERVICES ? (
<span>
({offerData.priceGoodsServices} {offerData.tickerPriceGoodsServices ?? 'USD'})
({formatFiatPrice(offerData.priceGoodsServices)} {offerData.tickerPriceGoodsServices ?? 'USD'})
</span>
) : null}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
'use client';

import { LIST_CURRENCIES_USED } from '@bcpros/lixi-models';
import { ChevronLeft } from '@mui/icons-material';
import {
Box,
Button,
Dialog,
DialogContent,
DialogTitle,
IconButton,
Slide,
TextField,
Typography,
useMediaQuery,
useTheme
} from '@mui/material';
import { styled } from '@mui/material/styles';
import { TransitionProps } from '@mui/material/transitions';
import React, { useId, useMemo, useState } from 'react';
import { FilterCurrencyType } from '../../store/type/types';

interface ShoppingCurrencyModalProps {
isOpen: boolean;
onDismissModal?: (value: boolean) => void;
setSelectedItem?: (value: FilterCurrencyType) => void;
}

const StyledDialog = styled(Dialog)(({ theme }) => ({
'.MuiPaper-root': {
background: theme.palette.background.default,
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
width: '500px',
height: '100vh',
maxHeight: '100%',
margin: 0,
[theme.breakpoints.down('sm')]: {
width: '100%'
}
},

'.MuiIconButton-root': {
width: 'fit-content',
svg: {
fontSize: '32px'
}
},

'.MuiDialogTitle-root': {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',

'.back-btn': {
position: 'absolute',
left: '10px'
},

'.btn-clear': {
color: '#FFF',
position: 'absolute',
right: '10px',
fontSize: '12px',
padding: '1px 5px'
}
},

'.MuiDialogContent-root': {
padding: '16px'
},

button: {
color: theme.palette.text.secondary
}
}));

const Transition = React.forwardRef(function Transition(
props: TransitionProps & {
children: React.ReactElement;
},
ref: React.Ref<unknown>
) {
return <Slide direction="up" ref={ref} {...props} />;
});

/**
* Simplified currency modal for Shopping tab
* Shows fiat currencies + XEC in a single list, sorted alphabetically
*/
const ShoppingCurrencyModal: React.FC<ShoppingCurrencyModalProps> = props => {
const { isOpen, onDismissModal, setSelectedItem } = props;
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));

const [searchTerm, setSearchTerm] = useState('');
const titleId = useId();

// Build combined list of fiat currencies + XEC, sorted alphabetically by code
const currencyList = useMemo(() => {
// Add XEC as a currency option
const xecOption = { code: 'XEC', name: 'eCash' };

// Combine fiat currencies with XEC
const allCurrencies = [...LIST_CURRENCIES_USED, xecOption];

// Sort alphabetically by code
return allCurrencies.sort((a, b) => a.code.localeCompare(b.code));
}, []);

// Filter currencies based on search term
const filteredCurrencies = useMemo(() => {
if (!searchTerm) return currencyList;

const lowerSearch = searchTerm.toLowerCase();
return currencyList.filter(
option => option.code.toLowerCase().includes(lowerSearch) || option.name.toLowerCase().includes(lowerSearch)
);
}, [currencyList, searchTerm]);

const handleSelect = (currency: { code: string; name: string }) => {
const filterCurrency: FilterCurrencyType = {
paymentMethod: 5, // PAYMENT_METHOD.GOODS_SERVICES
value: currency.code
};
setSelectedItem?.(filterCurrency);
onDismissModal?.(false);
setSearchTerm('');
};

const handleClear = () => {
setSelectedItem?.({ paymentMethod: 5, value: '' });
onDismissModal?.(false);
setSearchTerm('');
};

const handleClose = () => {
onDismissModal?.(false);
setSearchTerm('');
};

return (
<StyledDialog
fullScreen={fullScreen}
open={isOpen}
onClose={handleClose}
TransitionComponent={Transition}
aria-labelledby={titleId}
>
<DialogTitle id={titleId}>
<IconButton className="back-btn" onClick={handleClose} aria-label="Close">
<ChevronLeft />
</IconButton>
<Typography style={{ fontSize: '20px', fontWeight: 'bold' }}>Select currency</Typography>
<Button variant="contained" className="btn-clear" onClick={handleClear}>
Clear
</Button>
</DialogTitle>
<DialogContent>
<TextField
label="Search"
variant="filled"
fullWidth
onChange={e => setSearchTerm(e.target.value)}
value={searchTerm}
autoFocus
/>
<Box sx={{ mt: 1, maxHeight: 'calc(100vh - 200px)', overflow: 'auto' }}>
{filteredCurrencies.map(option => (
<Button
key={option.code}
onClick={() => handleSelect(option)}
fullWidth
variant="text"
style={{ textTransform: 'none', fontSize: '1.1rem' }}
sx={{ justifyContent: 'flex-start', textAlign: 'left' }}
>
{option.code} - {option.name}
</Button>
))}
{filteredCurrencies.length === 0 && (
<Typography sx={{ p: 2, textAlign: 'center', color: 'text.secondary' }}>No currencies found</Typography>
)}
</Box>
</DialogContent>
</StyledDialog>
);
};

export default ShoppingCurrencyModal;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { styled } from '@mui/material/styles';
import { debounce } from 'lodash';
import React, { useCallback, useState } from 'react';
import { NumericFormat } from 'react-number-format';
import FilterCurrencyModal from '../FilterList/FilterCurrencyModal';
import ShoppingCurrencyModal from '../FilterList/ShoppingCurrencyModal';

const WrapFilter = styled('div')(({ theme }) => ({
marginBottom: '16px',
Expand Down Expand Up @@ -228,7 +228,7 @@ const ShoppingFilterComponent: React.FC<ShoppingFilterComponentProps> = ({ filte
)}
</div>
</WrapFilter>
<FilterCurrencyModal
<ShoppingCurrencyModal
isOpen={openCurrencyList}
setSelectedItem={value => handleFilterCurrency(value)}
onDismissModal={value => setOpenCurrencyList(value)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ const OrderDetailInfo = ({ key, post }: { key: string; post: Post }) => {
isGoodsServices: _isGoodsServices
} = useOfferPrice({ paymentInfo: post?.offer, inputAmount: 1 });

// Format fiat price without decimals and with thousands separators for display
const formatFiatPrice = (price: number | string | undefined): string => {
if (price == null || price === '') return '';
const num = typeof price === 'string' ? parseFloat(price) : price;
if (isNaN(num)) return String(price);
return new Intl.NumberFormat('en-GB', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(Math.round(num));
};

return (
<OrderDetailWrap>
<Typography variant="body1">
Expand All @@ -60,7 +71,7 @@ const OrderDetailInfo = ({ key, post }: { key: string; post: Post }) => {
(post.offer?.tickerPriceGoodsServices ?? DEFAULT_TICKER_GOODS_SERVICES) !==
DEFAULT_TICKER_GOODS_SERVICES ? (
<span>
({post.offer.priceGoodsServices} {post.offer.tickerPriceGoodsServices ?? 'USD'})
({formatFiatPrice(post.offer.priceGoodsServices)} {post.offer.tickerPriceGoodsServices ?? 'USD'})
</span>
) : null}
</>
Expand Down
18 changes: 15 additions & 3 deletions apps/telegram-ecash-escrow/src/components/OfferItem/OfferItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ const OfferShowWrapItem = styled('div')(({ theme }) => ({

type OfferItemProps = {
timelineItem?: TimelineQueryItem;
hidePaymentMethods?: boolean;
};

export default function OfferItem({ timelineItem }: OfferItemProps) {
export default function OfferItem({ timelineItem, hidePaymentMethods = false }: OfferItemProps) {
const { status } = useSession();
const askAuthorization = useAuthorization();
const searchParams = useSearchParams();
Expand All @@ -164,6 +165,17 @@ export default function OfferItem({ timelineItem }: OfferItemProps) {
const settingContext = useContext(SettingContext);
const seedBackupTime = settingContext?.setting?.lastSeedBackupTime ?? lastSeedBackupTimeOnDevice ?? '';

// Format fiat price without decimals and with thousands separators for display
const formatFiatPrice = (price: number | string | undefined): string => {
if (price == null || price === '') return '';
const num = typeof price === 'string' ? parseFloat(price) : price;
if (isNaN(num)) return String(price);
return new Intl.NumberFormat('en-GB', {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(Math.round(num));
};

const { useGetAccountByAddressQuery } = accountsApi;
const { currentData: accountQueryData } = useGetAccountByAddressQuery(
{ address: selectedWalletPath?.xAddress },
Expand Down Expand Up @@ -337,7 +349,7 @@ export default function OfferItem({ timelineItem }: OfferItemProps) {
)}
</CardContent>
</Collapse>
<CardContent>{OfferItemPaymentMethod}</CardContent>
{!hidePaymentMethods && <CardContent>{OfferItemPaymentMethod}</CardContent>}

<Typography component={'div'} className="action-section">
<Typography variant="body2">
Expand All @@ -350,7 +362,7 @@ export default function OfferItem({ timelineItem }: OfferItemProps) {
(offerData?.tickerPriceGoodsServices ?? DEFAULT_TICKER_GOODS_SERVICES) !==
DEFAULT_TICKER_GOODS_SERVICES ? (
<span>
({offerData.priceGoodsServices} {offerData.tickerPriceGoodsServices ?? 'USD'})
({formatFiatPrice(offerData.priceGoodsServices)} {offerData.tickerPriceGoodsServices ?? 'USD'})
</span>
) : null}
</span>
Expand Down
9 changes: 7 additions & 2 deletions apps/telegram-ecash-escrow/src/hooks/useOfferPrice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ export default function useOfferPrice({ paymentInfo, inputAmount = 1 }: UseOffer
const priceOf1XECInLocalCurrency = xecRateEntry.rate;
const priceOf1MXECInLocalCurrency = priceOf1XECInLocalCurrency * 1000000;

setAmountXECGoodsServices(1); // 1 XEC
// Round to 2 decimal places for XEC display
const roundedXEC = Math.round(1 * 100) / 100;
setAmountXECGoodsServices(roundedXEC);
setAmountPer1MXEC(
formatAmountFor1MXEC(priceOf1MXECInLocalCurrency, paymentInfo?.marginPercentage, coinCurrency, isBuyOffer)
);
Expand All @@ -212,7 +214,10 @@ export default function useOfferPrice({ paymentInfo, inputAmount = 1 }: UseOffer
? paymentInfo.priceGoodsServices
: 1; // Default to 1 XEC for legacy offers without price

setAmountXECGoodsServices(displayPrice);
// Round XEC to maximum 2 decimal places
const roundedPrice = Math.round(displayPrice * 100) / 100;

setAmountXECGoodsServices(roundedPrice);
setAmountPer1MXEC(
formatAmountFor1MXEC(amountCoinOrCurrency, paymentInfo?.marginPercentage, coinCurrency, isBuyOffer)
);
Expand Down
4 changes: 4 additions & 0 deletions apps/telegram-ecash-escrow/src/store/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export const LIST_TICKER_GOODS_SERVICES = [
{
id: 2,
name: 'USD'
},
{
id: 3,
name: 'VND'
}
];
export const DEFAULT_TICKER_GOODS_SERVICES = 'XEC';
Expand Down
Loading