-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/main' into issue-#1648
- Loading branch information
Showing
71 changed files
with
922 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file modified
BIN
-5 Bytes
(100%)
...screenshots/simulator/recurring/Recurring_limit_limit/simulator-input-price.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.53 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.22 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.94 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_buy_range/duplicate/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.93 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.63 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.71 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_sell_limit/duplicate/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.12 KB
(100%)
e2e/screenshots/strategy/disposable/Disposable_sell_limit/undercut/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.95 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_sell_range/create/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.95 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_sell_range/duplicate/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.93 KB
(110%)
e2e/screenshots/strategy/disposable/Disposable_sell_range/undercut/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.67 KB
(100%)
e2e/screenshots/strategy/overlapping/Overlapping/create/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+1.07 KB
(100%)
e2e/screenshots/strategy/overlapping/Overlapping/create/my-strategy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.67 KB
(100%)
e2e/screenshots/strategy/overlapping/Overlapping/duplicate/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.68 KB
(100%)
e2e/screenshots/strategy/overlapping/Overlapping/undercut/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.6 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_limit_limit/create/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+9.67 KB
(120%)
e2e/screenshots/strategy/recurring/Recurring_limit_limit/create/my-strategy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
-99 Bytes
(100%)
e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+3.14 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+3.07 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_limit_limit/undercut/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.6 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_limit_range/create/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+3.1 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.91 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_limit_range/undercut/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+3.09 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.91 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified
BIN
+2.55 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_range_range/create/form.png
Oops, something went wrong.
Binary file modified
BIN
+3.03 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_range_range/duplicate/form.png
Oops, something went wrong.
Binary file modified
BIN
+2.94 KB
(100%)
e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { FC } from 'react'; | ||
import { CartStrategy } from 'libs/queries'; | ||
import { CartStrategyItems } from './CartStrategy'; | ||
import { cn } from 'utils/helpers'; | ||
import styles from 'components/strategies/overview/StrategyContent.module.css'; | ||
|
||
interface Props { | ||
strategies: CartStrategy[]; | ||
} | ||
|
||
export const CartList: FC<Props> = ({ strategies }) => { | ||
return ( | ||
<ul className={cn('grid gap-20', styles.strategyList)}> | ||
{strategies.map((strategy, i) => { | ||
const className = i < 12 ? styles.animateItem : ''; | ||
const style = { ['--delay' as any]: `${i * 50}ms` }; | ||
return ( | ||
<CartStrategyItems | ||
key={strategy.id} | ||
strategy={strategy} | ||
style={style} | ||
className={className} | ||
/> | ||
); | ||
})} | ||
</ul> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { PairName } from 'components/common/DisplayPair'; | ||
import { TokensOverlap } from 'components/common/tokensOverlap'; | ||
import { StrategyBlockBudget } from 'components/strategies/overview/strategyBlock/StrategyBlockBudget'; | ||
import { StrategyBlockBuySell } from 'components/strategies/overview/strategyBlock/StrategyBlockBuySell'; | ||
import { StrategyGraph } from 'components/strategies/overview/strategyBlock/StrategyGraph'; | ||
import { ReactComponent as IconTrash } from 'assets/icons/trash.svg'; | ||
import { CartStrategy } from 'libs/queries'; | ||
import { CSSProperties, FC } from 'react'; | ||
import { cn } from 'utils/helpers'; | ||
import { | ||
isOverlappingStrategy, | ||
isZero, | ||
outSideMarketWarning, | ||
} from 'components/strategies/common/utils'; | ||
import { Warning } from 'components/common/WarningMessageWithIcon'; | ||
import { useMarketPrice } from 'hooks/useMarketPrice'; | ||
import { removeStrategyFromCart } from './utils'; | ||
import { useWagmi } from 'libs/wagmi'; | ||
import { | ||
isMaxBelowMarket, | ||
isMinAboveMarket, | ||
} from 'components/strategies/overlapping/utils'; | ||
|
||
interface Props { | ||
strategy: CartStrategy; | ||
className?: string; | ||
style?: CSSProperties; | ||
} | ||
|
||
const getWarning = (strategy: CartStrategy, marketPrice?: number) => { | ||
const { base, order0, order1 } = strategy; | ||
if (isZero(order0.balance) && isZero(order1.balance)) { | ||
return 'Please note that your strategy will be inactive as it will not have any budget.'; | ||
} | ||
if (isOverlappingStrategy(strategy)) { | ||
const aboveMarket = isMinAboveMarket(order0); | ||
const belowMarket = isMaxBelowMarket(order1); | ||
if (aboveMarket || belowMarket) { | ||
return 'Notice: your strategy is “out of the money” and will be traded when the market price moves into your price range.'; | ||
} | ||
} else { | ||
const buyOutsideMarket = outSideMarketWarning({ | ||
base, | ||
marketPrice, | ||
min: order0.startRate, | ||
max: order0.endRate, | ||
buy: true, | ||
}); | ||
if (buyOutsideMarket) return buyOutsideMarket; | ||
const sellOutsideMarket = outSideMarketWarning({ | ||
base, | ||
marketPrice, | ||
min: order1.startRate, | ||
max: order1.endRate, | ||
buy: false, | ||
}); | ||
if (sellOutsideMarket) return sellOutsideMarket; | ||
} | ||
}; | ||
|
||
export const CartStrategyItems: FC<Props> = (props) => { | ||
const { strategy, style, className } = props; | ||
const { base, quote } = strategy; | ||
const { marketPrice } = useMarketPrice({ base, quote }); | ||
const { user } = useWagmi(); | ||
|
||
const warningMsg = getWarning(strategy, marketPrice); | ||
|
||
const remove = async () => { | ||
if (!user) return; | ||
removeStrategyFromCart(user, strategy); | ||
}; | ||
|
||
return ( | ||
<li | ||
id={strategy.id} | ||
style={style} | ||
className={cn( | ||
'rounded-10 bg-background-900 grid grid-cols-1 grid-rows-[auto_auto_auto] gap-16 p-24', | ||
className | ||
)} | ||
> | ||
<header className="flex items-center gap-16"> | ||
<TokensOverlap size={40} tokens={[base, quote]} /> | ||
<h3 className="text-18 flex gap-6" data-testid="token-pair"> | ||
<PairName baseToken={base} quoteToken={quote} /> | ||
</h3> | ||
<div role="menubar" className="ml-auto flex gap-8"> | ||
<button | ||
role="menuitem" | ||
type="button" | ||
className="size-38 rounded-6 border-background-800 grid place-items-center border-2 hover:bg-white/10 active:bg-white/20" | ||
aria-label="Delete strategy" | ||
onClick={remove} | ||
> | ||
<IconTrash className="size-16" /> | ||
</button> | ||
</div> | ||
</header> | ||
<div className="relative"> | ||
<StrategyBlockBudget strategy={strategy} /> | ||
{warningMsg && ( | ||
<div className="rounded-8 border-warning absolute inset-0 grid place-items-center border-2 bg-black/60 p-8 backdrop-blur-sm"> | ||
<Warning message={warningMsg} /> | ||
</div> | ||
)} | ||
</div> | ||
<div className="rounded-8 border-background-800 grid grid-cols-2 grid-rows-[auto_auto] border-2"> | ||
<StrategyBlockBuySell | ||
strategy={strategy} | ||
buy | ||
className="border-background-800 border-r-2" | ||
/> | ||
<StrategyBlockBuySell strategy={strategy} /> | ||
<div className="border-background-800 col-start-1 col-end-3 border-t-2"> | ||
<StrategyGraph strategy={strategy} /> | ||
</div> | ||
</div> | ||
</li> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Link } from '@tanstack/react-router'; | ||
import { buttonStyles } from 'components/common/button/buttonStyles'; | ||
import { ReactComponent as CartIcon } from 'assets/icons/cart.svg'; | ||
|
||
export const EmptyCart = () => { | ||
return ( | ||
<section className="gap-30 py-50 border-background-800 animate-fade relative mx-auto grid h-[600px] w-full max-w-[1240px] place-items-center content-center rounded border-2 px-20 text-center"> | ||
<div className="bg-background-800 grid rounded-full p-16 [grid-template-areas:'stack']"> | ||
<CartIcon className="size-40 place-self-center text-white [grid-area:stack]" /> | ||
<span className="bg-success-light grid size-16 place-items-center justify-self-end rounded-full text-[8px] leading-[1.4] text-black [grid-area:stack]"> | ||
0 | ||
</span> | ||
</div> | ||
<h2 className="max-w-[440px] text-[32px] leading-[36px]"> | ||
Your Cart is Empty | ||
</h2> | ||
<Link | ||
to="/trade/disposable" | ||
className={buttonStyles({ variant: 'success' })} | ||
> | ||
Create Strategy | ||
</Link> | ||
</section> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import { useFiatCurrency } from 'hooks/useFiatCurrency'; | ||
import { useTokens } from 'hooks/useTokens'; | ||
import { | ||
CartStrategy, | ||
CreateStrategyOrder, | ||
CreateStrategyParams, | ||
} from 'libs/queries'; | ||
import { useGetMultipleTokenPrices } from 'libs/queries/extApi/tokenPrice'; | ||
import { SafeDecimal } from 'libs/safedecimal'; | ||
import { useEffect, useMemo, useState } from 'react'; | ||
import { lsService } from 'services/localeStorage'; | ||
import { useWagmi } from 'libs/wagmi'; | ||
import strategyStyle from 'components/strategies/overview/StrategyContent.module.css'; | ||
import formStyle from 'components/strategies/common/form.module.css'; | ||
|
||
export type Cart = (CreateStrategyParams & { id: string })[]; | ||
|
||
const toOrder = (sdkOrder: CreateStrategyOrder) => ({ | ||
balance: sdkOrder.budget, | ||
startRate: sdkOrder.min, | ||
endRate: sdkOrder.max, | ||
marginalRate: sdkOrder.marginalPrice, | ||
}); | ||
|
||
export const useStrategyCart = () => { | ||
const [cart, setCart] = useState<Cart>([]); | ||
const { user } = useWagmi(); | ||
const { getTokenById } = useTokens(); | ||
const { selectedFiatCurrency } = useFiatCurrency(); | ||
|
||
const tokens = cart.map(({ base, quote }) => [base, quote]).flat(); | ||
const addresses = Array.from(new Set(tokens)); | ||
const priceQueries = useGetMultipleTokenPrices(addresses); | ||
|
||
useEffect(() => { | ||
if (!user) return setCart([]); | ||
const carts = lsService.getItem('carts') ?? {}; | ||
setCart(carts[user] ?? []); | ||
}, [user]); | ||
|
||
useEffect(() => { | ||
const handler = (event: StorageEvent) => { | ||
if (event.key !== lsService.keyFormatter('carts')) return; | ||
if (!user) return; | ||
const next = JSON.parse(event.newValue ?? '{}'); | ||
setCart(next[user] ?? []); | ||
}; | ||
window.addEventListener('storage', handler); | ||
return () => window.removeEventListener('storage', handler); | ||
}); | ||
|
||
return useMemo(() => { | ||
const prices: Record<string, number | undefined> = {}; | ||
for (let i = 0; i < priceQueries.length; i++) { | ||
const address = addresses[i]; | ||
const price = priceQueries[i].data?.[selectedFiatCurrency]; | ||
prices[address] = price; | ||
} | ||
return cart.map((strategy): CartStrategy => { | ||
const basePrice = new SafeDecimal(prices[strategy.base] ?? 0); | ||
const quotePrice = new SafeDecimal(prices[strategy.quote] ?? 0); | ||
const base = basePrice.times(strategy.order1.budget); | ||
const quote = quotePrice.times(strategy.order0.budget); | ||
const total = base.plus(quote); | ||
return { | ||
// temporary id for react key | ||
id: strategy.id, | ||
// We know the tokens are imported because cart comes from localstorage | ||
base: getTokenById(strategy.base)!, | ||
quote: getTokenById(strategy.quote)!, | ||
order0: toOrder(strategy.order0), | ||
order1: toOrder(strategy.order1), | ||
fiatBudget: { base, quote, total }, | ||
}; | ||
}); | ||
}, [addresses, cart, getTokenById, priceQueries, selectedFiatCurrency]); | ||
}; | ||
|
||
export const addStrategyToCart = ( | ||
user: string, | ||
params: CreateStrategyParams | ||
) => { | ||
const id = crypto.randomUUID(); | ||
const carts = lsService.getItem('carts') ?? {}; | ||
carts[user] ||= []; | ||
carts[user].push({ id, ...params }); | ||
lsService.setItem('carts', carts); | ||
|
||
// Animation | ||
const getTranslate = (target: HTMLElement, elRect: DOMRect) => { | ||
const { top, height, left, width } = target.getBoundingClientRect(); | ||
const centerX = left + width / 2; | ||
const centerY = top + height / 2; | ||
const radius = elRect.width / 2; | ||
const translateX = centerX - elRect.left - radius; | ||
const translateY = centerY - elRect.top - radius; | ||
return `translate(${translateX}px, ${translateY}px)`; | ||
}; | ||
const source = document.querySelector<HTMLElement>(`.${formStyle.addCart}`); | ||
const target = document.getElementById('menu-cart-link'); | ||
const el = document.getElementById('animate-cart-indicator'); | ||
if (!source || !target || !el) return; | ||
const currentRect = el.getBoundingClientRect(); | ||
const sourceTranslate = getTranslate(source, currentRect); | ||
const targetTranslate = getTranslate(target, currentRect); | ||
const animation = el.animate( | ||
[ | ||
{ opacity: 0, transform: `${sourceTranslate} scale(15)` }, | ||
{ opacity: 1, transform: sourceTranslate }, | ||
{ opacity: 1, transform: targetTranslate }, | ||
{ opacity: 0, transform: `${targetTranslate} scale(5)` }, | ||
], | ||
{ | ||
duration: 1000, | ||
easing: 'cubic-bezier(0,.6,1,.4)', | ||
} | ||
); | ||
return animation.finished; | ||
}; | ||
|
||
export const removeStrategyFromCart = async ( | ||
user: string, | ||
strategy: CartStrategy | ||
) => { | ||
// Animate leaving strategy | ||
const keyframes = { opacity: 0, transform: 'scale(0.9)' }; | ||
const option = { | ||
duration: 200, | ||
easing: 'cubic-bezier(.55, 0, 1, .45)', | ||
fill: 'forwards' as const, | ||
}; | ||
await document.getElementById(strategy.id)?.animate(keyframes, option) | ||
.finished; | ||
|
||
// Delete from localstorage | ||
const current = lsService.getItem('carts') ?? {}; | ||
if (!current[user]?.length) return; | ||
current[user] = current[user].filter(({ id }) => id !== strategy.id); | ||
lsService.setItem('carts', current); | ||
|
||
// Animate remaining strategies | ||
const selector = `.${strategyStyle.strategyList} > li`; | ||
const elements = document.querySelectorAll<HTMLElement>(selector); | ||
const boxes = new Map<HTMLElement, DOMRect>(); | ||
for (const el of elements) { | ||
boxes.set(el, el.getBoundingClientRect()); | ||
} | ||
let attempts = 0; | ||
const checkChange = () => { | ||
if (attempts > 10) return; | ||
attempts++; | ||
const updated = document.querySelectorAll<HTMLElement>(selector); | ||
if (elements.length === updated.length) { | ||
return requestAnimationFrame(checkChange); | ||
} | ||
for (const [el, box] of boxes.entries()) { | ||
const newBox = el.getBoundingClientRect(); | ||
if (box.top === newBox.top && box.left === newBox.left) continue; | ||
const keyframes = [ | ||
// eslint-disable-next-line prettier/prettier | ||
{ | ||
transform: `translate(${box.left - newBox.left}px, ${ | ||
box.top - newBox.top | ||
}px)`, | ||
}, | ||
{ transform: `translate(0px, 0px)` }, | ||
]; | ||
el.animate(keyframes, { | ||
duration: 300, | ||
easing: 'cubic-bezier(.85, 0, .15, 1)', | ||
}); | ||
} | ||
}; | ||
requestAnimationFrame(checkChange); | ||
}; | ||
|
||
export const clearCart = (user: string) => { | ||
const current = lsService.getItem('carts') ?? {}; | ||
current[user] = []; | ||
lsService.setItem('carts', current); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.