Skip to content

Commit

Permalink
Enhance account name display (#22)
Browse files Browse the repository at this point in the history
* Hook useAccountName

* Support inputChildren of Input

* Export api from TransferProvider

* Enhance account name display

* Enable notify pr comment for preview
  • Loading branch information
JayJay1024 authored May 6, 2024
1 parent af26dec commit 0c3de02
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 12 deletions.
1 change: 1 addition & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
project_name: "crosschain-ui"
script_run: false
dist_path: .
enable_notify_comment: true
enable_notify_slack: true
slack_channel: public-darwinia-websites-apps
slack_webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }}
1 change: 1 addition & 0 deletions .github/workflows/deploy-stg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
project_name: "crosschain-ui"
script_run: false
dist_path: .
enable_notify_comment: true
enable_notify_slack: true
slack_channel: public-darwinia-websites-apps
slack_webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }}
68 changes: 59 additions & 9 deletions src/components/address-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import InputSelect from "@/ui/input-select";
import { ChangeEventHandler, useCallback, useEffect, useMemo, useRef } from "react";
import ConnectWallet from "./connect-wallet";
import { WalletAccount } from "@talismn/connect-wallets";
import { isValidAddress } from "@/utils";
import { useTransfer } from "@/hooks";
import { isValidAddress, toShortAdrress } from "@/utils";
import { useAccountName, useTransfer } from "@/hooks";
import AddressIdenticon from "./address-identicon";
import { ApiPromise } from "@polkadot/api";
import { AddressType } from "@/types";

