Merge pull request #5 from hyperlane-xyz/form-improvements
Form improvements and code cleanuppull/6/head
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 42 KiB |
@ -1,28 +0,0 @@ |
||||
import { PropsWithChildren, useEffect, useState } from 'react'; |
||||
|
||||
export function Fade(props: PropsWithChildren<{ show: boolean }>) { |
||||
const { show, children } = props; |
||||
const [render, setRender] = useState(show); |
||||
|
||||
useEffect(() => { |
||||
if (show) setRender(true); |
||||
}, [show]); |
||||
|
||||
const onAnimationEnd = () => { |
||||
if (!show) setRender(false); |
||||
}; |
||||
|
||||
return render ? ( |
||||
<div |
||||
style={{ |
||||
animationName: show ? 'fadeIn' : 'fadeOut', |
||||
animationDuration: '1s', |
||||
//https://github.com/radix-ui/primitives/issues/1074#issuecomment-1089555751
|
||||
animationFillMode: 'forwards', |
||||
}} |
||||
onAnimationEnd={onAnimationEnd} |
||||
> |
||||
{children} |
||||
</div> |
||||
) : null; |
||||
} |
@ -1,7 +0,0 @@ |
||||
import LeftArrow from '../../images/icons/arrow-left-circle.svg'; |
||||
|
||||
import { IconButton, IconButtonProps } from './IconButton'; |
||||
|
||||
export function BackButton(props: IconButtonProps) { |
||||
return <IconButton imgSrc={LeftArrow} title="Go back" {...props} />; |
||||
} |
@ -1,41 +0,0 @@ |
||||
import { PropsWithChildren, ReactElement } from 'react'; |
||||
|
||||
interface ButtonProps { |
||||
type?: 'submit' | 'reset' | 'button'; |
||||
onClick?: () => void; |
||||
classes?: string; |
||||
bold?: boolean; |
||||
disabled?: boolean; |
||||
icon?: ReactElement; |
||||
title?: string; |
||||
} |
||||
|
||||
export function BorderedButton(props: PropsWithChildren<ButtonProps>) { |
||||
const { type, onClick, classes, bold, icon, disabled, title } = props; |
||||
|
||||
const base = 'border border-black rounded transition-all'; |
||||
const onHover = 'hover:border-gray-500 hover:text-gray-500'; |
||||
const onDisabled = 'disabled:border-gray-300 disabled:text-gray-300'; |
||||
const onActive = 'active:border-gray-400 active:text-gray-400'; |
||||
const weight = bold ? 'font-semibold' : ''; |
||||
const allClasses = `${base} ${onHover} ${onDisabled} ${onActive} ${weight} ${classes}`; |
||||
|
||||
return ( |
||||
<button |
||||
onClick={onClick} |
||||
type={type ?? 'button'} |
||||
disabled={disabled ?? false} |
||||
title={title} |
||||
className={allClasses} |
||||
> |
||||
{icon ? ( |
||||
<div className="flex items-center"> |
||||
{props.icon} |
||||
{props.children} |
||||
</div> |
||||
) : ( |
||||
<>{props.children}</> |
||||
)} |
||||
</button> |
||||
); |
||||
} |
@ -1,31 +0,0 @@ |
||||
import { PropsWithChildren } from 'react'; |
||||
|
||||
export interface TextButtonProps { |
||||
classes?: string; |
||||
onClick?: () => void; |
||||
disabled?: boolean; |
||||
type?: 'button' | 'submit'; |
||||
passThruProps?: any; |
||||
} |
||||
|
||||
export function TextButton(props: PropsWithChildren<TextButtonProps>) { |
||||
const { classes, onClick, disabled, type, children, passThruProps } = props; |
||||
|
||||
const base = 'flex place-content-center transition-all'; |
||||
const onHover = 'hover:opacity-70'; |
||||
const onDisabled = 'disabled:opacity-50'; |
||||
const onActive = 'active:opacity-60'; |
||||
const allClasses = `${base} ${onHover} ${onDisabled} ${onActive} ${classes}`; |
||||
|
||||
return ( |
||||
<button |
||||
onClick={onClick} |
||||
type={type || 'button'} |
||||
disabled={disabled ?? false} |
||||
className={allClasses} |
||||
{...passThruProps} |
||||
> |
||||
{children} |
||||
</button> |
||||
); |
||||
} |
@ -1,8 +0,0 @@ |
||||
interface Props { |
||||
classes?: string; |
||||
} |
||||
|
||||
export function HrDivider(props: Props) { |
||||
const { classes } = props; |
||||
return <hr className={`w-full h-px border-none bg-gray-300 ${classes}`} />; |
||||
} |
Before Width: | Height: | Size: 333 B |
Before Width: | Height: | Size: 293 B |
Before Width: | Height: | Size: 289 B |
Before Width: | Height: | Size: 358 B |
Before Width: | Height: | Size: 649 B |
Before Width: | Height: | Size: 473 B |
Before Width: | Height: | Size: 696 B |
Before Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 313 B |
Before Width: | Height: | Size: 362 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 285 B |
Before Width: | Height: | Size: 520 B |
Before Width: | Height: | Size: 373 B |
Before Width: | Height: | Size: 690 B |
Before Width: | Height: | Size: 459 B |
Before Width: | Height: | Size: 638 B |
Before Width: | Height: | Size: 755 B |
Before Width: | Height: | Size: 476 B |
Before Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 1.1 KiB |
@ -1,37 +0,0 @@ |
||||
// Modeled after ethers.providers.TransactionReceipt
|
||||
export interface PartialTransactionReceipt { |
||||
from: Address; |
||||
transactionHash: string; |
||||
blockNumber: number; |
||||
gasUsed: number; |
||||
timestamp: number; |
||||
} |
||||
|
||||
// TODO consider reconciling with SDK's MessageStatus
|
||||
export enum MessageStatus { |
||||
Pending = 'pending', |
||||
Delivered = 'delivered', |
||||
Failing = 'failing', |
||||
} |
||||
|
||||
export interface MessageStub { |
||||
id: number; |
||||
status: MessageStatus; |
||||
sender: Address; |
||||
recipient: Address; |
||||
originDomainId: number; |
||||
destinationDomainId: number; |
||||
originChainId: number; |
||||
destinationChainId: number; |
||||
originTimestamp: number; // Note, equivalent to timestamp in originTransaction
|
||||
destinationTimestamp?: number; // Note, equivalent to timestamp in destinationTransaction
|
||||
} |
||||
|
||||
export interface Message extends MessageStub { |
||||
body: string; |
||||
decodedBody?: string; |
||||
leafIndex: number; |
||||
hash: string; // message hash, not related to txs
|
||||
originTransaction: PartialTransactionReceipt; |
||||
destinationTransaction?: PartialTransactionReceipt; |
||||
} |
@ -1,30 +0,0 @@ |
||||
export function invertKeysAndValues(data: any) { |
||||
return Object.fromEntries(Object.entries(data).map(([key, value]) => [value, key])); |
||||
} |
||||
|
||||
// Get the subset of the object from key list
|
||||
export function pick<K extends string | number, V = any>(obj: Record<K, V>, keys: K[]) { |
||||
const ret: Partial<Record<K, V>> = {}; |
||||
for (const key of keys) { |
||||
ret[key] = obj[key]; |
||||
} |
||||
return ret as Record<K, V>; |
||||
} |
||||
|
||||
// Remove a particular key from an object if it exists
|
||||
export function omit<K extends string | number, V = any>(obj: Record<K, V>, key: K) { |
||||
const ret: Partial<Record<K, V>> = {}; |
||||
for (const k of Object.keys(obj)) { |
||||
if (k === key) continue; |
||||
ret[k] = obj[k]; |
||||
} |
||||
return ret as Record<K, V>; |
||||
} |
||||
|
||||
// Returns an object with the keys as values from an array and value set to true
|
||||
export function arrayToObject(keys: Array<string | number>, val = true) { |
||||
return keys.reduce((result, k) => { |
||||
result[k] = val; |
||||
return result; |
||||
}, {}); |
||||
} |
@ -1,28 +0,0 @@ |
||||
import type { ParsedUrlQuery } from 'querystring'; |
||||
|
||||
import { logger } from './logger'; |
||||
|
||||
// To make Next's awkward query param typing more convenient
|
||||
export function getQueryParamString(query: ParsedUrlQuery, key: string, defaultVal = '') { |
||||
if (!query) return defaultVal; |
||||
const val = query[key]; |
||||
if (val && typeof val === 'string') return val; |
||||
else return defaultVal; |
||||
} |
||||
|
||||
// Circumventing Next's router.replace method here because
|
||||
// it's async and causes race conditions btwn components.
|
||||
// This will only modify the url but not trigger any routing
|
||||
export function replacePathParam(key: string, val: string) { |
||||
try { |
||||
const url = new URL(window.location.href); |
||||
if (val) { |
||||
url.searchParams.set(key, val); |
||||
} else { |
||||
url.searchParams.delete(key); |
||||
} |
||||
window.history.replaceState('', '', url); |
||||
} catch (error) { |
||||
logger.error('Error replacing path param', error); |
||||
} |
||||
} |
@ -1,59 +0,0 @@ |
||||
// Inspired by https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
|
||||
export function getHumanReadableTimeString(timestamp: number) { |
||||
const seconds = Math.floor((Date.now() - timestamp) / 1000); |
||||
|
||||
if (seconds <= 1) { |
||||
return 'Just now'; |
||||
} |
||||
if (seconds <= 60) { |
||||
return `${seconds} seconds ago`; |
||||
} |
||||
const minutes = Math.floor(seconds / 60); |
||||
if (minutes <= 1) { |
||||
return '1 minute ago'; |
||||
} |
||||
if (minutes < 60) { |
||||
return `${minutes} minutes ago`; |
||||
} |
||||
const hours = Math.floor(minutes / 60); |
||||
if (hours <= 1) { |
||||
return '1 hour ago'; |
||||
} |
||||
if (hours < 24) { |
||||
return `${hours} hours ago`; |
||||
} |
||||
|
||||
const date = new Date(timestamp); |
||||
return date.toLocaleDateString(); |
||||
} |
||||
|
||||
export function getHumanReadableDuration(ms: number, minSec?: number) { |
||||
let seconds = Math.round(ms / 1000); |
||||
|
||||
if (minSec) { |
||||
seconds = Math.max(seconds, minSec); |
||||
} |
||||
|
||||
if (seconds <= 60) { |
||||
return `${seconds} sec`; |
||||
} |
||||
const minutes = Math.floor(seconds / 60); |
||||
if (minutes < 60) { |
||||
return `${minutes} min`; |
||||
} |
||||
const hours = Math.floor(minutes / 60); |
||||
return `${hours} hr`; |
||||
} |
||||
|
||||
export function getDateTimeString(timestamp: number) { |
||||
const date = new Date(timestamp); |
||||
return `${date.toLocaleTimeString()} ${date.toLocaleDateString()}`; |
||||
} |
||||
|
||||
// Adjusts a timestamp forward/backward based on
|
||||
// the local time's for the timezone offset
|
||||
export function adjustToUtcTime(timestamp: number) { |
||||
const offsetMs = new Date().getTimezoneOffset() * 60_000; |
||||
const adjusted = new Date(timestamp + offsetMs); |
||||
return adjusted.toISOString(); |
||||
} |