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
5 changes: 4 additions & 1 deletion app/home/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import React, { useState, useEffect } from 'react';
import { useAccount } from 'wagmi';
import PrimaryButton from '@/components/common/PrimaryButton';
import Footer from '@/components/layout/footer/Footer';
import backgroundImage from '@/imgs/bg/bg.png';
Expand All @@ -16,6 +17,8 @@ export default function HomePage() {
return () => clearInterval(interval);
}, []);

const { address } = useAccount();

return (
<div className="flex min-h-screen flex-col bg-primary">
<div
Expand Down Expand Up @@ -49,7 +52,7 @@ export default function HomePage() {
<PrimaryButton isSecondary href="/info">
Why Monarch
</PrimaryButton>
<PrimaryButton href="/markets">View Markets</PrimaryButton>
<PrimaryButton href={`/positions/${address ?? ''}`}>View Portfolio</PrimaryButton>
</div>
</div>
<Footer />
Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{children}
<RiskNotificationModal />
</OnchainProviders>
<ToastContainer position="bottom-right" />
<ToastContainer position="bottom-right" bodyClassName="font-zen" />
</Providers>
</body>
<GoogleAnalytics />
Expand Down
62 changes: 49 additions & 13 deletions app/markets/components/RiskIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Tooltip } from '@nextui-org/tooltip';
import { Market } from '@/utils/types';
import { WarningWithDetail } from '@/utils/types';
import { WarningCategory } from '@/utils/types';

type RiskFlagProps = {
Expand All @@ -22,57 +22,93 @@ export function RiskIndicator({ level, description }: RiskFlagProps) {
export function RiskIndicatorFromWarning({
market,
category,
greeDescription,
greenDescription,
yellowDescription,
redDescription,
isBatched = false,
}: {
market: Market;
market: { warningsWithDetail: WarningWithDetail[] };
category: WarningCategory;
greeDescription: string;
greenDescription: string;
yellowDescription: string;
redDescription: string;
isBatched?: boolean;
}) {
const warnings = market.warningsWithDetail.filter((w) => w.category === category);
if (warnings.length === 0) {
return <RiskIndicator level="green" description={greeDescription} />;
return <RiskIndicator level="green" description={greenDescription} />;
}
if (warnings.some((warning) => warning.level === 'alert')) {
return <RiskIndicator level="red" description={redDescription} />;
} else return <RiskIndicator level="yellow" description={yellowDescription} />;
return (
<RiskIndicator
level="red"
description={isBatched ? `One or more markets have: ${redDescription}` : redDescription}
/>
);
} else
return (
<RiskIndicator
level="yellow"
description={
isBatched ? `One or more markets have: ${yellowDescription}` : yellowDescription
}
/>
);
}

export function MarketAssetIndicator({ market }: { market: Market }) {
export function MarketAssetIndicator({
market,
isBatched = false,
}: {
market: { warningsWithDetail: WarningWithDetail[] };
isBatched?: boolean;
}) {
return (
<RiskIndicatorFromWarning
market={market}
category={WarningCategory.asset}
greeDescription="Recognized assets"
greenDescription="Recognized assets"
yellowDescription="Some warnings flagged with the assets"
redDescription="Potentially dangerous assets"
isBatched={isBatched}
/>
);
}

export function MarketOracleIndicator({ market }: { market: Market }) {
export function MarketOracleIndicator({
market,
isBatched = false,
}: {
market: { warningsWithDetail: WarningWithDetail[] };
isBatched?: boolean;
}) {
return (
<RiskIndicatorFromWarning
market={market}
category={WarningCategory.oracle}
greeDescription="Recognized oracles"
greenDescription="Recognized oracles"
yellowDescription="Some warnings flagged with the oracle"
redDescription="Some alerts flagged with the oracle"
isBatched={isBatched}
/>
);
}

export function MarketDebtIndicator({ market }: { market: Market }) {
export function MarketDebtIndicator({
market,
isBatched = false,
}: {
market: { warningsWithDetail: WarningWithDetail[] };
isBatched?: boolean;
}) {
return (
<RiskIndicatorFromWarning
market={market}
category={WarningCategory.debt}
greeDescription="No bad debt"
greenDescription="No bad debt"
yellowDescription="Bad debt has occurred"
redDescription="Bad debt higher than 1% of supply"
isBatched={isBatched}
/>
);
}
2 changes: 1 addition & 1 deletion app/positions/components/PositionsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function Positions() {
<Header />
<div className="container gap-8" style={{ padding: '0 5%' }}>
<div className="flex items-center justify-between pb-4">
<h1 className="flex items-center gap-2 py-4 font-zen text-2xl">Your Supplies</h1>
<h1 className="flex items-center gap-2 py-4 font-zen text-2xl">Portfolio</h1>
<div className="flex gap-4">
<Link href={`/history/${account}`}>
<button
Expand Down
120 changes: 60 additions & 60 deletions app/positions/components/PositionsSummaryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import Image from 'next/image';
import { GrRefresh } from 'react-icons/gr';
import { toast } from 'react-toastify';
import { useAccount } from 'wagmi';
import { TokenIcon } from '@/components/TokenIcon';
import { formatReadable, formatBalance } from '@/utils/balance';
import { getNetworkImg } from '@/utils/networks';
import { findToken } from '@/utils/tokens';
import { MarketPosition, GroupedPosition } from '@/utils/types';
import { getCollateralColor } from '../utils/colors';
import {
MarketAssetIndicator,
MarketOracleIndicator,
MarketDebtIndicator,
} from 'app/markets/components/RiskIndicator';
import { RebalanceModal } from './RebalanceModal';
import { SuppliedMarketsDetail } from './SuppliedMarketsDetail';

Expand Down Expand Up @@ -57,19 +61,24 @@ export function PositionsSummaryTable({
collaterals: [],
markets: [],
processedCollaterals: [],
allWarnings: [], // Initialize allWarnings as an empty array
};
acc.push(groupedPosition);
}

groupedPosition.markets.push(position);

// Combine warnings from all markets
groupedPosition.allWarnings = [
...new Set([...groupedPosition.allWarnings, ...position.warningsWithDetail]),
];

const supplyAmount = Number(
formatBalance(position.supplyAssets, position.market.loanAsset.decimals),
);
groupedPosition.totalSupply += supplyAmount;

const weightedApy = supplyAmount * position.market.dailyApys.netSupplyApy;
if (!groupedPosition.totalWeightedApy) {
groupedPosition.totalWeightedApy = 0;
}
groupedPosition.totalWeightedApy += weightedApy;

const collateralAddress = position.market.collateralAsset?.address;
Expand All @@ -90,7 +99,6 @@ export function PositionsSummaryTable({
}
}

groupedPosition.markets.push(position);
return acc;
}, []);
}, [marketPositions]);
Expand Down Expand Up @@ -125,6 +133,8 @@ export function PositionsSummaryTable({
});
}, [groupedPositions]);

