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 { Component } from 'react'; |
||||
|
||||
import { ErrorBoundary as ErrorBoundaryInner } from '@hyperlane-xyz/widgets'; |
||||
import { PropsWithChildren } from 'react'; |
||||
import { links } from '../../consts/links'; |
||||
import ErrorIcon from '../../images/icons/error-circle.svg'; |
||||
import { logger } from '../../utils/logger'; |
||||
|
||||
interface ErrorBoundaryState { |
||||
error: any; |
||||
errorInfo: any; |
||||
export function ErrorBoundary({ children }: PropsWithChildren<unknown>) { |
||||
return <ErrorBoundaryInner supportLink={<SupportLink />}>{children}</ErrorBoundaryInner>; |
||||
} |
||||
|
||||
export class ErrorBoundary extends Component<any, ErrorBoundaryState> { |
||||
constructor(props: any) { |
||||
super(props); |
||||
this.state = { error: null, errorInfo: null }; |
||||
} |
||||
|
||||
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; |
||||
} |
||||
function SupportLink() { |
||||
return ( |
||||
<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> |
||||
); |
||||
} |
||||
|
@ -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 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]); |
||||
} |