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

Improve relayer register #691

Merged
merged 9 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const path = require("path");

const buildAppsEslintCommand = (filenames) =>
`npm run lint -w apps -- --fix --file ${filenames.map((f) => path.relative("packages/apps", f)).join(" --file ")}`;
`npm run lint -- --fix --file ${filenames.map((f) => path.relative("./", f)).join(" --file ")}`;

module.exports = {
"packages/apps/src/**/*.{js,jsx,ts,tsx}": [buildAppsEslintCommand],
"src/**/*.{js,jsx,ts,tsx}": [buildAppsEslintCommand],
"**/*.{js,jsx,ts,tsx,json}": "prettier --write",
};
8 changes: 5 additions & 3 deletions src/app/relayer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import RelayerProviderV3 from "@/providers/relayer-provider-v3";
import PageWrap from "@/ui/page-wrap";
import SegmentedTabs from "@/ui/segmented-tabs";
import VersionSwitch from "@/components/version-switch";
import { useState } from "react";
import { useCallback, useState } from "react";

type TabKey = "manage" | "register" | "overview";

export default function RelayerPage() {
const [activeTab, setActiveTab] = useState<TabKey>("manage");
const [version, setVersion] = useState<"v2" | "v3">("v3");

const handleManage = useCallback(() => setActiveTab("manage"), []);

return (
<PageWrap>
<div className="flex flex-col items-center gap-5">
Expand Down Expand Up @@ -46,11 +48,11 @@ export default function RelayerPage() {
children:
version === "v3" ? (
<RelayerProviderV3>
<RelayerRegisterV3 />
<RelayerRegisterV3 onManage={handleManage} />
</RelayerProviderV3>
) : (
<RelayerProvider>
<RelayerRegister />
<RelayerRegister onManage={handleManage} />
</RelayerProvider>
),
},
Expand Down
117 changes: 13 additions & 104 deletions src/components/balance-input.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { InputValue, Token } from "@/types";
import Input from "@/ui/input";
import InputAlert from "@/ui/input-alert";
import { formatBalance, getTokenLogoSrc } from "@/utils";
import Image from "next/image";
import { formatBalance } from "@/utils";
import { ChangeEventHandler, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { parseUnits } from "viem";

Expand All @@ -13,60 +12,42 @@ enum ErrorCode {
}

interface Props {
placeholder?: string;
balance?: bigint;
max?: bigint;
min?: bigint;
compact?: boolean;
autoFocus?: boolean;
disabled?: boolean;
suffix?: "symbol";
enabledDynamicStyle?: boolean;
balance?: bigint;
placeholder?: string;
value: InputValue<bigint>;
token: Token | undefined;
tokenOptions?: Token[];
balanceLoading?: boolean;
onBalanceRefresh?: () => void;
onChange?: (value: InputValue<bigint>) => void;
onTokenChange?: (token: Token) => void;
}

export function BalanceInput({
placeholder,
balance,
max,
min,
compact,
autoFocus,
balance,
disabled,
suffix,
enabledDynamicStyle,
placeholder,
value,
token,
balanceLoading,
tokenOptions = [],
onBalanceRefresh = () => undefined,
onChange = () => undefined,
onTokenChange = () => undefined,
}: Props) {
const spanRef = useRef<HTMLSpanElement | null>(null);
const inputRef = useRef<HTMLInputElement | null>(null);
const balanceRef = useRef(balance);
const tokenRef = useRef(token);

const [dynamicStyle, setDynamicStyle] = useState("text-sm font-medium");
const [errorCode, setErrorCode] = useState<ErrorCode>();

const _placeholder = useMemo(() => {
if (token && compact) {
if (token?.decimals) {
if (max !== undefined) {
return `Max ${formatBalance(max, token.decimals)}`;
} else if (balance !== undefined) {
return `Balance ${formatBalance(balance, token.decimals)}`;
}
}
return placeholder ?? "Enter an amount";
}, [balance, max, placeholder, token, compact]);
}, [balance, max, placeholder, token?.decimals]);

const handleChange = useCallback<ChangeEventHandler<HTMLInputElement>>(
(e) => {
Expand Down Expand Up @@ -121,104 +102,32 @@ export function BalanceInput({
}
}, [token, onChange]);

useEffect(() => {
if (enabledDynamicStyle) {
const inputWidth = inputRef.current?.clientWidth || 1;
const spanWidth = spanRef.current?.clientWidth || 0;
const percent = (spanWidth / inputWidth) * 100;
if (percent < 10) {
setDynamicStyle("text-[3.75rem] font-extralight");
} else if (percent < 20) {
setDynamicStyle("text-[3rem] font-light");
} else if (percent < 30) {
setDynamicStyle("text-[2.25rem] font-light");
} else if (percent < 40) {
setDynamicStyle("text-[1.875rem] font-normal");
} else if (percent < 50) {
setDynamicStyle("text-[1.5rem] font-normal");
} else if (percent < 60) {
setDynamicStyle("text-[1.25rem] font-medium");
} else {
setDynamicStyle("text-[1.125rem] font-medium");
}
}
}, [value.input, enabledDynamicStyle]);

return (
<div
className={`normal-input-wrap relative flex flex-col rounded-medium bg-inner px-small py-small lg:px-medium ${
compact ? "lg:py-medium" : ""
} ${value.valid ? "valid-input-wrap border-transparent" : "invalid-input-wrap"}`}
className={`normal-input-wrap relative ${
value.valid ? "valid-input-wrap border-transparent" : "invalid-input-wrap"
} rounded-xl bg-app-bg px-medium`}
>
<div className="flex items-center justify-between gap-small">
<div className="flex h-10 items-center justify-between gap-small text-sm font-semibold text-white lg:h-11">
<Input
placeholder={_placeholder}
className={`w-full rounded bg-transparent text-white transition-[font-size,font-weight,line-height] duration-300 ${
compact ? "" : "h-12"
} ${enabledDynamicStyle ? `leading-none ${dynamicStyle}` : "text-sm font-medium"}`}
className="w-full rounded bg-transparent"
onChange={handleChange}
ref={inputRef}
disabled={disabled}
value={value.input}
autoFocus={autoFocus}
/>

{compact ? (
suffix === "symbol" && token ? (
<span className="text-sm font-medium">{token.symbol}</span>
) : null
) : (
<div className="flex shrink-0 items-center gap-medium self-end">
{token ? (
<div className="flex shrink-0 items-center gap-small">
<Image width={32} height={32} alt="Token" src={getTokenLogoSrc(token.logo)} className="rounded-full" />
<span className="text-base font-medium text-white">{token.symbol}</span>
</div>
) : null}
{tokenOptions
.filter((t) => t.symbol !== token?.symbol)
.map((t) => (
<Image
key={t.symbol}
width={26}
height={26}
alt="Token"
src={getTokenLogoSrc(t.logo)}
className="rounded-full opacity-80 transition-transform duration-300 hover:cursor-pointer hover:opacity-100 active:-translate-x-1"
onClick={() => onTokenChange(t)}
/>
))}
</div>
)}
{token ? <span>{token.symbol}</span> : null}
</div>

{!compact && token ? (
<div className="flex items-center gap-small">
<span className="text-xs font-medium text-white/50">
Balance: {formatBalance(balance ?? 0n, token.decimals)}
</span>
<button
className={`rounded-full bg-white/20 p-[3px] opacity-50 transition hover:bg-white/20 hover:opacity-100 active:scale-95 ${
balanceLoading ? "animate-spin" : ""
}`}
onClick={onBalanceRefresh}
>
<Image alt="Refresh" width={14} height={14} src="/images/refresh.svg" />
</button>
</div>
) : null}

{errorCode === ErrorCode.INSUFFICIENT ? (
<InputAlert text="* Insufficient" />
) : errorCode === ErrorCode.REQUIRE_LESS ? (
<InputAlert text={`* Max: ${formatBalance(max ?? 0n, token?.decimals ?? 0, { precision: 6 })}`} />
) : errorCode === ErrorCode.REQUIRE_MORE ? (
<InputAlert text={`* Min: ${formatBalance(min ?? 0n, token?.decimals ?? 0, { precision: 6 })}`} />
) : null}

<span className="invisible fixed left-0 top-0 -z-50" ref={spanRef}>
{value.input}
</span>
</div>
);
}
Expand Down
99 changes: 34 additions & 65 deletions src/components/chain-select.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,69 @@
import { ChainConfig } from "@/types";
import Select from "@/ui/select";
import { getChainLogoSrc } from "@/utils";
import { Placement } from "@floating-ui/react";
import Image from "next/image";

interface Props {
options?: ChainConfig[];
value?: ChainConfig;
placeholder?: string;
compact?: boolean;
placement?: Placement;
className?: string;
onChange?: (value: ChainConfig | undefined) => void;
}

export default function ChainSelect({
value,
placeholder,
compact,
placement,
className,
options = [],
onChange = () => undefined,
}: Props) {
return (
<Select
labelClassName={`gap-small rounded-medium flex items-center justify-between transition active:translate-y-1 ${className}`}
childClassName={`bg-inner flex flex-col rounded-medium max-h-60 overflow-y-auto border border-component app-scrollbar ${
compact ? "py-small" : "p-medium"
}`}
sameWidth
clearable
label={
value ? (
<div className="flex items-center gap-medium truncate">
{compact ? null : (
<Image
alt="Chain"
width={32}
height={32}
src={getChainLogoSrc(value.logo)}
className="h-8 w-8 shrink-0 rounded-full"
/>
)}

<span className="truncate text-sm font-medium text-white">{value.name}</span>
<Image
alt="Chain"
width={22}
height={22}
src={getChainLogoSrc(value.logo)}
className="hidden shrink-0 rounded-full lg:inline"
/>
<span className="truncate text-sm font-semibold text-white">{value.name}</span>
</div>
) : undefined
}
placeholder={<span className="truncate text-sm font-medium text-slate-400">{placeholder}</span>}
placement={placement}
sameWidth={compact ? true : false}
clearable={compact ? true : false}
placeholder={<span className="truncate text-sm font-semibold text-slate-400">{placeholder}</span>}
labelClassName={`gap-small flex items-center justify-between ${className}`}
childClassName="bg-app-bg flex flex-col rounded-xl max-h-60 overflow-y-auto border border-white/20 app-scrollbar py-small"
onClear={() => onChange(undefined)}
>
{options.length ? (
compact ? (
options.map((option) => {
return (
<button
key={option.id}
onClick={() => onChange(option)}
className="flex items-center gap-medium px-large py-2 text-start transition-colors hover:bg-white/10"
>
<Image
width={20}
height={20}
alt="Chain logo"
src={getChainLogoSrc(option.logo)}
className="rounded-full"
/>
<span className="truncate text-sm font-medium text-white">{option.name}</span>
</button>
);
})
) : (
<div className="grid grid-cols-2 gap-small">
{options.map((option) => (
<button
key={option.id}
className="flex w-36 shrink-0 items-center gap-small truncate rounded-medium bg-component px-2 py-1 transition-colors hover:bg-white/20"
onClick={() => onChange(option)}
>
<Image
width={18}
height={18}
alt="Chain logo"
src={getChainLogoSrc(option.logo)}
className="rounded-full"
/>
<span className="truncate text-sm font-medium text-white">{option.name}</span>
</button>
))}
</div>
)
options.map((option) => {
return (
<button
key={option.id}
disabled={value?.id === option.id}
onClick={() => onChange(option)}
className="flex items-center gap-medium px-large py-medium text-start transition-colors hover:bg-white/5 disabled:bg-white/10"
>
<Image
width={20}
height={20}
alt="Chain logo"
src={getChainLogoSrc(option.logo)}
className="rounded-full"
/>
<span className="truncate text-sm font-semibold text-white">{option.name}</span>
</button>
);
})
) : (
<div className="px-large py-small">
<span className="text-sm text-white/50">No data</span>
<div className="inline-flex justify-center p-2">
<span className="text-sm font-semibold text-slate-400">No data</span>
</div>
)}
</Select>
Expand Down
Loading
Loading