diff --git a/apps/main/src/dex/components/PoolAlertBanner.tsx b/apps/main/src/dex/components/PoolAlertBanner.tsx index a1033087a2..2429d707ff 100644 --- a/apps/main/src/dex/components/PoolAlertBanner.tsx +++ b/apps/main/src/dex/components/PoolAlertBanner.tsx @@ -3,10 +3,9 @@ import { createPortal } from 'react-dom' import { useIsDesktop } from '@ui-kit/hooks/useBreakpoints' import { useDismissBanner } from '@ui-kit/hooks/useLocalStorage' import { Banner, BannerProps } from '@ui-kit/shared/ui/Banner' +import { Duration } from '@ui-kit/themes/design/0_primitives' import { AlertType, PoolAlert } from '../types/main.types' -const ONE_DAY_MS = 24 * 60 * 60 * 1000 // 24 hours in milliseconds - /** Maps AlertType to BannerSeverity */ const alertTypeToBannerSeverity: Record = { error: 'alert', @@ -25,7 +24,7 @@ export const PoolAlertBanner = ({ poolAlertBannerKey: string alertType: AlertType }) => { - const { shouldShowBanner, dismissBanner } = useDismissBanner(poolAlertBannerKey, ONE_DAY_MS) + const { shouldShowBanner, dismissBanner } = useDismissBanner(poolAlertBannerKey, Duration.Banner.Daily) const isDesktop = useIsDesktop() // eslint-disable-next-line react-hooks/exhaustive-deps -- isDesktop triggers re-query when header changes (desktop ↔ mobile) const portalTarget = useMemo(() => document.getElementsByTagName('header')[0], [isDesktop]) diff --git a/apps/main/src/dex/store/createPoolsSlice.ts b/apps/main/src/dex/store/createPoolsSlice.ts index 1307759a0b..353945bb66 100644 --- a/apps/main/src/dex/store/createPoolsSlice.ts +++ b/apps/main/src/dex/store/createPoolsSlice.ts @@ -44,6 +44,7 @@ import { convertToLocaleTimestamp } from '@ui-kit/features/candle-chart/utils' import { requireLib } from '@ui-kit/features/connect-wallet' import { log } from '@ui-kit/lib/logging' import { fetchTokenUsdRate, getTokenUsdRateQueryData } from '@ui-kit/lib/model/entities/token-usd-rate' +import { TIME_FRAMES } from '@ui-kit/lib/model/time' import { fetchNetworks } from '../entities/networks' import { getPools } from '../lib/pools' @@ -526,7 +527,7 @@ const createPoolsSlice = (set: StoreApi['setState'], get: StoreApi fetchPricesPoolSnapshots: async (chainId: ChainId, poolAddress: string) => { const networks = await fetchNetworks() if (networks[chainId].pricesApi) { - const startTime = Math.floor((Date.now() - 24 * 60 * 60 * 1000) / 1000) + const startTime = Math.floor((Date.now() - TIME_FRAMES.DAY_MS) / 1000) const endTime = Math.floor(Date.now() / 1000) const network = networks[chainId].id.toLowerCase() diff --git a/apps/main/src/loan/entities/scrvusd-yield.ts b/apps/main/src/loan/entities/scrvusd-yield.ts index e8f02937ad..0a6f665389 100644 --- a/apps/main/src/loan/entities/scrvusd-yield.ts +++ b/apps/main/src/loan/entities/scrvusd-yield.ts @@ -2,6 +2,7 @@ import { getYield } from '@curvefi/prices-api/savings' import type { Yield } from '@curvefi/prices-api/savings/models' import { queryFactory } from '@ui-kit/lib/model/query' import { timeOptionValidationSuite } from '@ui-kit/lib/model/query/time-option-validation' +import { TIME_FRAMES } from '@ui-kit/lib/model/time' import type { TimeOption } from '@ui-kit/lib/types/scrvusd' export type ScrvUsdYieldWithAverages = Yield & { proj_apy_7d_avg: number; proj_apy_total_avg: number } @@ -9,9 +10,9 @@ export type ScrvUsdYieldWithAverages = Yield & { proj_apy_7d_avg: number; proj_a export const _getScrvUsdYield = async (params: { timeOption: TimeOption }) => { // calcs starting timestamp const timeOptionCalc: Record = { - '1M': 30 * 24 * 60 * 60 * 1000, // 30 days - '6M': 180 * 24 * 60 * 60 * 1000, // 180 days - '1Y': 365 * 24 * 60 * 60 * 1000, // 365 days + '1M': 30 * TIME_FRAMES.DAY_MS, // 30 days + '6M': 180 * TIME_FRAMES.DAY_MS, // 180 days + '1Y': 365 * TIME_FRAMES.DAY_MS, // 365 days } // sets number of aggregations const aggNumbers: Record = { '1M': 4, '6M': 16, '1Y': 32 } @@ -31,7 +32,7 @@ export const _getScrvUsdYield = async (params: { timeOption: TimeOption }) => { const totalAverage = array.reduce((sum, curr) => sum + curr.apyProjected, 0) / array.length // Calculate 7-day moving average - const SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000 + const SEVEN_DAYS_IN_MILLISECONDS = 7 * TIME_FRAMES.DAY_MS const currentTimestamp = item.timestamp.getTime() const sevenDaysAgoTimestamp = currentTimestamp - SEVEN_DAYS_IN_MILLISECONDS diff --git a/packages/curve-ui-kit/src/lib/model/time.ts b/packages/curve-ui-kit/src/lib/model/time.ts index 40c0960f46..8d4048acd8 100644 --- a/packages/curve-ui-kit/src/lib/model/time.ts +++ b/packages/curve-ui-kit/src/lib/model/time.ts @@ -15,6 +15,9 @@ export const REFRESH_INTERVAL = { } as const export const TIME_FRAMES = { + DAY_MS: 24 * 60 * 60 * 1000, WEEK: 7 * 24 * 60 * 60, MONTH: 30 * 24 * 60 * 60, + MONTH_MS: 30 * 24 * 60 * 60 * 1000, + YEAR_MS: 365 * 24 * 60 * 60 * 1000, } as const diff --git a/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx b/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx index d0c946ca28..5d204dc8b7 100644 --- a/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx +++ b/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx @@ -1,10 +1,14 @@ import { useChainId, useConnection, useSwitchChain } from 'wagmi' import Box from '@mui/material/Box' import { isFailure, useCurve, type WagmiChainId } from '@ui-kit/features/connect-wallet' -import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' +import { usePathname } from '@ui-kit/hooks/router' +import { useDismissBanner, useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' +import { getCurrentApp } from '@ui-kit/shared/routes' import { Banner } from '@ui-kit/shared/ui/Banner' +import { Duration } from '@ui-kit/themes/design/0_primitives' import { isCypress, ReleaseChannel } from '@ui-kit/utils' +import { Chain } from '@ui-kit/utils/network' import { PhishingWarningBanner } from '@ui-kit/widgets/Header/PhishingWarningBanner' export type GlobalBannerProps = { @@ -21,11 +25,18 @@ export const GlobalBanner = ({ networkId, chainId }: GlobalBannerProps) => { const { switchChain } = useSwitchChain() const { connectState } = useCurve() const walletChainId = useChainId() + const pathname = usePathname() + const currentApp = getCurrentApp(pathname) + const showSwitchNetworkMessage = isConnected && chainId && walletChainId != chainId const showConnectApiErrorMessage = !showSwitchNetworkMessage && isFailure(connectState) + + const { shouldShowBanner: showAaveBanner, dismissBanner: dismissAaveBanner } = useDismissBanner( + 'aave-v2-frozen-avalanche-polygon', + Duration.Banner.Monthly, + ) return ( - {releaseChannel !== ReleaseChannel.Stable && !isCypress && ( { {t`${releaseChannel} Mode Enabled`} )} + {maintenanceMessage && {maintenanceMessage}} {showSwitchNetworkMessage && ( { {t`There is an issue connecting to the API. Please try to switch your RPC in your wallet settings.`} )} + {showAaveBanner && currentApp === 'dex' && [Chain.Polygon, Chain.Avalanche].includes(chainId) && ( + + {t`Aave V2 Frozen aTokens`} + + )} ) } diff --git a/packages/curve-ui-kit/src/themes/design/0_primitives.ts b/packages/curve-ui-kit/src/themes/design/0_primitives.ts index f227de53b5..c76ccc2074 100644 --- a/packages/curve-ui-kit/src/themes/design/0_primitives.ts +++ b/packages/curve-ui-kit/src/themes/design/0_primitives.ts @@ -1,3 +1,5 @@ +import { TIME_FRAMES } from '@ui-kit/lib/model/time' + export const Grays = { '10': '#fdfcfc', '25': '#fafafa', @@ -141,6 +143,10 @@ export const Duration = { Snackbar: 6000, Tooltip: { Enter: 500, Exit: 500 }, Transition: 256, + Banner: { + Daily: TIME_FRAMES.DAY_MS, + Monthly: TIME_FRAMES.MONTH_MS, + }, } export const TransitionFunction = `ease-out ${Duration.Transition}ms` diff --git a/packages/curve-ui-kit/src/widgets/Header/PhishingWarningBanner.tsx b/packages/curve-ui-kit/src/widgets/Header/PhishingWarningBanner.tsx index d8ffc37c62..6f0e324071 100644 --- a/packages/curve-ui-kit/src/widgets/Header/PhishingWarningBanner.tsx +++ b/packages/curve-ui-kit/src/widgets/Header/PhishingWarningBanner.tsx @@ -1,16 +1,16 @@ import { useDismissBanner } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' import { Banner } from '@ui-kit/shared/ui/Banner' +import { Duration } from '@ui-kit/themes/design/0_primitives' const URL = 'https://www.curve.finance' -const ONE_MONTH_MS = 30 * 24 * 60 * 60 * 1000 // 30 days in milliseconds /** * Displays a banner warning users about phishing risks and encourages them to verify they are on the official Curve domains. * The banner will reappear after one month if dismissed. */ export const PhishingWarningBanner = () => { - const { shouldShowBanner, dismissBanner } = useDismissBanner('phishing-warning-dismissed', ONE_MONTH_MS) + const { shouldShowBanner, dismissBanner } = useDismissBanner('phishing-warning-dismissed', Duration.Banner.Monthly) return ( shouldShowBanner && ( diff --git a/tests/cypress/e2e/all/header.cy.ts b/tests/cypress/e2e/all/header.cy.ts index ddf318d7db..f912b50493 100644 --- a/tests/cypress/e2e/all/header.cy.ts +++ b/tests/cypress/e2e/all/header.cy.ts @@ -8,6 +8,7 @@ import { SCROLL_WIDTH, TABLET_BREAKPOINT, } from '@cy/support/ui' +import { TIME_FRAMES } from '@ui-kit/lib/model/time' const expectedMainNavHeight = 56 const expectedSubNavHeight = 42 // 40 + 2px border @@ -18,8 +19,6 @@ const expectedFooterXMargin = { mobile: 32, tablet: 48, desktop: 48 } const expectedFooterMinWidth = 273 const expectedFooterMaxWidth = 1536 -const DAY_IN_MS = 24 * 60 * 60 * 1000 - describe('Header', () => { let viewport: readonly [number, number] @@ -191,14 +190,14 @@ describe('Header', () => { it('should reappear after one month', () => { // Set dismissal date to 31 days ago (more than one month) - const oneMonthAgo = Date.now() - 31 * DAY_IN_MS + const oneMonthAgo = Date.now() - 31 * TIME_FRAMES.DAY_MS visitWithDismissedBanner(oneMonthAgo) cy.get("[data-testid='phishing-warning-banner']", LOAD_TIMEOUT).should('be.visible') }) it('should remain hidden within one month', () => { // Set dismissal date to 15 days ago (less than one month) - const fifteenDaysAgo = Date.now() - 15 * DAY_IN_MS + const fifteenDaysAgo = Date.now() - 15 * TIME_FRAMES.DAY_MS visitWithDismissedBanner(fifteenDaysAgo) cy.get("[data-testid='phishing-warning-banner']", LOAD_TIMEOUT).should('not.exist') }) diff --git a/tests/cypress/support/generators.ts b/tests/cypress/support/generators.ts index 0ed2ac4342..640a64488e 100644 --- a/tests/cypress/support/generators.ts +++ b/tests/cypress/support/generators.ts @@ -1,5 +1,6 @@ import type { Address } from '@curvefi/prices-api' import { range, recordValues } from '@curvefi/prices-api/objects.util' +import { TIME_FRAMES } from '@ui-kit/lib/model/time' export const MAX_USD_VALUE = 400_000_000 @@ -36,7 +37,7 @@ export const oneTokenType = () => oneOf('collateral', 'borrowed') export type TokenType = ReturnType export const oneDate = ({ - minDate = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000), // 1 year ago + minDate = new Date(Date.now() - TIME_FRAMES.YEAR_MS), // 1 year ago maxDate = new Date(Date.now()), }: { minDate?: Date