Skip to content

Commit

Permalink
Merge pull request #1005 from bancorprotocol/simulator-summary
Browse files Browse the repository at this point in the history
Simulator summary
  • Loading branch information
tiagofilipenunes authored Jan 29, 2024
2 parents ef074c4 + 48fdb15 commit 5f7c949
Show file tree
Hide file tree
Showing 17 changed files with 596 additions and 43 deletions.
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

0 comments on commit 5f7c949

Please sign in to comment.