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
67 changes: 67 additions & 0 deletions src/components/QueryError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { Button, Stack, Typography } from '@mui/material';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import RefreshIcon from '@mui/icons-material/Refresh';
import { FONTS } from '../theme';

interface QueryErrorProps {
/**
* Optional one-line description of what failed to load. Falls back to a
* generic "Couldn't load data" message.
*/
label?: string;
/**
* Called when the user clicks Retry. Pass the consumer's `refetch` from
* react-query.
*/
onRetry: () => void;
}

const QueryError: React.FC<QueryErrorProps> = ({
label = "Couldn't load data",
onRetry,
}) => (
<Stack
role="alert"
spacing={1.5}
alignItems="center"
sx={{
p: 3,
borderRadius: 0,
backgroundColor: 'surface.light',
border: '1px solid',
borderColor: 'divider',
color: 'text.secondary',
}}
>
<ErrorOutlineIcon
fontSize="small"
sx={{ color: 'status.timedOut' }}
aria-hidden
/>
<Typography
sx={{
fontFamily: FONTS.mono,
fontSize: '0.8rem',
textAlign: 'center',
}}
>
{label}
</Typography>
<Button
size="small"
variant="outlined"
onClick={onRetry}
startIcon={<RefreshIcon fontSize="small" />}
sx={{
fontFamily: FONTS.mono,
fontSize: '0.7rem',
textTransform: 'none',
}}
>
Retry
</Button>
</Stack>
);

export default QueryError;
7 changes: 6 additions & 1 deletion src/components/dashboard/EventFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import { displayEventType, useLatestEvents } from '../../api';
import { FONTS } from '../../theme';
import CopyableAddress from '../CopyableAddress';
import QueryError from '../QueryError';
import { EventFeedSkeleton } from './Skeletons';

