Skip to content

Commit

Permalink
Merge pull request #393 from Ibinola/feat/add-history-page
Browse files Browse the repository at this point in the history
feat: add history page, implement usePosition hook to fetch table data
  • Loading branch information
djeck1432 authored Dec 19, 2024
2 parents 6d8f559 + a74ca21 commit 7e3f5b1
Show file tree
Hide file tree
Showing 9 changed files with 512 additions and 16 deletions.
28 changes: 14 additions & 14 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Stake from 'pages/vault/stake/Stake';
import { TELEGRAM_BOT_LINK } from 'utils/constants';
import { useCheckMobile } from 'hooks/useCheckMobile';
import { notifyError } from 'utils/notification';
import PositionHistory from 'pages/spotnet/position_history/PositionHistory';


function App() {
Expand All @@ -34,7 +35,6 @@ function App() {

const connectWalletMutation = useConnectWallet(setWalletId);


const handleConnectWallet = () => {
connectWalletMutation.mutate();
};
Expand Down Expand Up @@ -65,14 +65,16 @@ function App() {

useEffect(() => {
if (window.Telegram?.WebApp?.initData) {
getTelegramUserWalletId(window.Telegram.WebApp.initDataUnsafe.user.id).then((linked_wallet_id) => {
setWalletId(linked_wallet_id);
window.Telegram.WebApp.ready();
}).catch((error) => {
console.error('Error getting Telegram user wallet ID:', error);
notifyError('Error loading wallet');
window.Telegram.WebApp.ready();
});
getTelegramUserWalletId(window.Telegram.WebApp.initDataUnsafe.user.id)
.then((linked_wallet_id) => {
setWalletId(linked_wallet_id);
window.Telegram.WebApp.ready();
})
.catch((error) => {
console.error('Error getting Telegram user wallet ID:', error);
notifyError('Error loading wallet');
window.Telegram.WebApp.ready();
});
}
}, [window.Telegram?.WebApp?.initDataUnsafe]);

Expand All @@ -93,14 +95,11 @@ function App() {
/>,
document.body
)}
<Header
onConnectWallet={handleConnectWallet}
onLogout={handleLogoutModal}
/>
<Header onConnectWallet={handleConnectWallet} onLogout={handleLogoutModal} />
<main>
<Routes>
<Route index element={<SpotnetApp onConnectWallet={handleConnectWallet} onLogout={handleLogout} />} />
<Route
<Route
path="/login"
element={walletId ? <Navigate to="/" /> : <Login onConnectWallet={handleConnectWallet} />}
/>
Expand All @@ -109,6 +108,7 @@ function App() {
<Route path="/overview" element={<OverviewPage />} />
<Route path="/form" element={<Form />} />
<Route path="/documentation" element={<Documentation />} />
<Route path="/position-history" element={<PositionHistory />} />
<Route path="/stake" element={<Stake />} />
</Routes>
</main>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/assets/icons/filter-horizontal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions frontend/src/components/Card/card.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
.card {
width: 309px;
height: 101px;
background: var(--dark-background);
background: var(--bg);
border: 1px solid var(--light-purple);
border-radius: 900px;
border-radius: 12px;
padding-top: 4px;
padding-right: 24px;
padding-left: 24px;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
--gray: #83919f;
--dark-purple: #120721;
--light-blue: #74d5fd;
--status-opened: #1edc9e;
--status-closed: #433b5a;
--status-pending: #83919f;
--slider-gray: #393942;
--light-blue: #88b4fa;
--thumb-image: url('./assets/icons/slider-thumb.svg');
Expand All @@ -63,6 +66,7 @@
--dark-background: #130713;
--light-dark-background: #130713;
--text-gray: #798795;
--modal-border: #170f2e;
}

body {
Expand Down
49 changes: 49 additions & 0 deletions frontend/src/hooks/usePositionHistory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useQuery } from '@tanstack/react-query';
import { axiosInstance } from 'utils/axios';
import { formatDate } from 'utils/formatDate';
import { useWalletStore } from 'stores/useWalletStore';

const fetchPositionHistoryTable = async (walletId) => {
if (!walletId) {
throw new Error('Wallet ID is undefined');
}
const response = await axiosInstance.get(`/api/user-positions/${walletId}`);
return response.data;
};

const formatPositionHistoryTable = (data) => {
return data.map((position) => ({
...position,
amount: Number(position.amount).toFixed(2),
start_price: `$${position.start_price.toFixed(2)}`,
multiplier: position.multiplier.toFixed(1),
created_at: formatDate(position.created_at),
datetime_liquidation: formatDate(position.datetime_liquidation),
status: position.status.charAt(0).toUpperCase() + position.status.slice(1),
is_liquidated: position.is_liquidated ? 'Yes' : 'No',
}));
};

const usePositionHistoryTable = () => {
const walletId = useWalletStore((state) => state.walletId);

const { data, isPending, error } = useQuery({
queryKey: ['positionHistory', walletId],
queryFn: () => fetchPositionHistoryTable(walletId),
enabled: !!walletId,
onError: (err) => {
console.error('Error during fetching position history:', err);
},
select: formatPositionHistoryTable,
});

const showSpinner = !!walletId && isPending;

return {
data,
isPending: showSpinner,
error: walletId ? error : 'Wallet ID is required',
};
};

export { usePositionHistoryTable };
127 changes: 127 additions & 0 deletions frontend/src/pages/spotnet/position_history/PositionHistory.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React, { useState } from 'react';
import './positionHistory.css';
import { ReactComponent as HealthIcon } from 'assets/icons/health.svg';
import { ReactComponent as EthIcon } from 'assets/icons/ethereum.svg';
import { ReactComponent as StrkIcon } from 'assets/icons/strk.svg';
import { ReactComponent as UsdIcon } from 'assets/icons/usd_coin.svg';
import { usePositionHistoryTable } from 'hooks/usePositionHistory';
import Spinner from 'components/spinner/Spinner';
import filterIcon from '../../../assets/icons/filter-horizontal.svg';
import useDashboardData from 'hooks/useDashboardData';
import Card from 'components/Card/Card';
import PositionHistoryModal from 'pages/spotnet/position_history/PositionHistoryModal';

function PositionHistory() {
const [selectedPosition, setSelectedPosition] = useState(null);

const { data: tableData, isPending } = usePositionHistoryTable();
const { data: cardData } = useDashboardData();

const tokenIconMap = {
STRK: <StrkIcon className="token-icon" />,
USDC: <UsdIcon className="token-icon" />,
ETH: <EthIcon className="token-icon" />,
};

const statusStyles = {
opened: 'status-opened',
closed: 'status-closed',
pending: 'status-pending',
};

return (
<div className="position-wrapper">
<div className="position-container">
<h1 className="position-title">zkLend Position History</h1>
<div className="position-content">
<div className="position-top-cards">
<Card
label="Health Factor"
value={cardData?.health_ratio || '0.00'}
icon={<HealthIcon className="icon" />}
/>
<Card label="Borrow Balance" cardData={cardData?.borrowed || '0.00'} icon={<EthIcon className="icon" />} />
</div>
</div>

<div className="position-content-table">
<div className="position-table-title">
<p>Position History</p>
</div>

<div className="position-table">
{isPending ? (
<div className="spinner-container">
<Spinner loading={isPending} />
</div>
) : (
<table className="text-white">
<thead>
<tr>
<th></th>
<th>Token</th>
<th>Amount</th>
<th>Created At</th>
<th>Status</th>
<th>Start Price</th>
<th>Multiplier</th>
<th>Liquidated</th>
<th>Closed At</th>
<th className="action-column">
<img src={filterIcon} alt="filter-icon" draggable="false" />
</th>
</tr>
</thead>

<tbody>
{!tableData || tableData.length === 0 ? (
<tr>
<td colSpan="10">No opened positions</td>
</tr>
) : (
tableData.map((data, index) => (
<tr key={data.id}>
<td className="index">{index + 1}.</td>
<td>
<div className="token-cell">
{tokenIconMap[data.token_symbol]}
<span className="token-symbol">{data.token_symbol.toUpperCase()}</span>
</div>
</td>
<td>{data.amount}</td>
<td>{data.created_at}</td>
<td className={`status-cell ${statusStyles[data.status.toLowerCase()] || ''}`}>
{data.status}
</td>
<td>{data.start_price}</td>
<td>{data.multiplier}</td>
<td>{data.is_liquidated}</td>
<td>{data.datetime_liquidation}</td>
<td className="action-column">
<span className="action-button" onClick={() => setSelectedPosition({ data, index })}>
&#x22EE;
</span>
</td>
</tr>
))
)}
</tbody>
</table>
)}
</div>
</div>
</div>
{selectedPosition && (
<PositionHistoryModal
position={selectedPosition.data}
onClose={() => setSelectedPosition(null)}
tokenIcon={tokenIconMap}
statusStyles={statusStyles}
index={selectedPosition.index + 1}
/>
)}
</div>
);
}

export default PositionHistory;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import './positionHistory.css';

function PositionHistoryModal({ position, onClose, index, tokenIcon, statusStyles }) {
return (
<div className="position-modal-overlay">
<div className="position-modal-content">
<div className="position-modal-header">
<p className="position-modal-p">
<span>
<span>{index}.</span>
{tokenIcon[position.token_symbol]}
{position.token_symbol}
</span>
<span>{position.amount}</span>
<span className={`status-cell ${statusStyles[position.status.toLowerCase()] || ''}`}>
{position.status}
</span>
</p>

<span onClick={onClose} className="position-close-button" aria-label="Close Account Info Modal Box">
</span>
</div>
<hr className="modal-divider" />
<div className="position-modal-body">
<div className="position-detail-row">
<p>
Start Price <span>{position.start_price}</span>
</p>
</div>
<div className="position-detail-row">
<p>
Multiplier <span>{position.multiplier}</span>
</p>
</div>
<div className="position-detail-row">
<p>
Liquidated <span>{position.is_liquidated ? 'Yes' : 'No'}</span>
</p>
</div>
<div className="position-detail-row">
<p>
Created At <span>{position.created_at}</span>
</p>
</div>
<div className="position-detail-row">
<p>
Closed At <span>{position.datetime_liquidation}</span>
</p>
</div>
</div>
</div>
</div>
);
}

export default PositionHistoryModal;
Loading

0 comments on commit 7e3f5b1

Please sign in to comment.