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

feat(widget): maintenance #2616

Merged
merged 28 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7a1bb74
Fetch pause data and store JSON object in client browser
bigboydiamonds May 11, 2024
6f553fc
Refetch only if last fetch was more than 24 hours ago
bigboydiamonds May 11, 2024
f005f88
Read chain + module pause from local storage
bigboydiamonds May 15, 2024
d544214
Maintenance components rendering based off of fetched pause data
bigboydiamonds May 15, 2024
2a76885
Pause Bridge button based on Maintenance status
bigboydiamonds May 15, 2024
876c97c
Filter quotes based on paused modules
bigboydiamonds May 15, 2024
02f2ed9
Use user defined styling or defaults
bigboydiamonds May 15, 2024
64b086d
Style Progress Bar
bigboydiamonds May 15, 2024
f0d87d1
Refactor getSynapsePauseData
bigboydiamonds May 15, 2024
6c10f92
Clean
bigboydiamonds May 15, 2024
d85b82b
Fix bridge quote filter
bigboydiamonds May 15, 2024
f2700ff
Adjust text size for maintenance
bigboydiamonds May 15, 2024
b0eaafe
Add comments + clean
bigboydiamonds May 16, 2024
8a6c737
Update comment
bigboydiamonds May 16, 2024
d658ebd
Refresh data every hour
bigboydiamonds May 16, 2024
7ce4e22
Clean
bigboydiamonds May 16, 2024
816dde2
Add key to warning messages
bigboydiamonds May 16, 2024
81a66fe
Fix render issues, start move event countdown component directly to W…
bigboydiamonds May 16, 2024
dcc1b0f
Resolve hooks render issue with localized component
bigboydiamonds May 16, 2024
b5048c1
Progress bar renders when not isabled
bigboydiamonds May 16, 2024
c7e790b
Clean and simplify Maintenance components
bigboydiamonds May 16, 2024
0a297b4
getMaintenanceData
bigboydiamonds May 16, 2024
c1b1754
Organize back into useMaintenance hook
bigboydiamonds May 16, 2024
5369601
Clean / organize
bigboydiamonds May 16, 2024
97021c1
Use prod urls
bigboydiamonds May 16, 2024
a3e4bcb
Organizational updates
abtestingalpha May 17, 2024
fc8c79c
Fetch pause data every render, set fetching status flag
bigboydiamonds May 17, 2024
f785d10
Rm timestamp key
bigboydiamonds May 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/widget/src/components/BridgeButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface BridgeButtonProps {
handleBridge: () => any
isApprovalPending: boolean
isBridgePending: boolean
isBridgePaused: boolean
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved
}

