Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simulator summary #1005

Merged
merged 15 commits into from
Jan 29, 2024
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
6 changes: 6 additions & 0 deletions src/assets/icons/arrow-round.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/calendar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/icons/download.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/movie.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/page.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions src/components/simulator/SimulatorChartHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ReactComponent as CalendarIcon } from 'assets/icons/calendar.svg';
import { SimulatorPageTabs } from './SimulatorPageTabs';
import { SimulatorDownloadMenu } from './SimulatorDownload';
import { SimulatorReturn } from 'libs/queries';

interface Props extends Pick<SimulatorReturn, 'data'> {
showSummary: boolean;
setShowSummary: React.Dispatch<React.SetStateAction<boolean>>;
}

export const SimulatorChartHeader = ({
showSummary,
setShowSummary,
data,
}: Props) => {
const startTimestamp = data![0].date * 1e3; // ms
const endTimestamp = data![data.length - 1].date * 1e3; // ms

const dateFormatOptions: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'long',
day: '2-digit',
};

const startDate = new Intl.DateTimeFormat('en-US', dateFormatOptions).format(
startTimestamp
);
const endDate = new Intl.DateTimeFormat('en-US', dateFormatOptions).format(
endTimestamp
);

return (
<section className="flex flex-wrap items-center justify-evenly gap-8 py-8 px-24 md:justify-between">
<article className="flex items-center gap-8">
<span className="h-24 w-24 items-center justify-center rounded-[12px] bg-white/10">
<CalendarIcon className="h-12 w-12" />
</span>
<span className="justify-self-end text-14 text-white/80">
{startDate} – {endDate}
</span>
</article>
<article className="flex items-center gap-8">
<SimulatorPageTabs
setShowSummary={setShowSummary}
showSummary={showSummary}
/>
<SimulatorDownloadMenu data={data} />
</article>
</section>
);
};
102 changes: 102 additions & 0 deletions src/components/simulator/SimulatorDownload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { cn } from 'utils/helpers';
import { ReactComponent as IconLog } from 'assets/icons/page.svg';
import { ReactComponent as IconAnimation } from 'assets/icons/movie.svg';
import { ReactComponent as IconSummary } from 'assets/icons/image.svg';
import { ReactComponent as IconDownload } from 'assets/icons/download.svg';
import { DropdownMenu } from 'components/common/dropdownMenu';
import { useState } from 'react';
import { buttonStyles } from 'components/common/button/buttonStyles';
import { SimulatorReturn } from 'libs/queries';
import { CsvDataService } from 'libs/csv';

interface Props extends Pick<SimulatorReturn, 'data'> {}

export const SimulatorDownloadMenu = ({ data }: Props) => {
const [isOpen, setIsOpen] = useState(false);

const items = [
{
id: 'summaryView',
title: 'Summary View',
subTitle: 'JPEG',
action: () => console.log('Summary View'),
icon: <IconSummary className="h-20 w-20" />,
},
{
id: 'animation' as const,
title: 'Animation',
subTitle: 'GIF',
action: () => console.log('Animation'),
icon: <IconAnimation className="h-20 w-20" />,
},
{
id: 'simulationLog' as const,
title: 'Simulation Log',
subTitle: 'CSV',
action: () => {
const csvOutput = data.map((item) => {
return {
...item,
date: new Date(item.date * 1e3),
};
});
CsvDataService.exportToCsv('data.csv', csvOutput);
},
icon: <IconLog className="h-20 w-20" />,
},
];

return (
<DropdownMenu
isOpen={isOpen}
setIsOpen={setIsOpen}
placement="bottom-end"
className="rounded-[10px] !border-0 p-8 text-white"
aria-expanded={isOpen}
button={(attr) => (
<button
{...attr}
className={cn(
buttonStyles({ variant: 'black' }),
'relative h-40 w-40 border-silver !p-0'
)}
onClick={(e) => {
setIsOpen(true);
attr.onClick(e);
}}
aria-label="Download Simulation"
>
<span className="flex h-36 w-36 items-center justify-center">
<IconDownload className="h-18 w-18" />
</span>
</button>
)}
>
{items?.map(({ id, action, title, subTitle, icon }, index) => {
return (
<div key={`${index}_${id}`} className="border-grey5">
<button
role="menuitem"
aria-labelledby="optionTitle"
className="hover:bg-body w-full rounded-6 p-8 text-left"
onClick={() => {
action();
setIsOpen(false);
}}
>
<div className="flex items-center gap-y-8">
{icon}
<span id="optionTitle" className="mx-8 text-14 font-weight-500">
{title}
</span>
<span className="text-14 font-weight-400 text-white/40">
{subTitle}
</span>
</div>
</button>
</div>
);
})}
</DropdownMenu>
);
};
58 changes: 58 additions & 0 deletions src/components/simulator/SimulatorPageTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Pathnames, PathParams } from 'libs/routing';
import { cn } from 'utils/helpers';
import { ReactComponent as IconAnimation } from 'assets/icons/movie.svg';
import { ReactComponent as IconSummary } from 'assets/icons/image.svg';

