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
14 changes: 8 additions & 6 deletions src/app/contributors/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import NextLink from "next/link";
import contributorData from "@/data/contributors.json";
import { safeHttpUrl } from "@/lib/sanitize";

// Heights mirror the loaded page's line boxes (font size × 1.5 body line
// height) so swapping in real content doesn't shift the layout.
function ContributorsSkeleton() {
return (
<Flex direction="column" align="center" px={{ base: 6, md: 16 }} py={10} gap={10}>
<Box w="100%" maxW="1100px">
<HStack spacing={2} mb={5}>
<Skeleton w="6px" h="6px" borderRadius="full" />
<Skeleton h="12px" w="120px" />
<Skeleton h="18px" w="120px" />
</HStack>
<Skeleton h={{ base: "40px", md: "48px" }} w="300px" mb={4} />
<Skeleton h="20px" w="400px" />
<Skeleton h={{ base: "54px", md: "72px" }} w="300px" mb={4} />
<Skeleton h="54px" w="100%" maxW="640px" />
</Box>
<Grid
w="100%"
Expand All @@ -35,9 +37,9 @@ function ContributorsSkeleton() {
bg="app.surface"
>
<SkeletonCircle size="48px" flexShrink={0} />
<Stack flex={1} spacing={2}>
<Skeleton h="16px" w="100px" />
<Skeleton h="12px" w="80px" />
<Stack flex={1} spacing={0}>
<Skeleton h="24px" w="100px" />
<Skeleton h="21px" w="80px" />
</Stack>
</Flex>
))}
Expand Down
47 changes: 25 additions & 22 deletions src/app/farm/[poolId]/PoolDetailClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
ModalHeader,
ModalOverlay,
Skeleton,
SkeletonText,
Spinner,
Table,
Tbody,
Expand Down Expand Up @@ -83,13 +82,13 @@ function StatCard({
<Text fontSize="xs" color="app.muted" fontWeight="medium">
{label}
</Text>
{loading ? (
<Skeleton height="24px" w="80%" borderRadius="md" startColor="app.border" endColor="app.surfaceHover" />
) : (
{/* Wrapping the real Text (rather than a fixed-height bar) sizes the
skeleton to the value's actual line box, so nothing shifts on load. */}
<Skeleton isLoaded={!loading} w={loading ? "80%" : undefined} borderRadius="md" startColor="app.border" endColor="app.surfaceHover">
<Text fontWeight="bold" fontSize="lg" color="app.text">
{value}
</Text>
)}
</Skeleton>
</Flex>
);
}
Expand Down Expand Up @@ -264,19 +263,17 @@ export default function PoolDetailClient({ poolId }: { poolId: string }) {

{/* Header */}
<Flex w="full" justify="space-between" align="center" flexWrap="wrap" gap={4}>
{poolLoading ? (
<Skeleton height="40px" w="200px" borderRadius="xl" startColor="app.border" endColor="app.surfaceHover" />
) : (
<Skeleton isLoaded={!poolLoading} w={poolLoading ? "200px" : undefined} borderRadius="xl" startColor="app.border" endColor="app.surfaceHover">
<Text
fontSize={{ base: "2xl", md: "3xl" }}
fontWeight="extrabold"
letterSpacing="tight"
bgGradient="linear(to-r, app.text, app.accent)"
bgClip="text"
>
{pool?.asset.code ?? "Pool"} Pool
{poolLoading ? "Pool" : `${pool?.asset.code ?? "Pool"} Pool`}
</Text>
)}
</Skeleton>
<Flex align="center" gap={2}>
{!poolLoading && (
<ShareButton
Expand Down Expand Up @@ -336,10 +333,12 @@ export default function PoolDetailClient({ poolId }: { poolId: string }) {
Your Earnings
</Text>
{creditsLoading || positionLoading ? (
<Flex direction="column" gap={2}>
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} height="40px" borderRadius="md" />
))}
// Same row of StatCards as the loaded state, so the section
// keeps its height and layout when earnings arrive.
<Flex gap={3} flexWrap="wrap">
<StatCard label="Daily Earnings" value="—" loading />
<StatCard label="Weekly Earnings" value="—" loading />
<StatCard label="Total Earnings" value="—" loading />
</Flex>
) : userCredits ? (
<Flex gap={3} flexWrap="wrap">
Expand Down Expand Up @@ -388,13 +387,7 @@ export default function PoolDetailClient({ poolId }: { poolId: string }) {
<Text fontSize="sm" fontWeight="semibold" mb={4} color="app.muted" letterSpacing="wide" textTransform="uppercase">
Top 20 Depositors
</Text>
{depositorsLoading ? (
<Flex direction="column" gap={2}>
{Array.from({ length: 5 }).map((_, i) => (
<SkeletonText key={i} noOfLines={1} skeletonHeight="32px" borderRadius="md" />
))}
</Flex>
) : depositors.length === 0 ? (
{!depositorsLoading && depositors.length === 0 ? (
<Text color="app.muted" fontSize="sm">
No depositors yet.
</Text>
Expand All @@ -410,7 +403,17 @@ export default function PoolDetailClient({ poolId }: { poolId: string }) {
</Tr>
</Thead>
<Tbody>
{depositors.map((d, i) => (
{/* Skeleton rows reuse the real table's cells (and its
sm row height) so the list doesn't jump on load. */}
{depositorsLoading && Array.from({ length: 5 }).map((_, i) => (
<Tr key={i}>
<Td borderColor="app.border"><Skeleton h="16px" w="16px" borderRadius="md" /></Td>
<Td borderColor="app.border"><Skeleton h="16px" w="88px" borderRadius="md" /></Td>
<Td borderColor="app.border" isNumeric><Skeleton h="16px" w="56px" ml="auto" borderRadius="md" /></Td>
<Td borderColor="app.border" isNumeric><Skeleton h="16px" w="56px" ml="auto" borderRadius="md" /></Td>
</Tr>
))}
{!depositorsLoading && depositors.map((d, i) => (
<Tr key={d.address} _hover={{ bg: "app.inputBg" }}>
<Td borderColor="app.border" color="app.muted">{i + 1}</Td>
<Td borderColor="app.border" fontFamily="mono" fontSize="xs">
Expand Down
141 changes: 74 additions & 67 deletions src/components/TvlChart/TvlChart.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useEffect, useState } from "react";
import { Box, Skeleton, Text, useBreakpointValue } from "@chakra-ui/react";
import { Box, Skeleton, Text } from "@chakra-ui/react";
import {
AreaChart,
Area,
Expand All @@ -13,6 +13,14 @@ import {

const ACCENT = "#4ae292";

// Issue #214: a fixed 180px is tall relative to viewport width on small
// phones; shrink it below the sm breakpoint instead. Expressed as a
// responsive CSS value (not useBreakpointValue) so the skeleton, empty
// state and chart all resolve to the same height on first paint — the JS
// breakpoint hook falls back to "sm" until hydration, which made the
// skeleton render at 180px and then jump to 140px on phones.
const CHART_HEIGHT = { base: "140px", sm: "180px" };

interface TvlDataPoint {
date: string;
tvl: string;
Expand All @@ -26,9 +34,6 @@ interface TvlChartProps {
export default function TvlChart({ poolId }: TvlChartProps) {
const [data, setData] = useState<TvlDataPoint[]>([]);
const [loading, setLoading] = useState(true);
// Issue #214: a fixed 180px is tall relative to viewport width on small
// phones; shrink it below the sm breakpoint instead.
const chartHeight = useBreakpointValue({ base: 140, sm: 180 }, { fallback: "sm" }) ?? 180;

useEffect(() => {
let cancelled = false;
Expand All @@ -50,13 +55,13 @@ export default function TvlChart({ poolId }: TvlChartProps) {
}, [poolId]);

if (loading) {
return <Skeleton height={`${chartHeight}px`} borderRadius="2xl" startColor="app.border" endColor="app.surfaceHover" />;
return <Skeleton h={CHART_HEIGHT} borderRadius="2xl" startColor="app.border" endColor="app.surfaceHover" />;
}

if (data.length === 0) {
return (
<Box
h={`${chartHeight}px`}
h={CHART_HEIGHT}
display="flex"
alignItems="center"
justifyContent="center"
Expand All @@ -72,66 +77,68 @@ export default function TvlChart({ poolId }: TvlChartProps) {
}

return (
<ResponsiveContainer width="100%" height={chartHeight}>
<AreaChart data={data} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
<defs>
<linearGradient id="tvlGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={ACCENT} stopOpacity={0.3} />
<stop offset="95%" stopColor={ACCENT} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis
dataKey="date"
tick={{ fontSize: 11, fill: "#A2A2A2" }}
tickFormatter={(v: string) =>
new Date(v).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
})
}
axisLine={false}
tickLine={false}
/>
<YAxis
tick={{ fontSize: 11, fill: "#A2A2A2" }}
tickFormatter={(v: number) =>
v >= 1_000_000
? `$${(v / 1_000_000).toFixed(1)}M`
: v >= 1_000
? `$${(v / 1_000).toFixed(0)}K`
: `$${v}`
}
axisLine={false}
tickLine={false}
width={56}
/>
<Tooltip
contentStyle={{
background: "var(--chakra-colors-app-surface)",
border: "1px solid var(--chakra-colors-app-border)",
borderRadius: "12px",
fontSize: "12px",
color: "var(--chakra-colors-app-text)",
}}
labelFormatter={(label) =>
new Date(String(label)).toLocaleDateString(undefined, {
weekday: "short",
month: "short",
day: "numeric",
})
}
formatter={(value) => [`$${Number(value).toLocaleString()}`, "TVL"]}
/>
<Area
type="monotone"
dataKey="tvl"
stroke={ACCENT}
strokeWidth={2}
fill="url(#tvlGradient)"
dot={false}
activeDot={{ r: 4, fill: ACCENT }}
/>
</AreaChart>
</ResponsiveContainer>
<Box h={CHART_HEIGHT}>
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data} margin={{ top: 8, right: 8, left: 0, bottom: 0 }}>
<defs>
<linearGradient id="tvlGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={ACCENT} stopOpacity={0.3} />
<stop offset="95%" stopColor={ACCENT} stopOpacity={0} />
</linearGradient>
</defs>
<XAxis
dataKey="date"
tick={{ fontSize: 11, fill: "#A2A2A2" }}
tickFormatter={(v: string) =>
new Date(v).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
})
}
axisLine={false}
tickLine={false}
/>
<YAxis
tick={{ fontSize: 11, fill: "#A2A2A2" }}
tickFormatter={(v: number) =>
v >= 1_000_000
? `$${(v / 1_000_000).toFixed(1)}M`
: v >= 1_000
? `$${(v / 1_000).toFixed(0)}K`
: `$${v}`
}
axisLine={false}
tickLine={false}
width={56}
/>
<Tooltip
contentStyle={{
background: "var(--chakra-colors-app-surface)",
border: "1px solid var(--chakra-colors-app-border)",
borderRadius: "12px",
fontSize: "12px",
color: "var(--chakra-colors-app-text)",
}}
labelFormatter={(label) =>
new Date(String(label)).toLocaleDateString(undefined, {
weekday: "short",
month: "short",
day: "numeric",
})
}
formatter={(value) => [`$${Number(value).toLocaleString()}`, "TVL"]}
/>
<Area
type="monotone"
dataKey="tvl"
stroke={ACCENT}
strokeWidth={2}
fill="url(#tvlGradient)"
dot={false}
activeDot={{ r: 4, fill: ACCENT }}
/>
</AreaChart>
</ResponsiveContainer>
</Box>
);
}