export const BridgeButton = ({
Expand All @@ -23,6 +24,7 @@ export const BridgeButton = ({
handleBridge,
isApprovalPending,
isBridgePending,
isBridgePaused,
}: BridgeButtonProps) => {
const web3Context = useContext(Web3Context)

Expand Down Expand Up @@ -52,6 +54,14 @@ export const BridgeButton = ({

const tooltipPositionStyle = '-top-8'

if (isBridgePaused) {
return (
<button className={buttonClassName} style={buttonStyle} disabled>
Bridge paused
</button>
)
}

if (!provider || !connectedAddress) {
return (
<Tooltip hoverText="Connect Wallet" positionStyles={tooltipPositionStyle}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { isNull } from 'lodash'
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved

import { LinearAnimatedProgressBar } from './LinearAnimatedProgressBar'

export const EventCountdownProgressBar = ({
eventLabel,
startDate,
endDate,
timeRemaining,
status,
}: {
eventLabel: string
startDate: Date
endDate: Date | null
timeRemaining: string
status: 'idle' | 'pending' | 'complete'
}) => {
const isIndefinite = isNull(endDate)

if (status === 'pending') {
return (
<div
className={`
flex flex-col bg-[--synapse-surface]
border border-[--synapse-border] rounded-md
text-[--synapse-text] text-xs md:text-base
`}
>
<div className="flex justify-between px-3 py-2">
<div>{eventLabel}</div>
{isIndefinite ? null : <div>{timeRemaining} remaining</div>}
</div>
<div className="flex px-1">
<LinearAnimatedProgressBar
id="event-countdown-progress-bar"
startDate={startDate}
endDate={endDate}
/>
</div>
</div>
)
} else {
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { memo } from 'react'
import { isNull } from 'lodash'
bigboydiamonds marked this conversation as resolved.
Show resolved Hide resolved

import { getCountdownTimeStatus } from '@/utils/getCountdownTimeStatus'

/**
* Constructs animated progress bar visualizing time progress of an event.
* If end date is not provided, progress bar animates indefinitely.
*
* @param id - A unique ID to distinguish instances.
* @param startDate - The start date of the tracked event.
* @param endDate - The end date of the tracked event.
*/
export const LinearAnimatedProgressBar = memo(
({
id,
startDate,
endDate,
}: {
id: string
startDate: Date
endDate: Date | null
}) => {
const height = 3
const maskId = `mask-${id}`
const progressId = `progress-${id}`
const greenColor = 'rgb(74 222 128)'
const purpleColor = 'hsl(265deg 100% 75%)'

let duration: string | number

const isIndefinite = isNull(endDate)

if (isIndefinite) {
duration = 'infinite'
return (
<svg
id="linear-animated-progress-bar"
key={Date.now()}
width="100%"
height={height}
xmlns="http://www.w3.org/2000/svg"
className="rounded-sm"
>
<defs>
<linearGradient
id={progressId}
spreadMethod="reflect"
x1="0"
x2="1"
>
<stop stopColor={purpleColor} />
<stop stopColor={purpleColor} offset=".25" />
<stop stopColor={purpleColor} stopOpacity=".67" offset=".75" />
<stop stopColor={purpleColor} stopOpacity=".67" offset="1" />
<animate
attributeName="x1"
values="0%; -6%"
dur=".67s"
repeatCount="indefinite"
/>
<animate
attributeName="x2"
values="3%; -3%"
dur=".67s"
repeatCount="indefinite"
/>
</linearGradient>
<clipPath id={maskId}>
<rect height="100%">
<animate
attributeName="width"
values={`100%; 100%`}
dur="infinite"
fill="freeze"
calcMode={'linear'}
/>
</rect>
</clipPath>
</defs>
<rect
width="100%"
height={height}
fill={`url(#${progressId})`}
clipPath={`url(#${maskId})`}
></rect>
</svg>
)
} else {
const {
totalTimeInSeconds,
totalTimeElapsedInSeconds,
totalTimeRemainingInSeconds,
isComplete,
} = getCountdownTimeStatus(startDate, endDate)

const percentElapsed = Math.floor(
(totalTimeElapsedInSeconds / totalTimeInSeconds) * 100
)

duration = isComplete ? 0.5 : totalTimeRemainingInSeconds

return (
<svg
id="linear-animated-progress-bar"
key={Date.now()}
width="100%"
height={height}
xmlns="http://www.w3.org/2000/svg"
className="rounded-sm"
>
<defs>
<linearGradient
id={progressId}
spreadMethod="reflect"
x1="0"
x2="1"
>
<stop stopColor={purpleColor} />
<stop stopColor={purpleColor} offset=".25" />
<stop stopColor={purpleColor} stopOpacity=".67" offset=".75" />
<stop stopColor={purpleColor} stopOpacity=".67" offset="1" />
<animate
attributeName="x1"
values="0%; -6%"
dur=".67s"
repeatCount="indefinite"
/>
<animate
attributeName="x2"
values="3%; -3%"
dur=".67s"
repeatCount="indefinite"
/>
</linearGradient>
<clipPath id={maskId}>
<rect height="100%">
<animate
attributeName="width"
values={`${isComplete ? 100 : percentElapsed}%; 100%`}
dur={totalTimeInSeconds}
fill="freeze"
calcMode={'linear'}
/>
</rect>
</clipPath>
</defs>
<rect
width="100%"
height={height}
fill={`url(#${progressId})`}
clipPath={`url(#${maskId})`}
>
{isComplete && (
<animate
attributeName="fill"
values={`${purpleColor}; hsl(185deg 100% 40%); ${greenColor}`}
keyTimes="0; .5; 1"
dur={duration}
fill="freeze"
/>
)}
</rect>
{isComplete && (
<animate
attributeName="height"
values={`${height}; ${height}; 0`}
keyTimes="0; .5; 1"
calcMode="spline"
keySplines="0 0 1 1; .8 0 .2 1"
dur={duration * 1.5}
fill="freeze"
/>
)}
</svg>
)
}
}
)
106 changes: 106 additions & 0 deletions packages/widget/src/components/Maintenance/Maintenance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useBridgeState } from '@/state/slices/bridge/hooks'
import { isChainIncluded } from '@/utils/isChainIncluded'
import { useEventCountdownProgressBar } from '@/hooks/useEventCountdownProgressBar'
import { MaintenanceWarningMessage } from './MaintenanceWarningMessage'
import { getSynapsePauseData } from '@/utils/getSynapsePauseData'
import { isValidBridgeModule } from '@/utils/isValidBridgeModule'

interface ChainPause {
id: string
pausedFromChains: number[]
pausedToChains: number[]
pauseBridge: boolean
startTimePauseChain: Date
endTimePauseChain: Date | null
inputWarningMessage: string
progressBarMessage: string
disableWarning: boolean
disableCountdown: boolean
}

interface BridgeModulePause {
chainId: number | undefined
bridgeModuleName: 'SynapseBridge' | 'SynapseRFQ' | 'SynapseCCTP' | 'ALL'
}

export const getMaintenanceData = () => {
const { pausedChainsData, pausedModulesData } = getSynapsePauseData()

const pausedChainsList: ChainPause[] = pausedChainsData
? pausedChainsData?.map((pause: ChainPause) => {
return {
...pause,
startTimePauseChain: new Date(pause.startTimePauseChain),
endTimePauseChain: pause.endTimePauseChain
? new Date(pause.endTimePauseChain)
: null,
inputWarningMessage: pause.inputWarningMessage,
progressBarMessage: pause.progressBarMessage,
}
})
: []

const pausedModulesList: BridgeModulePause[] = pausedModulesData
? pausedModulesData?.map((route: BridgeModulePause) => {
if (!isValidBridgeModule(route.bridgeModuleName)) {
throw new Error(`Invalid module type: ${route.bridgeModuleName}`)
}

return {
...route,
bridgeModuleName: route.bridgeModuleName as
| 'SynapseBridge'
| 'SynapseRFQ'
| 'SynapseCCTP'
| 'ALL',
}
})
: []

return {
pausedChainsList,
pausedModulesList,
}
}

export const useMaintenance = () => {
const { originChainId, destinationChainId } = useBridgeState()
const { pausedChainsList, pausedModulesList } = getMaintenanceData()

const activePause = pausedChainsList.find(
(pauseData) =>
isChainIncluded(pauseData?.pausedFromChains, [originChainId]) ||
isChainIncluded(pauseData?.pausedToChains, [destinationChainId])
)

const { isPending: isBridgePaused, EventCountdownProgressBar } =
useEventCountdownProgressBar(
activePause?.progressBarMessage,
activePause?.startTimePauseChain,
activePause?.endTimePauseChain,
activePause?.disableCountdown
)

const BridgeMaintenanceProgressBar = () => EventCountdownProgressBar

const BridgeMaintenanceWarningMessage = () => (
<MaintenanceWarningMessage
originChainId={originChainId}
destinationChainId={destinationChainId}
startDate={activePause?.startTimePauseChain}
endDate={activePause?.endTimePauseChain}
pausedOriginChains={activePause?.pausedFromChains}
pausedDestinationChains={activePause?.pausedToChains}
warningMessage={activePause?.inputWarningMessage}
disabled={activePause?.disableWarning}
/>
)

return {
isBridgePaused,
pausedChainsList,
pausedModulesList,
BridgeMaintenanceProgressBar,
BridgeMaintenanceWarningMessage,
}
}
Loading
Loading