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

chore: disable limit orders promotion banner by setting SHOW_LIMIT_OR… #5333

Closed
wants to merge 7 commits into from
2 changes: 2 additions & 0 deletions apps/cowswap-frontend/src/common/constants/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export const ordersTableFeatures = {
DISPLAY_EST_EXECUTION_PRICE: false,
DISPLAY_EXECUTION_TIME: false,
}

export const SHOW_LIMIT_ORDERS_PROMO = false // Feature flag for the limit orders promotion banner
22 changes: 18 additions & 4 deletions apps/cowswap-frontend/src/common/pure/ArrowBackground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UI } from '@cowprotocol/ui'

import styled from 'styled-components/macro'

const ArrowBackgroundWrapper = styled.div`
const ArrowBackgroundWrapper = styled.div<{ $maxOpacity: number }>`
position: absolute;
top: 0;
left: 0;
Expand All @@ -14,7 +14,15 @@ const ArrowBackgroundWrapper = styled.div`
pointer-events: none;
visibility: hidden;
opacity: 0;
transition: opacity 0.3s ease-in-out;
transition: opacity 1s ease-in-out;

&.visible {
visibility: visible;
}

&.show {
opacity: ${({ $maxOpacity }) => $maxOpacity};
}
`

const MIN_FONT_SIZE = 18
Expand All @@ -26,19 +34,23 @@ const Arrow = styled.div<{ delay: number; color: string; fontSize: number }>`
color: ${({ color }) => color};
animation: float 2s infinite linear;
animation-delay: ${({ delay }) => delay}s;
opacity: 0;

@keyframes float {
0% {
transform: translateY(100%);
opacity: 0;
}
10% {
transform: translateY(80%);
opacity: 0.3;
}
50% {
transform: translateY(0);
opacity: 0.3;
}
90% {
transform: translateY(-80%);
opacity: 0.2;
}
100% {
Expand All @@ -51,11 +63,13 @@ const Arrow = styled.div<{ delay: number; color: string; fontSize: number }>`
export interface ArrowBackgroundProps {
count?: number
color?: string
className?: string
maxOpacity?: number
}