interface Value {
name?: string;
Expand All @@ -14,6 +16,7 @@ interface Value {

interface Props {
canInput?: boolean;
api: ApiPromise | undefined;
who: "sender" | "recipient";
value?: Value;
options?: Pick<Value, "address" | "name">[];
Expand All @@ -25,6 +28,7 @@ interface Props {
}

export default function AddressInput({
api,
who,
value,
options,
Expand Down Expand Up @@ -78,26 +82,33 @@ export default function AddressInput({
childClassName="flex flex-col py-middle bg-component border-primary border border-radius max-h-60 overflow-y-auto"
placeholder={placeholder ?? "Address"}
value={value?.address ?? ""} // Keep it as a controlled component
inputChildren={
value?.name ? (
<div className="inline-block px-1">
{value.name}
<span className="text-white/50">&nbsp;({toShortAdrress(value.address)})</span>
</div>
) : undefined
}
alert={value?.valid === false ? "* Unavailable address" : ""}
onClear={handleClear}
onChange={handleChange}
>
{options?.length ? (
options.map((account, index) => (
<button
<Option
api={api}
account={account}
key={account.address}
addressType={addressType}
onClick={() => {
onChange({ ...account, valid: isValidAddress(account.address, addressType) });
if (accounts?.at(index)) {
onAccountChange(accounts[index]);
}
}}
onChange={onChange}
disabled={!isValidAddress(account.address, addressType)}
className="flex items-center gap-middle px-middle py-1 transition-colors hover:bg-white/10 disabled:cursor-not-allowed disabled:opacity-60"
>
<AddressIdenticon size={20} address={account.address} />
<span className="truncate text-sm font-medium">{account.name || account.address}</span>
</button>
/>
))
) : (
<div className="inline-flex justify-center px-middle py-small">
Expand All @@ -107,3 +118,42 @@ export default function AddressInput({
</InputSelect>
);
}

function Option({
api,
account,
disabled,
addressType,
onClick,
onChange,
}: {
account: Pick<Value, "address" | "name">;
api: ApiPromise | undefined;
disabled?: boolean;
addressType: AddressType;
onClick: () => void;
onChange: (value: Value | undefined) => void;
}) {
const name = useAccountName(account.address, api);

return (
<button
onClick={() => {
onChange({ ...account, name: account.name ?? name, valid: isValidAddress(account.address, addressType) });
onClick();
}}
disabled={disabled}
className="flex items-center gap-middle px-middle py-1 transition-colors hover:bg-white/10 disabled:cursor-not-allowed disabled:opacity-60"
>
<AddressIdenticon size={20} address={account.address} />
{account.name || name !== account.address ? (
<div className="inline-block truncate text-sm font-medium">
{account.name ?? name}
<span className="text-white/50">&nbsp;({toShortAdrress(account.address)})</span>
</div>
) : (
<span className="truncate text-sm font-medium">{account.address}</span>
)}
</button>
);
}
4 changes: 4 additions & 0 deletions src/components/transfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const {

export default function Transfer() {
const {
sourceApi,
targetApi,
assetLimit,
targetAssetDetails,
sender,
Expand Down Expand Up @@ -243,6 +245,7 @@ export default function Transfer() {
<TransferSection label="Sender" className="border-radius mt-10 border-[2px] border-white/20">
<AddressInput
who="sender"
api={sourceApi}
placeholder="Select an address"
value={sender}
options={senderOptions}
Expand All @@ -257,6 +260,7 @@ export default function Transfer() {
<TransferSection label="Recipient" className="border-radius mt-10 border-[2px] border-white/20">
<AddressInput
canInput
api={targetApi}
who="recipient"
placeholder="Select or enter an address"
value={recipient}
Expand Down
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./use-balance";
export * from "./use-toggle";
export * from "./use-asset-limit";
export * from "./use-asset-details";
export * from "./use-account-name";
62 changes: 62 additions & 0 deletions src/hooks/use-account-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { DeriveAccountRegistration } from "@polkadot/api-derive/accounts/types";
import type { AccountId } from "@polkadot/types/interfaces";
import { TypeRegistry } from "@polkadot/types/create";
import { stringToU8a } from "@polkadot/util";
import { from, Subscription } from "rxjs";

import { useEffect, useState } from "react";
import { ApiPromise } from "@polkadot/api";

const registry = new TypeRegistry();
const PAD = 32;
const KNOWN: [AccountId, string][] = [
[registry.createType("AccountId", stringToU8a("modlpy/socie".padEnd(PAD, "\0"))), "Society"],
[registry.createType("AccountId", stringToU8a("modlpy/trsry".padEnd(PAD, "\0"))), "Treasury"],
];

const extractName = (accountAddress: string, defaultName?: string) => {
const known = KNOWN.find(([address]) => address.eq(accountAddress));
if (known) {
return known[1];
}
return defaultName || accountAddress;
};

const extractIdentity = (cacheAddress: string, identity: DeriveAccountRegistration) => {
const judgements = identity.judgements.filter(([, judgement]) => !judgement.isFeePaid);
const isGood = judgements.some(([, judgement]) => judgement.isKnownGood || judgement.isReasonable);
// const isBad = judgements.some(([, judgement]) => judgement.isErroneous || judgement.isLowQuality);
const displayName = isGood ? identity.display : (identity.display || "").replace(/[^\x20-\x7E]/g, "");
const displayParent =
identity.displayParent && (isGood ? identity.displayParent : identity.displayParent.replace(/[^\x20-\x7E]/g, ""));
return displayParent || displayName || cacheAddress;
};

export const useAccountName = (address: string, api: ApiPromise | undefined) => {
const [name, setName] = useState(address);

useEffect(() => {
let sub$$: Subscription | undefined;

if (api) {
sub$$ = from(api.derive.accounts.info(address)).subscribe({
next: ({ nickname, identity }) => {
if (typeof api.query.identity?.identityOf === "function") {
setName(identity.display ? extractIdentity(address, identity) : extractName(address));
} else if (nickname) {
setName(nickname);
} else {
setName(extractName(address));
}
},
error: console.error,
});
}

return () => {
sub$$?.unsubscribe();
};
}, [address, api]);

return name;
};
7 changes: 7 additions & 0 deletions src/providers/transfer-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { WalletAccount } from "@talismn/connect-wallets";
import { Signer } from "@polkadot/api/types";
import { notifyError, notifyTransaction, parseCross, signAndSendExtrinsic } from "@/utils";
import { useApi, useAssetDetails, useAssetLimit, useBalance } from "@/hooks";
import { ApiPromise } from "@polkadot/api";

interface TransferCtx {
assetLimit: BN | undefined;
Expand All @@ -30,6 +31,8 @@ interface TransferCtx {
activeRecipientAccount: WalletAccount | undefined;
activeSenderWallet: WalletID | undefined;
activeRecipientWallet: WalletID | undefined;
sourceApi: ApiPromise | undefined;
targetApi: ApiPromise | undefined;

setTransferAmount: Dispatch<SetStateAction<{ valid: boolean; input: string; amount: BN }>>;
setSourceChain: Dispatch<SetStateAction<ChainConfig>>;
Expand Down Expand Up @@ -81,6 +84,8 @@ const defaultValue: TransferCtx = {
activeRecipientAccount: undefined,
activeSenderWallet: undefined,
activeRecipientWallet: undefined,
sourceApi: undefined,
targetApi: undefined,

setTransferAmount: () => undefined,
setSourceChain: () => undefined,
Expand Down Expand Up @@ -195,6 +200,8 @@ export default function TransferProvider({ children }: PropsWithChildren<unknown
return (
<TransferContext.Provider
value={{
sourceApi,
targetApi,
assetLimit,
targetAssetDetails,
bridgeInstance,
Expand Down
3 changes: 3 additions & 0 deletions src/ui/input-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface Props {
inputClassName?: string;
childClassName?: string;
arrowClassName?: string;
inputChildren?: JSX.Element;
onClear?: () => void;
onChange?: ChangeEventHandler<HTMLInputElement>;
}
Expand All @@ -51,6 +52,7 @@ export default function InputSelect({
placement,
canInput,
sameWidth,
inputChildren,
wrapClassName,
inputClassName,
childClassName,
Expand Down Expand Up @@ -113,6 +115,7 @@ export default function InputSelect({
disabled={disabled}
onChange={onChange}
readOnly={!canInput}
inputChildren={inputChildren}
/>
{suffix === true ? (
<div className="flex h-full shrink-0 items-center gap-small">
Expand Down
17 changes: 14 additions & 3 deletions src/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { InputHTMLAttributes, forwardRef } from "react";

interface Props {}
interface Props {
inputChildren?: JSX.Element;
}

export default forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputElement> & Props>(function Input(
{ className, ...rest },
{ className, inputChildren, ...rest },
ref,
) {
return (
<input className={`focus-visible:outline-none disabled:cursor-not-allowed ${className}`} ref={ref} {...rest} />
<div className="relative w-full">
<input
className={`focus-visible:outline-none disabled:cursor-not-allowed ${
inputChildren ? "text-transparent" : ""
} ${className}`}
ref={ref}
{...rest}
/>
{inputChildren && <div className="absolute bottom-0 left-0 right-0 top-0 truncate">{inputChildren}</div>}
</div>
);
});

0 comments on commit 0c3de02

Please sign in to comment.