Skip to content
Draft
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
3 changes: 2 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
yarn run lint:fix && yarn run format:fix && git add .
STAGED_FILES=$(git diff --cached --name-only)
yarn run lint:fix && yarn run format:fix && git add $STAGED_FILES
5 changes: 5 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ const nextConfig = {
},
webpack(config, options) {
if (options.isServer) config.devtool = 'source-map';
config.module.rules.push({
test: /\.svg$/,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
});
return config;
},
async headers() {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@starknet-react/chains": "3.0.0",
"@starknet-react/core": "3.0.1",
"@strkfarm/sdk": "^1.0.51",
"@svgr/webpack": "^8.1.0",
"@tanstack/query-core": "5.28.0",
"@types/mixpanel-browser": "2.49.0",
"@types/mustache": "4.2.5",
Expand Down Expand Up @@ -72,9 +73,10 @@
"react-responsive-carousel": "3.2.23",
"react-select": "5.8.0",
"react-share": "5.1.0",
"recharts": "^3.1.0",
"sharp": "0.33.4",
"starknet": "6.11.0",
"starknetkit": "2.4.0",
"starknetkit": "^2.12.1",
"swr": "2.2.5",
"wonka": "6.3.4"
},
Expand Down
2 changes: 1 addition & 1 deletion public/fulllogo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions src/app/api/strategies/apyHistory/[strategyId]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NextRequest } from 'next/server';
import { getStrategies } from '@/store/strategies.atoms';

