-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Fetch pause data and store JSON object in client browser * Refetch only if last fetch was more than 24 hours ago * Read chain + module pause from local storage * Maintenance components rendering based off of fetched pause data * Pause Bridge button based on Maintenance status * Filter quotes based on paused modules * Use user defined styling or defaults * Style Progress Bar * Refactor getSynapsePauseData * Clean * Fix bridge quote filter * Adjust text size for maintenance * Add comments + clean * Update comment * Refresh data every hour * Clean * Add key to warning messages * Fix render issues, start move event countdown component directly to Widget to resolve hooks issue * Resolve hooks render issue with localized component * Progress bar renders when not isabled * Clean and simplify Maintenance components * getMaintenanceData * Organize back into useMaintenance hook * Clean / organize * Use prod urls * Organizational updates * Fetch pause data every render, set fetching status flag * Rm timestamp key --------- Co-authored-by: abtestingalpha <abtestingalpha@gmail.com>
- Loading branch information
1 parent
9b30673
commit 96f3e77
Showing
19 changed files
with
723 additions
and
11 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
45 changes: 45 additions & 0 deletions
45
packages/widget/src/components/Maintenance/EventCountdownProgressBar.tsx
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,45 @@ | ||
import { isNull } from 'lodash' | ||
|
||
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 | ||
} | ||
} |
179 changes: 179 additions & 0 deletions
179
packages/widget/src/components/Maintenance/LinearAnimatedProgressBar.tsx
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,179 @@ | ||
import { memo } from 'react' | ||
import { isNull } from 'lodash' | ||
|
||
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
106
packages/widget/src/components/Maintenance/Maintenance.tsx
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,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, | ||
} | ||
} |
Oops, something went wrong.