console.log('processedPositions', processedPositions);

// Update selectedGroupedPosition when groupedPositions change, don't depend on selectedGroupedPosition
useEffect(() => {
if (selectedGroupedPosition) {
Expand Down Expand Up @@ -162,7 +172,7 @@ export function PositionsSummaryTable({
<div className="space-y-4 overflow-x-auto">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<h2 className="text-xl font-semibold">Position Summary</h2>
<h2 className="text-xl font-semibold">Your Supply</h2>
{isRefetching && <Spinner size="sm" />}
</div>
<button
Expand All @@ -180,10 +190,10 @@ export function PositionsSummaryTable({
<tr>
<th className="w-10" />
<th className="w-10">Network</th>
<th>Asset</th>
<th>Total Supplied</th>
<th>Size</th>
<th>Avg APY</th>
<th className="w-1/4">Collateral Exposure</th>
<th>Collateral Exposure</th>
<th>Warnings</th>
<th>Actions</th>
</tr>
</thead>
Expand All @@ -192,6 +202,7 @@ export function PositionsSummaryTable({
const rowKey = `${position.loanAssetAddress}-${position.chainId}`;
const isExpanded = expandedRows.has(rowKey);
const avgApy = position.totalWeightedApy / position.totalSupply;

return (
<React.Fragment key={rowKey}>
<tr className="cursor-pointer hover:bg-gray-50" onClick={() => toggleRow(rowKey)}>
Expand All @@ -208,63 +219,52 @@ export function PositionsSummaryTable({
/>
</div>
</td>
<td data-label="Asset">
<td data-label="Size">
<div className="flex items-center justify-center gap-2">
{findToken(position.loanAssetAddress, position.chainId)?.img && (
<Image
src={findToken(position.loanAssetAddress, position.chainId)?.img ?? ''}
alt={position.loanAsset}
width={24}
height={24}
/>
)}
<span className="font-medium">{position.loanAsset}</span>
</div>
</td>
<td data-label="Total Supplied">
<div className="text-center">
{formatReadable(position.totalSupply)} {position.loanAsset}
<span className="font-medium">{formatReadable(position.totalSupply)}</span>
<span>{position.loanAsset}</span>
<TokenIcon
address={position.loanAssetAddress}
chainId={position.chainId}
width={16}
height={16}
/>
</div>
</td>
<td data-label="Avg APY">
<div className="text-center">{formatReadable(avgApy * 100)}%</div>
</td>
<td data-label="Collateral Breakdown" className="w-1/4">
<div className="flex h-3 w-full overflow-hidden rounded-full bg-secondary">
{position.processedCollaterals.map((collateral, colIndex) => (
<div
key={`${collateral.address}-${colIndex}`}
className="h-full opacity-70"
style={{
width: `${collateral.percentage}%`,
backgroundColor:
collateral.symbol === 'Others'
? '#A0AEC0'
: getCollateralColor(collateral.address),
}}
title={`${collateral.symbol}: ${collateral.percentage.toFixed(2)}%`}
/>
))}
<td data-label="Collateral Exposure">
<div className="flex items-center justify-center gap-1">
{position.collaterals.length > 0 ? (
position.collaterals.map((collateral, index) => (
<TokenIcon
key={`${collateral.address}-${index}`}
address={collateral.address}
chainId={position.chainId}
width={20}
height={20}
/>
))
) : (
<span className="text-sm text-gray-500">No known collaterals</span>
)}
</div>
<div className="mt-1 flex flex-wrap justify-center text-xs">
{position.processedCollaterals.map((collateral, colIndex) => (
<span
key={`${collateral.address}-${colIndex}`}
className="mb-1 mr-2 opacity-70"
>
<span
style={{
color:
collateral.symbol === 'Others'
? '#A0AEC0'
: getCollateralColor(collateral.address),
}}
>
</span>{' '}
{collateral.symbol}
</span>
))}
</td>
<td data-label="Warnings" className="align-middle">
<div className="flex items-center justify-center gap-1">
<MarketAssetIndicator
market={{ warningsWithDetail: position.allWarnings }}
isBatched
/>
<MarketOracleIndicator
market={{ warningsWithDetail: position.allWarnings }}
isBatched
/>
<MarketDebtIndicator
market={{ warningsWithDetail: position.allWarnings }}
isBatched
/>
</div>
</td>
<td data-label="Actions" className="text-right">
Expand Down
Loading