export interface StrategyTab {
label: string;
href: Pathnames;
params?: PathParams;
icon: JSX.Element;
badge?: number;
}

interface Props {
showSummary: boolean;
setShowSummary: React.Dispatch<React.SetStateAction<boolean>>;
}

export const SimulatorPageTabs = ({ showSummary, setShowSummary }: Props) => {
const tabs = [
{
label: 'Animation',
icon: <IconAnimation className="h-18 w-18" />,
isActive: () => !showSummary,
click: () => setShowSummary(false),
},
{
label: 'Summary',
icon: <IconSummary className="h-18 w-18" />,
isActive: () => showSummary,
click: () => setShowSummary(true),
},
];

return (
<nav
aria-label="Simulation Tabs"
className="max-w-40 flex h-40 w-full gap-2 rounded-full border-2 border-silver p-4 text-14 md:w-auto"
>
{tabs.map(({ label, icon, isActive, click }) => {
const active = isActive();
return (
<button
key={label}
onClick={click}
className={cn(
'flex w-full items-center justify-center gap-4 rounded-full py-5 px-16',
active ? 'bg-white/10' : 'bg-transparent text-white/60'
)}
>
{icon}
<span className="text-14 font-weight-500">{label}</span>
</button>
);
})}
</nav>
);
};
73 changes: 73 additions & 0 deletions src/components/simulator/SimulatorSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Link, useSearch } from 'libs/routing';
import { buttonStyles } from 'components/common/button/buttonStyles';
import { SimulatorSummaryGains } from './SimulatorSummaryGains';
import { SimulatorSummaryRoi } from './SimulatorSummaryRoi';
import { SimulatorSummaryTokens } from './SimulatorTokens';
import { SimulatorSummaryTable } from './SimulatorSummaryTable';
import { useTokens } from 'hooks/useTokens';

interface Props {
roi: number;
gains: number;
isLoading: boolean;
}

export const SimulatorSummary = ({ roi, gains, isLoading }: Props) => {
const search = useSearch({ from: '/simulator/result' });

const portfolioGains = isLoading ? 0.0 : gains;
const portfolioRoi = isLoading ? 0.0 : roi;

const { getTokenById } = useTokens();
const baseToken = getTokenById(search.baseToken);
const quoteToken = getTokenById(search.quoteToken);

const summaryData = {
buyMin: search.buyMin,
buyMax: search.buyMax,
sellMin: search.sellMin,
sellMax: search.sellMax,
baseBudget: search.baseBudget,
quoteBudget: search.quoteBudget,
};

const strategyType = 'recurring';

return (
<header className="my-8 flex flex-wrap gap-8">
<section className="flex flex-grow flex-wrap items-center justify-evenly gap-8 rounded-10 bg-black p-16 md:justify-between">
<SimulatorSummaryTokens
baseToken={baseToken!}
quoteToken={quoteToken!}
strategyType={strategyType}
/>
<SimulatorSummaryTable
summaryData={summaryData}
baseToken={baseToken!}
quoteToken={quoteToken!}
/>
</section>
<section className="flex flex-grow flex-wrap items-center justify-evenly gap-8 rounded-10 bg-black p-16 md:justify-between">
<SimulatorSummaryGains
portfolioGains={portfolioGains}
quoteToken={quoteToken!}
/>
<SimulatorSummaryRoi portfolioRoi={portfolioRoi} />
<Link
to="/strategies/create"
search={{
base: baseToken!.address,
quote: quoteToken!.address,
strategyType:
strategyType === 'recurring' ? 'recurring' : undefined,
strategySettings:
strategyType === 'recurring' ? 'range' : 'overlapping',
}}
className={buttonStyles({ variant: 'success', size: 'md' })}
>
Create strategy
</Link>
</section>
</header>
);
};
39 changes: 39 additions & 0 deletions src/components/simulator/SimulatorSummaryGains.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Tooltip } from 'components/common/tooltip/Tooltip';
import { ReactComponent as IconTooltip } from 'assets/icons/tooltip.svg';
import { cn, prettifyNumber } from 'utils/helpers';
import { FC } from 'react';
import { Token } from 'libs/tokens';
import { useFiatCurrency } from 'hooks/useFiatCurrency';

interface Props {
quoteToken: Token;
portfolioGains: number;
}

export const SimulatorSummaryGains = ({
portfolioGains,
quoteToken,
}: Props) => {
const quoteFiat = useFiatCurrency(quoteToken);
const budgetFormatted = prettifyNumber(portfolioGains, {
currentCurrency: quoteFiat.selectedFiatCurrency,
});

return (
<article className={cn('flex flex-col rounded-8')}>
<Tooltip element={<TooltipContent />}>
<h4 className="text-secondary flex items-center gap-4 font-mono !text-12">
Estimated Gains
<IconTooltip className="h-10 w-10" />
</h4>
</Tooltip>
<p className={`text-24 font-weight-500`}>{budgetFormatted}</p>
</article>
);
};

const TooltipContent: FC<{}> = () => (
<>
<span className="align-middle">TBD.&nbsp;</span>
</>
);
Loading
Loading