export const ArrowBackground = memo(
forwardRef<HTMLDivElement, ArrowBackgroundProps>(
({ count = 36, color = `var(${UI.COLOR_COWAMM_LIGHT_GREEN})` }, ref) => {
({ count = 36, color = `var(${UI.COLOR_COWAMM_LIGHT_GREEN})`, className = '', maxOpacity = 0.25 }, ref) => {
const arrows = useMemo(() => {
return Array.from({ length: count }, (_, index) => ({
delay: (index / count) * 4,
Expand All @@ -66,7 +80,7 @@ export const ArrowBackground = memo(
}, [count])

return (
<ArrowBackgroundWrapper ref={ref}>
<ArrowBackgroundWrapper ref={ref} className={className} $maxOpacity={maxOpacity}>
{arrows.map((arrow, index) => (
<Arrow
key={index}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { atomWithStorage } from 'jotai/utils'

export const limitOrdersPromoDismissedAtom = atomWithStorage<boolean>('limitOrdersPromo_dismissed', false)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useAdvancedOrdersRawState } from './useAdvancedOrdersRawState'

import { AdvancedOrdersRawState } from '../state/advancedOrdersAtom'

export function useIsWidgetUnlocked(): boolean {
const rawState = useAdvancedOrdersRawState() as AdvancedOrdersRawState

return rawState.isUnlocked
}
1 change: 1 addition & 0 deletions apps/cowswap-frontend/src/modules/advancedOrders/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './containers/AdvancedOrdersWidget'
export * from './hooks/useAdvancedOrdersRawState'
export * from './hooks/useAdvancedOrdersDerivedState'
export * from './hooks/useIsWidgetUnlocked'
export * from './state/advancedOrdersAtom'
export * from './updaters/FillAdvancedOrdersDerivedStateUpdater'
export * from './updaters/SetupAdvancedOrderAmountsFromUrlUpdater'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useTradeConfirmState } from 'modules/trade'
import { BulletListItem, UnlockWidgetScreen } from 'modules/trade/pure/UnlockWidgetScreen'
import { useSetTradeQuoteParams, useTradeQuote } from 'modules/tradeQuote'

import { SHOW_LIMIT_ORDERS_PROMO } from 'common/constants/featureFlags'
import { useRateInfoParams } from 'common/hooks/useRateInfoParams'
import { CurrencyInfo } from 'common/pure/CurrencyInputPanel/types'

Expand Down Expand Up @@ -164,18 +165,19 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => {

const slots: TradeWidgetSlots = {
settingsWidget: <SettingsWidget />,
lockScreen: isUnlocked ? undefined : (
<UnlockWidgetScreen
id="limit-orders"
items={LIMIT_BULLET_LIST_CONTENT}
buttonLink={UNLOCK_SCREEN.buttonLink}
title={UNLOCK_SCREEN.title}
subtitle={UNLOCK_SCREEN.subtitle}
orderType={UNLOCK_SCREEN.orderType}
buttonText={UNLOCK_SCREEN.buttonText}
handleUnlock={() => updateLimitOrdersState({ isUnlocked: true })}
/>
),
lockScreen:
isUnlocked || SHOW_LIMIT_ORDERS_PROMO ? undefined : (
<UnlockWidgetScreen
id="limit-orders"
items={LIMIT_BULLET_LIST_CONTENT}
buttonLink={UNLOCK_SCREEN.buttonLink}
title={UNLOCK_SCREEN.title}
subtitle={UNLOCK_SCREEN.subtitle}
orderType={UNLOCK_SCREEN.orderType}
buttonText={UNLOCK_SCREEN.buttonText}
handleUnlock={() => updateLimitOrdersState({ isUnlocked: true })}
/>
),
middleContent: (
<>
{!isWrapOrUnwrap &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { useNavigate } from 'common/hooks/useNavigate'

import * as styledEl from './styled'
import { TradeWidgetForm } from './TradeWidgetForm'
import { TradeWidgetModals } from './TradeWidgetModals'
import { TradeWidgetUpdaters } from './TradeWidgetUpdaters'
import { TradeWidgetProps } from './types'

import { useLimitOrdersPromoBanner } from '../../hooks/useLimitOrdersPromoBanner'
import { LimitOrdersPromoBanner } from '../../pure/LimitOrdersPromoBanner'

export const TradeWidgetContainer = styledEl.Container

export function TradeWidget(props: TradeWidgetProps) {
Expand All @@ -14,7 +19,40 @@ export function TradeWidget(props: TradeWidgetProps) {
tradeQuoteStateOverride,
enableSmartSlippage,
} = params
const modals = TradeWidgetModals({confirmModal, genericModal, selectTokenWidget: slots.selectTokenWidget})
const modals = TradeWidgetModals({ confirmModal, genericModal, selectTokenWidget: slots.selectTokenWidget })

const { isVisible, onDismiss, isLimitOrdersTab } = useLimitOrdersPromoBanner()
const navigate = useNavigate()

const handleCtaClick = () => {
// First dismiss the banner
onDismiss()
// Navigate to limit orders
navigate('/limit')
}

// Inject the banner into the slots and use it as lockScreen when visible
const slotsWithBanner = {
...slots,
topContent: (
<>
{isVisible && (
<LimitOrdersPromoBanner
onCtaClick={handleCtaClick}
onDismiss={onDismiss}
isLimitOrdersTab={isLimitOrdersTab}
/>
)}
{slots.topContent}
</>
),
// When banner is visible, use it as lockScreen to hide the rest of the content
lockScreen: isVisible ? (
<LimitOrdersPromoBanner onCtaClick={handleCtaClick} onDismiss={onDismiss} isLimitOrdersTab={isLimitOrdersTab} />
) : (
slots.lockScreen
),
}

return (
<>
Expand All @@ -29,7 +67,7 @@ export function TradeWidget(props: TradeWidgetProps) {
{slots.updaters}
</TradeWidgetUpdaters>

<styledEl.Container>{modals || <TradeWidgetForm {...props} />}</styledEl.Container>
<styledEl.Container>{modals || <TradeWidgetForm {...props} slots={slotsWithBanner} />}</styledEl.Container>
</styledEl.Container>
</>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useCallback, useEffect, useState } from 'react'

import { useMatch } from 'react-router-dom'

import { isInjectedWidget } from '@cowprotocol/common-utils'

import { Routes } from 'common/constants/routes'
import { SHOW_LIMIT_ORDERS_PROMO } from 'common/constants/featureFlags'

import { useInjectedWidgetParams } from 'modules/injectedWidget'

const STORAGE_KEY = 'limitOrdersPromoBanner:v0'

export function useLimitOrdersPromoBanner() {
const isLimitOrdersTab = !!(useMatch(Routes.LIMIT_ORDER) || useMatch(Routes.LONG_LIMIT_ORDER))
const [isVisible, setIsVisible] = useState(false)
const { standaloneMode } = useInjectedWidgetParams()

// Effect to handle feature flag changes
useEffect(() => {
// Never show the banner in widget mode (standalone or injected)
if (standaloneMode || isInjectedWidget()) {
setIsVisible(false)
return
}

if (!SHOW_LIMIT_ORDERS_PROMO) {
setIsVisible(false)
return
}

const storedValue = localStorage.getItem(STORAGE_KEY)
setIsVisible(storedValue === null)
}, [SHOW_LIMIT_ORDERS_PROMO, standaloneMode])

const onDismiss = useCallback(() => {
localStorage.setItem(STORAGE_KEY, 'dismissed')
setIsVisible(false)
}, [])

return {
isVisible,
onDismiss,
isLimitOrdersTab,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useState, useEffect } from 'react'

import iconCompleted from '@cowprotocol/assets/cow-swap/check.svg'

import SVG from 'react-inlinesvg'

import { ArrowBackground } from 'common/pure/ArrowBackground'

import * as styledEl from './styled'

const BULLET_POINTS = [
'Locked limits - lock or unlock prices for finer control, the order does the rest',
'Easily set and manage your orders in USD',
'Try before you buy - see the potential fill price before you hit trade',
'Longer limit orders - place orders for up to a year.',
'Trade your way - personalize the interface and customize your limit orders',
'More spacious, and enhanced design!',
]

interface LimitOrdersPromoBannerProps {
onCtaClick: () => void
onDismiss: () => void
isLimitOrdersTab?: boolean
}

export function LimitOrdersPromoBanner({
onCtaClick,
onDismiss,
isLimitOrdersTab = false,
}: LimitOrdersPromoBannerProps) {
const [isHovered, setIsHovered] = useState(false)
const [arrowRef, setArrowRef] = useState<HTMLDivElement | null>(null)
const [arrowsReady, setArrowsReady] = useState(false)

useEffect(() => {
// First make arrows visible but transparent
setArrowsReady(true)

// Wait for animations to be ready before showing
const timer = setTimeout(() => {
if (arrowRef) {
arrowRef.classList.add('show')
}
}, 1000)

return () => {
if (timer) {
clearTimeout(timer)
}
}
}, [arrowRef])

return (
<styledEl.BannerWrapper>
<ArrowBackground ref={setArrowRef} count={20} className={arrowsReady ? 'visible' : ''} maxOpacity={0.3} />
<styledEl.CloseButton size={24} onClick={onDismiss} />
<styledEl.TitleSection>
<h3>
Limit orders, but <span>s-moooo-ther</span> than ever
</h3>
<strong>
Limit orders are now easier to use. <br />
Give them a try
</strong>
</styledEl.TitleSection>

<styledEl.List>
{BULLET_POINTS.map((point, index) => (
<li key={index}>
<span>
<SVG src={iconCompleted} />
</span>
{point}
</li>
))}
</styledEl.List>

<styledEl.ControlSection>
<styledEl.CTAButton
onClick={onCtaClick}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<styledEl.ButtonText $hover={isHovered}>Try new limit orders now</styledEl.ButtonText>
<styledEl.Shimmer />
</styledEl.CTAButton>
{!isLimitOrdersTab && <styledEl.DismissLink onClick={onDismiss}>Maybe next time</styledEl.DismissLink>}
</styledEl.ControlSection>
</styledEl.BannerWrapper>
)
}
Loading
Loading