export async function GET(req: NextRequest, context: any) {
const { params } = context;
const { searchParams } = new URL(req.url);
const strategyId = params.strategyId;

const duration = parseInt(searchParams.get('duration') || '7', 10);

const strategies = getStrategies();
const strategy = strategies.find((s) => s.id === strategyId);

if (!strategy) {
return new Response(JSON.stringify({ error: 'Strategy not found' }), {
status: 404,
headers: { 'Content-Type': 'application/json' },
});
}

const result = await fetch(`https://app.endur.fi/api/blocks/${duration}`);
const response = await result.json();

const blockInfo = response.blocks.map(
(block: { block: number; timestamp: number }) => {
return { block: block.block, timestamp: block.timestamp };
},
);

const apyHistory = {
strategy: strategy.name,
history: await strategy.getAPYHistory(blockInfo),
};

return new Response(JSON.stringify({ apyHistory }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
51 changes: 51 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,57 @@ body {
);
}

.strategy-page-gradient {
background: linear-gradient(
0deg,
rgba(33, 33, 33, 0.35),
rgba(33, 33, 33, 0.35)
),
linear-gradient(
288.17deg,
rgba(144, 105, 240, 0.2) -123.96%,
rgba(33, 33, 33, 0.2) 101.38%
);
}

.apy-gradient {
background: linear-gradient(
0deg,
rgba(33, 33, 33, 0.35),
rgba(33, 33, 33, 0.35)
),
linear-gradient(
326.73deg,
rgba(144, 105, 240, 0.5) -541.23%,
rgba(33, 33, 33, 0.4) 92.85%
);
}

.holdings-gradient {
background: linear-gradient(
0deg,
rgba(33, 33, 33, 0.35),
rgba(33, 33, 33, 0.35)
),
linear-gradient(
326.73deg,
rgba(144, 105, 240, 0.5) -541.23%,
rgba(33, 33, 33, 0.4) 92.85%
);
}

.faded-purple-gradient {
background: linear-gradient(
0deg,
rgba(33, 33, 33, 0.35),
rgba(33, 33, 33, 0.35)
),
linear-gradient(
326.73deg,
rgba(144, 105, 240, 0.5) -541.23%,
rgba(33, 33, 33, 0.4) 92.85%
);
}
.connect-button-gradient {
background: linear-gradient(93.94deg, #9069f0 3.22%, #4a14cd 101.67%);
}
Expand Down
3 changes: 0 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';

const banner_images = [

Check warning on line 27 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'banner_images' is assigned a value but never used. Allowed unused vars must match /^_/u
// {
// desktop: '/banners/strkfarm_braavos.svg',
// mobile: '/banners/strkfarm_braavos_mobile.svg',
Expand All @@ -45,19 +45,19 @@
export default function Home() {
const [tabIndex, setTabIndex] = useState(0);

const { address } = useAccount();

Check warning on line 48 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'address' is assigned a value but never used. Allowed unused vars must match /^_/u
const searchParams = useSearchParams();
const size = useWindowSize();

Check warning on line 50 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'size' is assigned a value but never used. Allowed unused vars must match /^_/u
const router = useRouter();

const [emblaRef, emblaApi] = useEmblaCarousel(

Check warning on line 53 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'emblaRef' is assigned a value but never used. Allowed unused vars must match /^_/u
{
loop: true,
},
[Autoplay({ playOnInit: true, delay: 8000 })],
);

const { selectedIndex, scrollSnaps, onDotButtonClick } =

Check warning on line 60 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'onDotButtonClick' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 60 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'scrollSnaps' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 60 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Performs linting, formatting on the application

'selectedIndex' is assigned a value but never used. Allowed unused vars must match /^_/u
useDotButton(emblaApi);

function setRoute(value: string) {
Expand Down Expand Up @@ -159,14 +159,12 @@
bg="purple"
color="color1"
borderRadius="1px"
boxShadow={'0px 0px 8px 0px var(--chakra-colors-purple)'}
/>
<TabPanels>
<TabPanel
bg="color_3"
float={'left'}
width={'100%'}
// borderWidth={'1px'}
borderColor={'color_3'}
borderRadius={'8px'}
padding={'1rem 0'}
Expand All @@ -177,7 +175,6 @@
bg="color_3"
width={'100%'}
float={'left'}
// borderWidth={'1px'}
borderColor={'color_3'}
borderRadius={'8px'}
padding={'1rem 0'}
Expand Down
200 changes: 200 additions & 0 deletions src/app/strategy/[strategyId]/_components/APYHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { Box, Flex, Text } from '@chakra-ui/react';
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
ResponsiveContainer,
} from 'recharts';
import TimeRangeSelector from '@/components/TimeRangeSelector';
import { useState, useMemo } from 'react';

type TimeRange = '1d' | '7d' | '30d' | 'all';

interface APYHistoryData {
month: string;
apy: number;
}

const dummyAPYHistory: APYHistoryData[] = (() => {
const days = 60;
const today = new Date();
const data: APYHistoryData[] = [];
const baseAPY = 4.0;
for (let i = days - 1; i >= 0; i--) {
const date = new Date(today);
date.setDate(today.getDate() - i);
const dayStr = date.toISOString().slice(0, 10);

const apy =
baseAPY +
Math.sin(i / 7) * 0.2 +
Math.random() * 0.1 +
(i > 30 ? 0.5 : 0);
data.push({
month: dayStr,
apy: Number(apy.toFixed(2)),
});
}
return data;
})();

function getMinMax<T>(arr: T[], key: keyof T & string): [number, number] {
const values = arr.map((item) => item[key] as unknown as number);
const min = Math.min(...values);
const max = Math.max(...values);
if (min === max) {
return [min - 1, max + 1];
}
return [min, max];
}

function generateTicks([min, max]: [number, number]): number[] {
const step = Math.max(0.1, (max - min) / 4);
return [min, min + step, min + 2 * step, min + 3 * step, max];
}

function formatYAxis(value: number): string {
return `${value.toFixed(1)}%`;
}

const timeRangeToDays: Record<TimeRange, number> = {
'1d': 10,
'7d': 7,
'30d': 30,
all: dummyAPYHistory.length,
};

const renderAPYHistoryChart = (
selectedRange: TimeRange,
onRangeChange: (range: TimeRange) => void,
filteredData: APYHistoryData[],
) => {
const yAxisDomain: [number, number] = getMinMax(filteredData, 'apy');

return (
<Box
className="faded-purple-gradient"
borderRadius="xl"
boxShadow="lg"
overflow="hidden"
display="flex"
flexDirection="column"
height="100%"
width={'70%'}
padding={'10px'}
>
<Flex
align="center"
justify="space-between"
px={4}
py={3}
borderBottom="1px solid"
borderColor="border_grey"
borderBottomWidth="1px"
>
<Text fontWeight="bold" fontSize="lg" color="white">
APY History
</Text>
<TimeRangeSelector
selectedRange={selectedRange}
onRangeChange={onRangeChange}
/>
</Flex>
<Box p={4} flex="1 1 0" display="flex" flexDirection="column">
<Box flex="1 1 0" minHeight="300px">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={filteredData}
margin={{ top: 10, right: 30, left: 0, bottom: 0 }}
syncId="validator-charts"
>
<defs>
<linearGradient id="colorAPY" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="rgba(16, 185, 129, 0.8)"
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor="rgba(16, 185, 129, 0.1)"
stopOpacity={0}
/>
</linearGradient>
</defs>
<CartesianGrid
strokeDasharray="3 3"
stroke="none"
opacity={0.4}
/>
<XAxis
dataKey="month"
stroke="none"
tick={{ fill: '#868898', fontSize: 10 }}
tickMargin={10}
tickFormatter={(value) => {
const date = new Date(value);
return date.toLocaleString('default', {
month: 'short',
day: 'numeric',
});
}}
type="category"
/>
<YAxis
stroke="#10B981"
tick={{ fill: '#868898', fontSize: 10 }}
tickMargin={14}
ticks={generateTicks(yAxisDomain)}
tickFormatter={formatYAxis}
domain={yAxisDomain}
width={60}
axisLine={false}
tickLine={false}
/>
<Area
type="monotone"
dataKey="apy"
stroke="#10B981"
fillOpacity={1}
fill="url(#colorAPY)"
strokeWidth={2}
/>
</AreaChart>
</ResponsiveContainer>
</Box>
</Box>
</Box>
);
};

function APYHistory() {
const [selectedRange, setSelectedRange] = useState<TimeRange>('all');
const days = timeRangeToDays[selectedRange];
const filteredData = useMemo(
() => dummyAPYHistory.slice(-days),
[selectedRange],
);
return (
<>{renderAPYHistoryChart(selectedRange, setSelectedRange, filteredData)}</>
);
}

export function APYHistoryTab() {
return (
<Box background="black">
<Flex
maxWidth={'1152px'}
margin={'0 auto'}
flexDirection="column"
gap="16px"
width="100%"
padding={'32px 0px'}
>
<APYHistory />
</Flex>
</Box>
);
}
Loading
Loading