chore: De-duplicate images, components, and most utils with widgets lib (#143)
Remove more dead and duplicated code, favoring widgets lib versionsmain
@ -1,81 +0,0 @@ |
|||||||
.spinner { |
|
||||||
color: official; |
|
||||||
display: inline-block; |
|
||||||
position: relative; |
|
||||||
width: 80px; |
|
||||||
height: 80px; |
|
||||||
} |
|
||||||
.spinner div { |
|
||||||
transform-origin: 40px 40px; |
|
||||||
animation: spinner 1.2s linear infinite; |
|
||||||
} |
|
||||||
.spinner div:after { |
|
||||||
content: ' '; |
|
||||||
display: block; |
|
||||||
position: absolute; |
|
||||||
top: 3px; |
|
||||||
left: 37px; |
|
||||||
width: 6px; |
|
||||||
height: 18px; |
|
||||||
border-radius: 20%; |
|
||||||
background: #010101; |
|
||||||
} |
|
||||||
.spinner.white div:after { |
|
||||||
background: #ffffff; |
|
||||||
} |
|
||||||
.spinner div:nth-child(1) { |
|
||||||
transform: rotate(0deg); |
|
||||||
animation-delay: -1.1s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(2) { |
|
||||||
transform: rotate(30deg); |
|
||||||
animation-delay: -1s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(3) { |
|
||||||
transform: rotate(60deg); |
|
||||||
animation-delay: -0.9s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(4) { |
|
||||||
transform: rotate(90deg); |
|
||||||
animation-delay: -0.8s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(5) { |
|
||||||
transform: rotate(120deg); |
|
||||||
animation-delay: -0.7s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(6) { |
|
||||||
transform: rotate(150deg); |
|
||||||
animation-delay: -0.6s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(7) { |
|
||||||
transform: rotate(180deg); |
|
||||||
animation-delay: -0.5s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(8) { |
|
||||||
transform: rotate(210deg); |
|
||||||
animation-delay: -0.4s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(9) { |
|
||||||
transform: rotate(240deg); |
|
||||||
animation-delay: -0.3s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(10) { |
|
||||||
transform: rotate(270deg); |
|
||||||
animation-delay: -0.2s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(11) { |
|
||||||
transform: rotate(300deg); |
|
||||||
animation-delay: -0.1s; |
|
||||||
} |
|
||||||
.spinner div:nth-child(12) { |
|
||||||
transform: rotate(330deg); |
|
||||||
animation-delay: 0s; |
|
||||||
} |
|
||||||
@keyframes spinner { |
|
||||||
0% { |
|
||||||
opacity: 1; |
|
||||||
} |
|
||||||
100% { |
|
||||||
opacity: 0; |
|
||||||
} |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
import { memo } from 'react'; |
|
||||||
|
|
||||||
import styles from './Spinner.module.css'; |
|
||||||
|
|
||||||
// From https://loading.io/css/
|
|
||||||
function _Spinner({ white, classes }: { white?: boolean; classes?: string }) { |
|
||||||
return ( |
|
||||||
<div className={`${styles.spinner} ${white && styles.white} ${classes || ''}`}> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
<div></div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
export const Spinner = memo(_Spinner); |
|
@ -1,42 +0,0 @@ |
|||||||
import Image from 'next/image'; |
|
||||||
import { useState } from 'react'; |
|
||||||
|
|
||||||
import CheckmarkIcon from '../../images/icons/checkmark.svg'; |
|
||||||
import CopyIcon from '../../images/icons/copy-stack.svg'; |
|
||||||
import { tryClipboardSet } from '../../utils/clipboard'; |
|
||||||
|
|
||||||
interface Props { |
|
||||||
width: number; |
|
||||||
height: number; |
|
||||||
copyValue: string; |
|
||||||
classes: string; |
|
||||||
} |
|
||||||
|
|
||||||
export function CopyButton({ width, height, copyValue, classes }: Props) { |
|
||||||
const [showCheckmark, setShowCheckmark] = useState(false); |
|
||||||
|
|
||||||
const onClick = async () => { |
|
||||||
const result = await tryClipboardSet(copyValue); |
|
||||||
if (result) { |
|
||||||
setShowCheckmark(true); |
|
||||||
setTimeout(() => setShowCheckmark(false), 2000); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<button |
|
||||||
onClick={onClick} |
|
||||||
type="button" |
|
||||||
title="Copy" |
|
||||||
className={`flex items-center justify-center transition-all ${ |
|
||||||
showCheckmark ? 'opacity-100' : 'opacity-50' |
|
||||||
} hover:opacity-70 active:opacity-90 ${classes}`}
|
|
||||||
> |
|
||||||
{showCheckmark ? ( |
|
||||||
<Image src={CheckmarkIcon} width={width} height={height} alt="" /> |
|
||||||
) : ( |
|
||||||
<Image src={CopyIcon} width={width} height={height} alt="" /> |
|
||||||
)} |
|
||||||
</button> |
|
||||||
); |
|
||||||
} |
|
@ -1,56 +0,0 @@ |
|||||||
/* The switch - the box around the slider */ |
|
||||||
.switch { |
|
||||||
position: relative; |
|
||||||
display: inline-block; |
|
||||||
width: 32px; |
|
||||||
height: 18px; |
|
||||||
} |
|
||||||
|
|
||||||
/* Hide default HTML checkbox */ |
|
||||||
.switch input { |
|
||||||
opacity: 0; |
|
||||||
width: 0; |
|
||||||
height: 0; |
|
||||||
} |
|
||||||
|
|
||||||
/* The slider */ |
|
||||||
.slider { |
|
||||||
position: absolute; |
|
||||||
cursor: pointer; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
right: 0; |
|
||||||
bottom: 0; |
|
||||||
background-color: #ccc; |
|
||||||
-webkit-transition: 0.4s; |
|
||||||
transition: 0.4s; |
|
||||||
} |
|
||||||
|
|
||||||
.slider:before { |
|
||||||
position: absolute; |
|
||||||
content: ''; |
|
||||||
height: 14px; |
|
||||||
width: 14px; |
|
||||||
left: 2px; |
|
||||||
bottom: 2px; |
|
||||||
background-color: white; |
|
||||||
-webkit-transition: 0.4s; |
|
||||||
transition: 0.4s; |
|
||||||
} |
|
||||||
|
|
||||||
input:checked + .slider { |
|
||||||
background-color: #45cd85; |
|
||||||
} |
|
||||||
|
|
||||||
input:checked + .slider:before { |
|
||||||
transform: translateX(13px); |
|
||||||
} |
|
||||||
|
|
||||||
/* Rounded sliders */ |
|
||||||
.slider.round { |
|
||||||
border-radius: 24px; |
|
||||||
} |
|
||||||
|
|
||||||
.slider.round:before { |
|
||||||
border-radius: 50%; |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
import { ChangeEvent } from 'react'; |
|
||||||
import styles from 'src/components/buttons/SwitchButton.module.css'; |
|
||||||
|
|
||||||
interface Props { |
|
||||||
checked: boolean; |
|
||||||
onChange: (checked: boolean) => void; |
|
||||||
} |
|
||||||
|
|
||||||
export function SwitchButton({ checked, onChange }: Props) { |
|
||||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { |
|
||||||
const target = event.target; |
|
||||||
onChange(target.checked); |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<label className={styles.switch}> |
|
||||||
<input type="checkbox" checked={checked} onChange={handleChange} /> |
|
||||||
<span className={`${styles.slider} ${styles.round}`}></span> |
|
||||||
</label> |
|
||||||
); |
|
||||||
} |
|
@ -1,52 +1,15 @@ |
|||||||
import Image from 'next/image'; |
import { ErrorBoundary as ErrorBoundaryInner } from '@hyperlane-xyz/widgets'; |
||||||
import { Component } from 'react'; |
import { PropsWithChildren } from 'react'; |
||||||
|
|
||||||
import { links } from '../../consts/links'; |
import { links } from '../../consts/links'; |
||||||
import ErrorIcon from '../../images/icons/error-circle.svg'; |
|
||||||
import { logger } from '../../utils/logger'; |
|
||||||
|
|
||||||
interface ErrorBoundaryState { |
export function ErrorBoundary({ children }: PropsWithChildren<unknown>) { |
||||||
error: any; |
return <ErrorBoundaryInner supportLink={<SupportLink />}>{children}</ErrorBoundaryInner>; |
||||||
errorInfo: any; |
|
||||||
} |
} |
||||||
|
|
||||||
export class ErrorBoundary extends Component<any, ErrorBoundaryState> { |
function SupportLink() { |
||||||
constructor(props: any) { |
return ( |
||||||
super(props); |
<a href={links.discord} target="_blank" rel="noopener noreferrer" className="mt-5 text-sm"> |
||||||
this.state = { error: null, errorInfo: null }; |
For support, join the <span className="underline underline-offset-2">Hyperlane Discord</span>{' '} |
||||||
} |
</a> |
||||||
|
); |
||||||
componentDidCatch(error: any, errorInfo: any) { |
|
||||||
this.setState({ |
|
||||||
error, |
|
||||||
errorInfo, |
|
||||||
}); |
|
||||||
logger.error('Error caught by error boundary', error, errorInfo); |
|
||||||
} |
|
||||||
|
|
||||||
render() { |
|
||||||
const errorInfo = this.state.error || this.state.errorInfo; |
|
||||||
if (errorInfo) { |
|
||||||
const details = errorInfo.message || JSON.stringify(errorInfo); |
|
||||||
return ( |
|
||||||
<div className="flex h-screen w-screen items-center justify-center bg-gray-50"> |
|
||||||
<div className="flex flex-col items-center"> |
|
||||||
<Image src={ErrorIcon} width={80} height={80} alt="" /> |
|
||||||
<h1 className="mt-5 text-lg">Fatal Error Occurred</h1> |
|
||||||
<div className="mt-5 text-sm">{details}</div> |
|
||||||
<a |
|
||||||
href={links.discord} |
|
||||||
target="_blank" |
|
||||||
rel="noopener noreferrer" |
|
||||||
className="mt-5 text-sm" |
|
||||||
> |
|
||||||
For support, join the{' '} |
|
||||||
<span className="underline underline-offset-2">Hyperlane Discord</span>{' '} |
|
||||||
</a> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
return this.props.children; |
|
||||||
} |
|
||||||
} |
} |
||||||
|
@ -1,8 +0,0 @@ |
|||||||
interface Props { |
|
||||||
classes?: string; |
|
||||||
} |
|
||||||
|
|
||||||
export function HrDivider(props: Props) { |
|
||||||
const { classes } = props; |
|
||||||
return <hr className={`h-px w-full border-none bg-gray-300 ${classes}`} />; |
|
||||||
} |
|
@ -1,4 +1,2 @@ |
|||||||
export const MIN_ROUNDED_VALUE = 0.00001; |
|
||||||
export const DISPLAY_DECIMALS = 5; |
|
||||||
export const DELIVERY_LOG_CHECK_BLOCK_RANGE = 1_000; |
export const DELIVERY_LOG_CHECK_BLOCK_RANGE = 1_000; |
||||||
export const PI_MESSAGE_LOG_CHECK_BLOCK_RANGE = 5_000; |
export const PI_MESSAGE_LOG_CHECK_BLOCK_RANGE = 5_000; |
||||||
|
Before Width: | Height: | Size: 275 B |
Before Width: | Height: | Size: 279 B |
Before Width: | Height: | Size: 258 B |
Before Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 248 B |
Before Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 490 B |
Before Width: | Height: | Size: 710 B |
Before Width: | Height: | Size: 506 B |
Before Width: | Height: | Size: 400 B |
Before Width: | Height: | Size: 372 B |
Before Width: | Height: | Size: 316 B |
Before Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 716 B |
Before Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 347 B |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 640 B |
Before Width: | Height: | Size: 570 B |
@ -1,9 +0,0 @@ |
|||||||
import BigNumber from 'bignumber.js'; |
|
||||||
|
|
||||||
export function BigNumberMin(bn1: BigNumber, bn2: BigNumber) { |
|
||||||
return bn1.gte(bn2) ? bn2 : bn1; |
|
||||||
} |
|
||||||
|
|
||||||
export function BigNumberMax(bn1: BigNumber, bn2: BigNumber) { |
|
||||||
return bn1.lte(bn2) ? bn2 : bn1; |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
import { logger } from './logger'; |
|
||||||
|
|
||||||
export function isClipboardReadSupported() { |
|
||||||
return !!navigator?.clipboard?.readText; |
|
||||||
} |
|
||||||
|
|
||||||
export async function tryClipboardSet(value: string) { |
|
||||||
try { |
|
||||||
await navigator.clipboard.writeText(value); |
|
||||||
return true; |
|
||||||
} catch (error) { |
|
||||||
logger.error('Failed to set clipboard', error); |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
export async function tryClipboardGet() { |
|
||||||
try { |
|
||||||
// Note: doesn't work in firefox, which only allows extensions to read clipboard
|
|
||||||
return await navigator.clipboard.readText(); |
|
||||||
} catch (error) { |
|
||||||
logger.error('Failed to read from clipboard', error); |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
@ -1,18 +0,0 @@ |
|||||||
import { useEffect, useState } from 'react'; |
|
||||||
|
|
||||||
// Based on https://usehooks.com/useDebounce
|
|
||||||
export default function useDebounce<T>(value: T, delayMs = 500): T { |
|
||||||
const [debouncedValue, setDebouncedValue] = useState<T>(value); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
const handler = setTimeout(() => { |
|
||||||
setDebouncedValue(value); |
|
||||||
}, delayMs); |
|
||||||
|
|
||||||
return () => { |
|
||||||
clearTimeout(handler); |
|
||||||
}; |
|
||||||
}, [value, delayMs]); |
|
||||||
|
|
||||||
return debouncedValue; |
|
||||||
} |
|
@ -1,10 +0,0 @@ |
|||||||
import { useEffect, useState } from 'react'; |
|
||||||
|
|
||||||
// Is the component server-side rendering or not
|
|
||||||
export function useIsSsr() { |
|
||||||
const [isSsr, setIsSsr] = useState(true); |
|
||||||
useEffect(() => { |
|
||||||
setIsSsr(false); |
|
||||||
}, []); |
|
||||||
return isSsr; |
|
||||||
} |
|
@ -1,41 +0,0 @@ |
|||||||
import { useCallback, useEffect, useRef } from 'react'; |
|
||||||
|
|
||||||
// https://medium.com/javascript-in-plain-english/usetimeout-react-hook-3cc58b94af1f
|
|
||||||
export const useTimeout = ( |
|
||||||
callback: () => void, |
|
||||||
delay = 0, // in ms (default: immediately put into JS Event Queue)
|
|
||||||
): (() => void) => { |
|
||||||
const timeoutIdRef = useRef<NodeJS.Timeout>(); |
|
||||||
|
|
||||||
const cancel = useCallback(() => { |
|
||||||
const timeoutId = timeoutIdRef.current; |
|
||||||
if (timeoutId) { |
|
||||||
timeoutIdRef.current = undefined; |
|
||||||
clearTimeout(timeoutId); |
|
||||||
} |
|
||||||
}, [timeoutIdRef]); |
|
||||||
|
|
||||||
useEffect(() => { |
|
||||||
if (delay >= 0) { |
|
||||||
timeoutIdRef.current = setTimeout(callback, delay); |
|
||||||
} |
|
||||||
return cancel; |
|
||||||
}, [callback, delay, cancel]); |
|
||||||
|
|
||||||
return cancel; |
|
||||||
}; |
|
||||||
|
|
||||||
export async function fetchWithTimeout( |
|
||||||
resource: RequestInfo | URL, |
|
||||||
options?: RequestInit, |
|
||||||
timeout = 10000, |
|
||||||
) { |
|
||||||
const controller = new AbortController(); |
|
||||||
const id = setTimeout(() => controller.abort(), timeout); |
|
||||||
const response = await fetch(resource, { |
|
||||||
...options, |
|
||||||
signal: controller.signal, |
|
||||||
}); |
|
||||||
clearTimeout(id); |
|
||||||
return response; |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
export function isValidHttpUrl(string) { |
|
||||||
let url; |
|
||||||
try { |
|
||||||
url = new URL(string); |
|
||||||
} catch (_) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
return url.protocol === 'http:' || url.protocol === 'https:'; |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
import { useEffect, useLayoutEffect, useRef } from 'react'; |
|
||||||
|
|
||||||
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; |
|
||||||
|
|
||||||
// https://usehooks-typescript.com/react-hook/use-interval
|
|
||||||
export function useInterval(callback: () => void, delay: number | null) { |
|
||||||
const savedCallback = useRef(callback); |
|
||||||
|
|
||||||
// Remember the latest callback if it changes.
|
|
||||||
useIsomorphicLayoutEffect(() => { |
|
||||||
savedCallback.current = callback; |
|
||||||
}, [callback]); |
|
||||||
|
|
||||||
// Set up the interval.
|
|
||||||
useEffect(() => { |
|
||||||
// Don't schedule if no delay is specified.
|
|
||||||
// Note: 0 is a valid value for delay.
|
|
||||||
if (!delay && delay !== 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
const id = setInterval(() => savedCallback.current(), delay); |
|
||||||
|
|
||||||
return () => clearInterval(id); |
|
||||||
}, [delay]); |
|
||||||
} |
|