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
7 changes: 5 additions & 2 deletions src/apps/token/components/dex-table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import styles from './styles.module.scss';
interface DexTableProps {
tokens?: TokenData[];
onTokenClick?: (tokenAddress: string) => void;
onTradeClick?: (tokenAddress: string) => void;
}

export const DexTable = ({ tokens, onTokenClick }: DexTableProps) => {
export const DexTable = ({ tokens, onTokenClick, onTradeClick }: DexTableProps) => {
const [sortConfig, setSortConfig] = useState<SortConfig>({ key: null, direction: null });

const handleSortClick = (key: string) => {
Expand All @@ -28,7 +29,9 @@ export const DexTable = ({ tokens, onTokenClick }: DexTableProps) => {
return <EmptyState />;
}

return sortedTokens.map((token) => <TableRow key={token.id} token={token} onTokenClick={onTokenClick} />);
return sortedTokens.map((token) => (
<TableRow key={token.id} token={token} onTokenClick={onTokenClick} onTradeClick={onTradeClick} />
));
};

return (
Expand Down
17 changes: 9 additions & 8 deletions src/apps/token/components/table-row/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import styles from './styles.module.scss';
interface TableRowProps {
token: TokenData;
onTokenClick?: (tokenAddress: string) => void;
onTradeClick?: (tokenAddress: string) => void;
}

export const TableRow = ({ token, onTokenClick }: TableRowProps) => {
export const TableRow = ({ token, onTokenClick, onTradeClick }: TableRowProps) => {
const [imageError, setImageError] = useState(false);

const handleClick = () => {
Expand All @@ -22,6 +23,12 @@ export const TableRow = ({ token, onTokenClick }: TableRowProps) => {
setImageError(true);
};

const handleTradeClick = () => {
if (onTradeClick) {
onTradeClick(token.address);
}
};

const renderTokenIcon = () => {
if (token.iconUrl && !imageError) {
return (
Expand Down Expand Up @@ -62,13 +69,7 @@ export const TableRow = ({ token, onTokenClick }: TableRowProps) => {
<div className={styles.MarketCap}>{formatMarketCap(token.marketCap)}</div>
</td>
<td className={styles.TradeColumn}>
<Button
variant={ButtonVariant.Primary}
onPress={() => {
// TODO: Implement trade functionality
}}
className={styles.TradeButton}
>
<Button variant={ButtonVariant.Primary} onPress={handleTradeClick} className={styles.TradeButton}>
Trade
</Button>
</td>
Expand Down
244 changes: 244 additions & 0 deletions src/apps/token/components/trading-form/FormInputs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import React, { useEffect } from 'react';
import { Input } from '@zero-tech/zui/components/Input/Input';
import { Button, Variant as ButtonVariant } from '@zero-tech/zui/components/Button';
import { TradingFormData } from './utils';
import { ZBancToken } from '../utils';
import { useAmountForDeposit, useAmountForMint, usePriceQuote } from '../../hooks/useZBancTrading';

import styles from './styles.module.scss';

interface FormInputsProps {
token: ZBancToken;
formData: TradingFormData;
isSubmitting: boolean;
onInputChange: (field: keyof TradingFormData) => (value: string) => void;
onTradeTypeChange: (tradeType: 'buy' | 'sell') => void;
onCalculationStateChange?: (isCalculating: boolean, hasError: boolean) => void;
}

export const FormInputs = ({
token,
formData,
isSubmitting,
onInputChange,
onTradeTypeChange,
onCalculationStateChange,
}: FormInputsProps) => {
// Helper to check if we should fetch data
const shouldFetchData = !!formData.amount && !!token.address;
const isBuyMode = formData.tradeType === 'buy';
const isDepositMode = formData.mode === 'deposit';
const isMintMode = formData.mode === 'mint';
const isSellMode = formData.tradeType === 'sell';

const {
data: depositAmount,
isLoading: depositLoading,
error: depositError,
} = useAmountForDeposit(
{
tokenAddress: token.address,
amount: formData.amount,
},
shouldFetchData && isBuyMode && isDepositMode
);

const {
data: mintAmount,
isLoading: mintLoading,
error: mintError,
} = useAmountForMint(
{
tokenAddress: token.address,
shares: formData.amount,
},
shouldFetchData && isBuyMode && isMintMode
);

const {
data: sellQuote,
isLoading: sellLoading,
error: sellError,
} = usePriceQuote(
{
tokenAddress: token.address,
amount: formData.amount,
direction: 'sell',
},
shouldFetchData && isSellMode
);

const isCalculating = depositLoading || mintLoading || sellLoading;
const hasCalculationError = !!depositError || !!mintError || !!sellError;

// Notify parent of calculation state changes
useEffect(() => {
if (onCalculationStateChange) {
onCalculationStateChange(isCalculating, hasCalculationError);
}
}, [isCalculating, hasCalculationError, onCalculationStateChange]);

const getAmountLabel = () => {
if (isBuyMode) {
return isDepositMode ? token.asset.symbol : token.symbol;
}
return token.symbol;
};

const getHelperText = () => {
if (isBuyMode) {
const action = isDepositMode ? 'deposit' : 'mint';
const symbol = isDepositMode ? token.asset.symbol : token.symbol;
return `Enter the amount of ${symbol} to ${action}`;
}
return `Enter the amount of ${token.symbol} tokens to sell`;
};

return (
<div className={styles.InputsSection}>
<div className={styles.TradeTypeSelector}>
<Button
variant={formData.tradeType === 'buy' ? ButtonVariant.Primary : ButtonVariant.Secondary}
onPress={() => onTradeTypeChange('buy')}
className={styles.TradeTypeButton}
disabled={isSubmitting}
>
Buy
</Button>
<Button
variant={formData.tradeType === 'sell' ? ButtonVariant.Primary : ButtonVariant.Secondary}
onPress={() => onTradeTypeChange('sell')}
className={styles.TradeTypeButton}
disabled={isSubmitting}
>
Sell
</Button>
</div>

<div className={styles.InputGroup}>
<label className={styles.Label}>Amount ({getAmountLabel()})</label>
<Input
value={formData.amount}
onChange={onInputChange('amount')}
placeholder='0.0'
type='number'
isDisabled={isSubmitting}
/>
<div className={styles.HelperText}>{getHelperText()}</div>
</div>

{formData.amount && (
<div className={styles.PreviewInfo}>
{isCalculating ? (
<div className={styles.PreviewLoading}>Calculating trade details...</div>
) : hasCalculationError ? (
<>
{isBuyMode ? (
<>
{isDepositMode ? (
<>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You deposit:</span>
<span className={styles.PreviewValue}>
{formData.amount} {token.asset.symbol}
</span>
</div>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You receive:</span>
<span className={styles.PreviewValue}>-</span>
</div>
</>
) : (
<>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You want to mint:</span>
<span className={styles.PreviewValue}>
{formData.amount} {token.symbol}
</span>
</div>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You need to deposit:</span>
<span className={styles.PreviewValue}>-</span>
</div>
</>
)}
</>
) : (
<>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You receive:</span>
<span className={styles.PreviewValue}>-</span>
</div>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You pay:</span>
<span className={styles.PreviewValue}>
{formData.amount} {token.symbol}
</span>
</div>
</>
)}
<div className={styles.PreviewError}>Unable to calculate trade details. Please try again.</div>
</>
) : (
<>
{isBuyMode ? (
<>
{isDepositMode ? (
<>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You deposit:</span>
<span className={styles.PreviewValue}>
{formData.amount} {token.asset.symbol}
</span>
</div>
{depositAmount && (
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You receive:</span>
<span className={styles.PreviewValue}>
{depositAmount.mintAmount} {depositAmount.tokenSymbol}
</span>
</div>
)}
</>
) : (
<>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You want to mint:</span>
<span className={styles.PreviewValue}>
{formData.amount} {token.symbol}
</span>
</div>
{mintAmount && (
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You need to deposit:</span>
<span className={styles.PreviewValue}>
{mintAmount.depositAmount} {mintAmount.assetSymbol}
</span>
</div>
)}
</>
)}
</>
) : (
<>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You receive:</span>
<span className={styles.PreviewValue}>
{sellQuote ? sellQuote.outputAmount : formData.amount} {token.asset.symbol}
</span>
</div>
<div className={styles.PreviewItem}>
<span className={styles.PreviewLabel}>You pay:</span>
<span className={styles.PreviewValue}>
{formData.amount} {token.symbol}
</span>
</div>
</>
)}
</>
)}
</div>
)}
</div>
);
};
26 changes: 26 additions & 0 deletions src/apps/token/components/trading-form/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { Button, Variant as ButtonVariant } from '@zero-tech/zui/components/Button';
import { IconArrowLeft, IconTrendUp } from '@zero-tech/zui/icons';
import { ZBancToken } from '../utils';
import styles from './styles.module.scss';

interface HeaderProps {
token: ZBancToken;
onBack: () => void;
}

export const Header = ({ token, onBack }: HeaderProps) => {
return (
<div className={styles.Header}>
<Button variant={ButtonVariant.Secondary} onPress={onBack} className={styles.BackButton}>
<IconArrowLeft size={16} />
Back
</Button>
<div className={styles.Title}>
<IconTrendUp size={24} className={styles.TrendingIcon} />
Trade {token.symbol}
</div>
<div className={styles.Subtitle}>Buy or sell {token.name} tokens using the ZBanc bonding curve.</div>
</div>
);
};
18 changes: 18 additions & 0 deletions src/apps/token/components/trading-form/InfoBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import styles from './styles.module.scss';

export const InfoBox = () => {
return (
<div className={styles.InfoBox}>
<div className={styles.InfoTitle}>Fees</div>
<div className={styles.FeeItem}>
<span className={styles.FeeLabel}>Vault Entry Fee:</span>
<span className={styles.FeeValue}>1%</span>
</div>
<div className={styles.FeeItem}>
<span className={styles.FeeLabel}>Protocol Entry Fee:</span>
<span className={styles.FeeValue}>1%</span>
</div>
</div>
);
};
Loading