const getEventColor = (
Expand Down Expand Up @@ -56,7 +57,7 @@ const getEventColor = (

const EventFeed: React.FC = () => {
const theme = useTheme();
const { data: events, isLoading } = useLatestEvents();
const { data: events, isLoading, isError, refetch } = useLatestEvents();
const scrollRef = useRef<HTMLDivElement>(null);
const [scrolled, setScrolled] = useState(false);

Expand All @@ -69,6 +70,10 @@ const EventFeed: React.FC = () => {
scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
}, []);

if (isError && !events) {
return <QueryError label="Couldn't load event feed" onRetry={refetch} />;
}

return isLoading || !events ? (
<EventFeedSkeleton />
) : (
Expand Down
7 changes: 6 additions & 1 deletion src/components/dashboard/MinerRatesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import SearchIcon from '@mui/icons-material/Search';
import { useMiners, type Miner } from '../../api';
import { FONTS } from '../../theme';
import CopyableAddress from '../CopyableAddress';
import QueryError from '../QueryError';
import { MinerRatesTableSkeleton } from './Skeletons';
import { formatRate } from '../../utils/format';

Expand Down Expand Up @@ -130,7 +131,7 @@ const MinerRatesTable: React.FC = () => {
collateral: { textAlign: 'center' },
};

const { data: miners, isLoading } = useMiners();
const { data: miners, isLoading, isError, refetch } = useMiners();
const [sortKey, setSortKey] = useState<SortKey>('rate');
const [sortDir, setSortDir] = useState<SortDir>('desc');
const [search, setSearch] = useState('');
Expand Down Expand Up @@ -345,6 +346,10 @@ const MinerRatesTable: React.FC = () => {
return hasForward !== hasReverse;
};

if (isError && !miners) {
return <QueryError label="Couldn't load miner rates" onRetry={refetch} />;
}

return isLoading || !miners ? (
<MinerRatesTableSkeleton />
) : (
Expand Down
9 changes: 8 additions & 1 deletion src/components/dashboard/OrderbookDepth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { useMiners } from '../../api';
import { FONTS } from '../../theme';
import QueryError from '../QueryError';
import { OrderbookDepthSkeleton } from './Skeletons';

const OrderbookDepth: React.FC = () => {
Expand Down Expand Up @@ -99,7 +100,7 @@ const OrderbookDepth: React.FC = () => {
borderBottom: `1px solid ${theme.palette.divider}`,
};

const { data: miners, isLoading } = useMiners();
const { data: miners, isLoading, isError, refetch } = useMiners();
type Direction = 'forward' | 'reverse';
type DirectionOption = {
asset: string;
Expand Down Expand Up @@ -193,6 +194,12 @@ const OrderbookDepth: React.FC = () => {
[depthData],
);

if (isError && !miners) {
return (
<QueryError label="Couldn't load orderbook depth" onRetry={refetch} />
);
}

return isLoading || !miners ? (
<OrderbookDepthSkeleton />
) : (
Expand Down
7 changes: 6 additions & 1 deletion src/components/dashboard/StatsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Box, Grid, Typography } from '@mui/material';
import { useStats } from '../../api';
import { FONTS } from '../../theme';
import { RollingValue } from '../animated';
import QueryError from '../QueryError';
import { StatsPanelSkeleton } from './Skeletons';

const StatCard: React.FC<{ label: string; value: string }> = ({
Expand Down Expand Up @@ -46,7 +47,11 @@ const StatCard: React.FC<{ label: string; value: string }> = ({
);

const StatsPanel: React.FC = () => {
const { data: stats, isLoading } = useStats();
const { data: stats, isLoading, isError, refetch } = useStats();

if (isError && !stats) {
return <QueryError label="Couldn't load network stats" onRetry={refetch} />;
}

const volume = stats ? parseFloat(stats.totalVolumeTao).toFixed(2) : '0';

Expand Down
21 changes: 19 additions & 2 deletions src/components/dashboard/SwapTracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { useAllSwaps, useSwapDetail } from '../../api';
import { FONTS } from '../../theme';
import CopyableAddress from '../CopyableAddress';
import QueryError from '../QueryError';
import { SwapTrackerSkeleton } from './Skeletons';
import { formatAmount } from '../../utils/format';

Expand Down Expand Up @@ -66,14 +67,26 @@ const SwapTracker: React.FC = () => {
const idMatch = debouncedSearch.trim().match(/^#?(\d+)$/);
const exactSwapId = idMatch?.[1] ?? '';

const { data: detail, isLoading: detailLoading } = useSwapDetail(exactSwapId);
const { data: fuzzy, isLoading: fuzzyLoading } = useAllSwaps(
const {
data: detail,
isLoading: detailLoading,
isError: detailError,
refetch: refetchDetail,
} = useSwapDetail(exactSwapId);
const {
data: fuzzy,
isLoading: fuzzyLoading,
isError: fuzzyError,
refetch: refetchFuzzy,
} = useAllSwaps(
{ search: debouncedSearch || undefined, limit },
!exactSwapId,
);

const swaps = exactSwapId ? (detail?.swap ? [detail.swap] : []) : fuzzy;
const isLoading = exactSwapId ? detailLoading : fuzzyLoading;
const isError = exactSwapId ? detailError : fuzzyError;
const refetch = exactSwapId ? refetchDetail : refetchFuzzy;

// Reset limit when search changes
React.useEffect(() => {
Expand All @@ -91,6 +104,10 @@ const SwapTracker: React.FC = () => {
}
}, [hasMore]);

if (isError && !swaps) {
return <QueryError label="Couldn't load swaps" onRetry={refetch} />;
}

return isLoading && !swaps ? (
<SwapTrackerSkeleton />
) : (
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as Card } from './Card';
export { default as HoverCard } from './HoverCard';
export { default as LabelValue } from './LabelValue';
export { default as PageWrapper } from './PageWrapper';
export { default as QueryError } from './QueryError';
export { TimelineStep, SectionTitle } from './Timeline';
export type { TimelineStepState } from './Timeline';
export { SEO } from './SEO';
Expand Down
14 changes: 13 additions & 1 deletion src/pages/SwapDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Card,
LabelValue,
PageWrapper,
QueryError,
SectionTitle,
TimelineStep,
type TimelineStepState,
Expand Down Expand Up @@ -70,11 +71,22 @@ const SwapDetailPage: React.FC = () => {
const { swapId } = useParams<{ swapId: string }>();
const theme = useTheme();

const { data, isLoading } = useSwapDetail(swapId ?? '');
const { data, isLoading, isError, refetch } = useSwapDetail(swapId ?? '');
const { data: protocol } = useProtocolConstants();
const { data: chainState } = useChainState();
const currentBlock = chainState?.currentBlock ?? 0;

if (isError && !data) {
return (
<PageWrapper>
<QueryError
label={`Couldn't load transaction #${swapId}`}
onRetry={refetch}
/>
</PageWrapper>
);
}

if (isLoading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', pt: 10 }}>
Expand Down