From ee14cb82e04ec16d6fccc8cfb7f3194a1b99c140 Mon Sep 17 00:00:00 2001 From: bigboydiamonds <57741810+bigboydiamonds@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:39:52 -0700 Subject: [PATCH] feat(synapse-interface): maintenance aggregator using PAUSED_CHAINS (#2345) * Aggregate maintenance events for banners and warning message * Dynamically render countdown progress bars based on PAUSED_CHAIN * Dynamically rendering banners * Slightly organize * ChainPause type applied to enforce maintenance event structure, pass in component messages as a prop * Working with multiple events * Add dev comments to MaintenanceBanner; refactor * Add dev comments for MaintenanceWarningMessage; refactor * Dev comments * Organize components * isChainIncluded util * Clean * Add ability to specify paused chains by from/to side (#2346) * Allow indefinite maintenance components by setting end date to null * Banners to show indefinitely as well * Add props to disable banner / warning / countdown * Implement disable warning * Implement disable countdown, bridge pause still working * Example * Clean * Update naming on Bridge page * Update comment for isChainIncluded * Create maintenance events reading from pausedChains.json * Remove custom margins to allow Bridge parent gap styling to handle spacing * Require all props to be defined * Add Swap to maintenance warning messages * Update useMaintenanceCountdownProgresses to allow distinction between Swap and Bridge pauses * Move MaintenanceBanners into LandingPageWrapper so banner appears on all pages * Add ability to specify whether to pause bridge / swap with maintenance event in json * Clean * Unused code * Update dev comments * Update pause start/end time name for legibility * Create type guard to check for paused bridge module * usePausedBridgeModules * usePausedBridgeModules to filter out SDK quotes * Initialize paused routes to handle specific route pauses instead of grouping with chain pauses * Update paused route structure * Filter for valid quotes based on paused routes * Create a Set with paused bridge module names to improve time complexity * Allow for all bridge modules to be paused with ALL * Add ability to pause bridge modules for all chains, if chainId is left undefined * Move json files to /v1/ version control folder * Compare quotes against paused bridge modules more cleanly * Paused bridge modules json control working * Fix pausedChains json * Create examples folder for pause jsons * Retrigger build * Fix banner flashing after clearing * Add padding to banner Close button * Update text sizing on progress bar * Update prop naming to prevent confusion on start/end * Clear chain pauses to ready PR * Change json file naming to be more readable * Use inputWarningMessage prop name to indicate warning placement * Pause Doge activity using Maintenance, to replace prior Chain pause mechanism * Doge chain paused chain prop values * Remove paused from/to chainId constants --- .../Events/template/MaintenanceEvent.tsx | 113 ----------- .../Maintenance/LinearAnimatedProgressBar.tsx | 108 ---------- .../components/Maintenance/Maintenance.tsx | 189 ++++++++++++++++++ .../{ => components}/AnnouncementBanner.tsx | 51 +++-- .../EventCountdownProgressBar.tsx | 85 +++++--- .../components/LinearAnimatedProgressBar.tsx | 179 +++++++++++++++++ .../components/MaintenanceBanner.tsx | 47 +++++ .../components/MaintenanceWarningMessage.tsx | 47 +++++ .../useMaintenanceCountdownProgress.tsx | 48 +++++ .../example/EcotoneForkUpgrade.tsx | 8 +- .../BridgeTransactionButton.tsx | 11 - .../hooks/useFromChainListArray.ts | 9 +- .../hooks/useToChainListArray.ts | 9 +- .../SwapTransactionButton.tsx | 11 +- .../synapse-interface/components/Warning.tsx | 2 +- .../layouts/LandingPageWrapper/index.tsx | 2 + .../constants/chains/index.tsx | 3 - packages/synapse-interface/pages/index.tsx | 2 - .../pages/state-managed-bridge/index.tsx | 45 +++-- .../synapse-interface/pages/swap/index.tsx | 17 ++ .../paused-bridge-modules-example.json | 10 + .../v1/examples/paused-chains-example.json | 19 ++ .../pauses/v1/paused-bridge-modules.json | 2 + .../public/pauses/v1/paused-chains.json | 19 ++ .../utils/isChainIncluded.tsx | 11 + .../routeMaker/generateRoutePossibilities.ts | 8 +- .../swapFinder/generateSwapPossibilities.ts | 4 - 27 files changed, 727 insertions(+), 332 deletions(-) delete mode 100644 packages/synapse-interface/components/Maintenance/Events/template/MaintenanceEvent.tsx delete mode 100644 packages/synapse-interface/components/Maintenance/LinearAnimatedProgressBar.tsx create mode 100644 packages/synapse-interface/components/Maintenance/Maintenance.tsx rename packages/synapse-interface/components/Maintenance/{ => components}/AnnouncementBanner.tsx (63%) rename packages/synapse-interface/components/Maintenance/{ => components}/EventCountdownProgressBar.tsx (70%) create mode 100644 packages/synapse-interface/components/Maintenance/components/LinearAnimatedProgressBar.tsx create mode 100644 packages/synapse-interface/components/Maintenance/components/MaintenanceBanner.tsx create mode 100644 packages/synapse-interface/components/Maintenance/components/MaintenanceWarningMessage.tsx create mode 100644 packages/synapse-interface/components/Maintenance/components/useMaintenanceCountdownProgress.tsx rename packages/synapse-interface/components/Maintenance/{Events => }/example/EcotoneForkUpgrade.tsx (94%) create mode 100644 packages/synapse-interface/public/pauses/v1/examples/paused-bridge-modules-example.json create mode 100644 packages/synapse-interface/public/pauses/v1/examples/paused-chains-example.json create mode 100644 packages/synapse-interface/public/pauses/v1/paused-bridge-modules.json create mode 100644 packages/synapse-interface/public/pauses/v1/paused-chains.json create mode 100644 packages/synapse-interface/utils/isChainIncluded.tsx diff --git a/packages/synapse-interface/components/Maintenance/Events/template/MaintenanceEvent.tsx b/packages/synapse-interface/components/Maintenance/Events/template/MaintenanceEvent.tsx deleted file mode 100644 index 01ffbf6dc6..0000000000 --- a/packages/synapse-interface/components/Maintenance/Events/template/MaintenanceEvent.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { useBridgeState } from '@/slices/bridge/hooks' -import { useIntervalTimer } from '@/utils/hooks/useIntervalTimer' -import { OPTIMISM, BASE, BLAST, METIS } from '@/constants/chains/master' -import { - useEventCountdownProgressBar, - getCountdownTimeStatus, -} from '../../EventCountdownProgressBar' -import { AnnouncementBanner } from '../../AnnouncementBanner' -import { WarningMessage } from '../../../Warning' - -/** - * Edit this file for Website Maintenance, components already placed on Bridge page - * - * If require multiple maintenance events, create another file using this file as a template - * and add another instance of components on relevant pages - */ - -/** Banner start time */ -const MAINTENANCE_BANNERS_START = new Date(Date.UTC(2024, 3, 15, 0, 0, 0)) -/** Countdown Progress Bar, Bridge Warning Message + Bridge Pause start time */ -const MAINTENANCE_START_DATE = new Date(Date.UTC(2024, 3, 15, 0, 0, 0)) -/** Ends Banner, Countdown Progress Bar, Bridge Warning Message, Bridge Pause */ -const MAINTENANCE_END_DATE = new Date(Date.UTC(2024, 3, 15, 0, 0, 0)) - - -export const MaintenanceBanner = () => { - const { isComplete } = getCountdownTimeStatus( - MAINTENANCE_BANNERS_START, // Banner will automatically appear after start time - MAINTENANCE_END_DATE // Banner will automatically disappear when end time is reached - ) - - useIntervalTimer(60000, isComplete) - - return ( - -

Bridging on Optimism is temporarily paused.

- - } - startDate={MAINTENANCE_BANNERS_START} - endDate={MAINTENANCE_END_DATE} - /> - ) -} - -export const MaintenanceWarningMessage = () => { - const { fromChainId, toChainId } = useBridgeState() - - const isWarningChain = isChainIncluded( - [fromChainId, toChainId], - [OPTIMISM.id] // Update for Chains to show warning on - ) - - const { isComplete } = getCountdownTimeStatus( - MAINTENANCE_BANNERS_START, // Banner will automatically appear after start time - MAINTENANCE_END_DATE // Banner will automatically disappear when end time is reached - ) - - if (isComplete) return null - - if (isWarningChain) { - return ( - -

Bridging on Optimism is temporarily paused.

- - } - /> - ) - } - - return null -} - -export const useMaintenanceCountdownProgress = () => { - const { fromChainId, toChainId } = useBridgeState() - - const isCurrentChain = isChainIncluded( - [fromChainId, toChainId], - [OPTIMISM.id] // Update for Chains to show maintenance on - ) - - const { - isPending: isMaintenancePending, - EventCountdownProgressBar: MaintenanceCountdownProgressBar, - } = useEventCountdownProgressBar( - 'Bridging on Optimism paused.', - MAINTENANCE_START_DATE, // Countdown Bar will automatically appear after start time - MAINTENANCE_END_DATE // Countdown Bar will automatically disappear when end time is reached - ) - - return { - isMaintenancePending, - isCurrentChainDisabled: isCurrentChain && isMaintenancePending, // Used to pause Bridge - MaintenanceCountdownProgressBar: isCurrentChain - ? MaintenanceCountdownProgressBar - : null, - } -} - -/** - * Checks if any of the chain IDs in `hasChains` are found within the `chainList` array. - * - * @param {number[]} chainList - The array of chain IDs to check against. - * @param {number[]} hasChains - The array of chain IDs to find within `checkChains`. - * @returns {boolean} - True if any chain ID from `hasChains` is found in `checkChains`, otherwise false. - */ -const isChainIncluded = (chainList: number[], hasChains: number[]) => { - return hasChains.some((chainId) => chainList.includes(chainId)) -} diff --git a/packages/synapse-interface/components/Maintenance/LinearAnimatedProgressBar.tsx b/packages/synapse-interface/components/Maintenance/LinearAnimatedProgressBar.tsx deleted file mode 100644 index 955b932afc..0000000000 --- a/packages/synapse-interface/components/Maintenance/LinearAnimatedProgressBar.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { memo } from 'react' -import { getCountdownTimeStatus } from './EventCountdownProgressBar' - -/** - * @param id unique identifier for progress bar instance - * @param startTime start time in unix seconds - * @param endTime end time in unix seconds - */ -export const LinearAnimatedProgressBar = memo( - ({ - id, - startDate, - endDate, - }: { - id: string - startDate: Date - endDate: Date - }) => { - const { - totalTimeInSeconds, - totalTimeElapsedInSeconds, - totalTimeRemainingInSeconds, - isComplete, - } = getCountdownTimeStatus(startDate, endDate) - - const percentElapsed = Math.floor( - (totalTimeElapsedInSeconds / totalTimeInSeconds) * 100 - ) - - let duration = isComplete ? 0.5 : totalTimeRemainingInSeconds - - const synapsePurple = 'hsl(265deg 100% 75%)' - const tailwindGreen400 = 'rgb(74 222 128)' - const height = 3 - const progressId = `progress-${id}` - const maskId = `mask-${id}` - - return ( - - - - - - - - - - - - - - - - - - {isComplete && ( - - )} - - {isComplete && ( - - )} - - ) - } -) diff --git a/packages/synapse-interface/components/Maintenance/Maintenance.tsx b/packages/synapse-interface/components/Maintenance/Maintenance.tsx new file mode 100644 index 0000000000..fbe90417fd --- /dev/null +++ b/packages/synapse-interface/components/Maintenance/Maintenance.tsx @@ -0,0 +1,189 @@ +import { MaintenanceBanner } from './components/MaintenanceBanner' +import { MaintenanceWarningMessage } from './components/MaintenanceWarningMessage' +import { useMaintenanceCountdownProgress } from './components/useMaintenanceCountdownProgress' +import { useBridgeState } from '@/slices/bridge/hooks' +import { useSwapState } from '@/slices/swap/hooks' +import pausedChains from '@/public/pauses/v1/paused-chains.json' +import pausedBridgeModules from '@/public/pauses/v1/paused-bridge-modules.json' + +/** Pause Chain Activity */ +interface ChainPause { + id: string + pausedFromChains: number[] + pausedToChains: number[] + pauseBridge: boolean + pauseSwap: boolean + startTimePauseChain: Date + endTimePauseChain: Date | null // If null, pause indefinitely + startTimeBanner: Date + endTimeBanner: Date | null // If null, pause indefinitely + inputWarningMessage: JSX.Element + bannerMessage: JSX.Element + progressBarMessage: JSX.Element + disableBanner: boolean + disableWarning: boolean + disableCountdown: boolean +} + +const PAUSED_CHAINS: ChainPause[] = pausedChains.map((pause) => { + return { + ...pause, + startTimePauseChain: new Date(pause.startTimePauseChain), + endTimePauseChain: pause.endTimePauseChain + ? new Date(pause.endTimePauseChain) + : null, + startTimeBanner: new Date(pause.startTimeBanner), + endTimeBanner: pause.endTimeBanner ? new Date(pause.endTimeBanner) : null, + inputWarningMessage:

{pause.inputWarningMessage}

, + bannerMessage:

{pause.bannerMessage}

, + progressBarMessage:

{pause.progressBarMessage}

, + } +}) + +export const MaintenanceBanners = () => { + return ( + <> + {PAUSED_CHAINS.map((event) => { + return ( + + ) + })} + + ) +} + +export const MaintenanceWarningMessages = ({ + type, +}: { + type: 'Bridge' | 'Swap' +}) => { + const { fromChainId: bridgeFromChainId, toChainId: bridgeToChainId } = + useBridgeState() + const { swapChainId } = useSwapState() + + if (type === 'Bridge') { + return ( + <> + {PAUSED_CHAINS.map((event) => { + return ( + + ) + })} + + ) + } else if (type === 'Swap') { + return ( + <> + {PAUSED_CHAINS.map((event) => { + return ( + + ) + })} + + ) + } else { + return null + } +} + +/** + * Hook that maps through PAUSED_CHAINS to apply the single event countdown progress logic to each. + * @returns A list of objects containing maintenance status and components for each paused chain. + */ +export const useMaintenanceCountdownProgresses = ({ + type, +}: { + type: 'Bridge' | 'Swap' +}) => { + const { fromChainId: bridgeFromChainId, toChainId: bridgeToChainId } = + useBridgeState() + const { swapChainId } = useSwapState() + + if (type === 'Bridge') { + return PAUSED_CHAINS.map((event) => { + return useMaintenanceCountdownProgress({ + fromChainId: bridgeFromChainId, + toChainId: bridgeToChainId, + startDate: event.startTimePauseChain, + endDate: event.endTimePauseChain, + pausedFromChains: event.pausedFromChains, + pausedToChains: event.pausedToChains, + progressBarMessage: event.progressBarMessage, + disabled: event.disableCountdown || !event.pauseBridge, + }) + }) + } else if (type === 'Swap') { + return PAUSED_CHAINS.map((event) => { + return useMaintenanceCountdownProgress({ + fromChainId: swapChainId, + toChainId: null, + startDate: event.startTimePauseChain, + endDate: event.endTimePauseChain, + pausedFromChains: event.pausedFromChains, + pausedToChains: event.pausedToChains, + progressBarMessage: event.progressBarMessage, + disabled: event.disableCountdown || !event.pauseSwap, + }) + }) + } +} + +/** Pause Bridge Modules */ +interface BridgeModulePause { + chainId?: number // Will pause for all chains if undefined + bridgeModuleName: 'SynapseBridge' | 'SynapseRFQ' | 'SynapseCCTP' | 'ALL' +} + +function isValidBridgeModule( + module: any +): module is 'SynapseBridge' | 'SynapseRFQ' | 'SynapseCCTP' | 'ALL' { + return ['SynapseBridge', 'SynapseRFQ', 'SynapseCCTP', 'ALL'].includes(module) +} + +export function getBridgeModuleNames(module) { + if (module.bridgeModuleName === 'ALL') { + return ['SynapseRFQ', 'SynapseCCTP', 'SynapseBridge'] + } + return [module.bridgeModuleName] +} + +export const PAUSED_MODULES: BridgeModulePause[] = pausedBridgeModules.map( + (route) => { + if (!isValidBridgeModule(route.bridgeModuleName)) { + throw new Error(`Invalid module type: ${route.bridgeModuleName}`) + } + + return { + ...route, + bridgeModuleName: route.bridgeModuleName as + | 'SynapseBridge' + | 'SynapseRFQ' + | 'SynapseCCTP' + | 'ALL', + } + } +) diff --git a/packages/synapse-interface/components/Maintenance/AnnouncementBanner.tsx b/packages/synapse-interface/components/Maintenance/components/AnnouncementBanner.tsx similarity index 63% rename from packages/synapse-interface/components/Maintenance/AnnouncementBanner.tsx rename to packages/synapse-interface/components/Maintenance/components/AnnouncementBanner.tsx index b9dcb30677..5433d115d1 100644 --- a/packages/synapse-interface/components/Maintenance/AnnouncementBanner.tsx +++ b/packages/synapse-interface/components/Maintenance/components/AnnouncementBanner.tsx @@ -1,14 +1,16 @@ import { useState, useEffect } from 'react' import { getCountdownTimeStatus } from './EventCountdownProgressBar' +import { isNull } from 'lodash' /** - * Reusable automated Announcement Banner with custom Start/End Time - * Will automatically appear after Start time - * Will automatically disappear after End time - * @param bannerId: store in $MMDDYYYY-$BANNER_NAME format (e.g 03132024-ETH-DENCUN) - * @param bannerContents: contents to display in banner - * @param startDate: start date to show banner - * @param endDate: end date to remove banner + * Generic Message Banner that appears between defined start and end time. + * If end date is null, banner will appear indefinitely until removed. + * + * @param bannerId Unique ID to prevent conflicts with other banner instances. + * Assign ID $MMDDYYYY-$BANNER_NAME format (e.g 03132024-ETH-DENCUN) + * @param bannerContents Message to display + * @param startDate Start time to display banner + * @param endDate End time to remove banner */ export const AnnouncementBanner = ({ bannerId, @@ -19,11 +21,12 @@ export const AnnouncementBanner = ({ bannerId: string bannerContents: any startDate: Date - endDate: Date + endDate: Date | null }) => { const { isStarted, isComplete } = getCountdownTimeStatus(startDate, endDate) + const [hasMounted, setHasMounted] = useState(false) - const [showBanner, setShowBanner] = useState(true) + const [showBanner, setShowBanner] = useState(false) useEffect(() => { setHasMounted(true) @@ -54,26 +57,38 @@ export const AnnouncementBanner = ({ if (!showBanner || !hasMounted || !isStarted || isComplete) return null return ( -
+