From 3718a10b31b331c19690e04b8eeae2c2fd3be488 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 2 Oct 2023 15:54:11 +0300 Subject: [PATCH] Add skeleton for price field --- .../common/inputs/SelectAccountInput.tsx | 43 +++++++++---------- src/components/domains/DomainInput.tsx | 13 +++++- .../domains/EligibleDomainsSection.tsx | 40 +++++++++++------ .../domains/pendingOrders/index.tsx | 3 +- .../registerDomainModal/Index.module.sass | 13 +++++- .../RegisterDomainModal.tsx | 26 +++++++++-- src/components/domains/utils.tsx | 25 ++++++++--- .../lazy-connection/LazyConnectionContext.tsx | 3 +- .../features/chainsInfo/chainsInfoSlice.ts | 1 + 9 files changed, 116 insertions(+), 51 deletions(-) diff --git a/src/components/common/inputs/SelectAccountInput.tsx b/src/components/common/inputs/SelectAccountInput.tsx index d9c94b232..a1fae4508 100644 --- a/src/components/common/inputs/SelectAccountInput.tsx +++ b/src/components/common/inputs/SelectAccountInput.tsx @@ -1,15 +1,15 @@ +import { GenericAccountId } from '@polkadot/types' +import registry from '@subsocial/api/utils/registry' +import { isDef, isEmptyArray, toSubsocialAddress } from '@subsocial/utils' import { Select } from 'antd' +import clsx from 'clsx' import { useEffect, useState } from 'react' +import { useMyAccounts } from 'src/components/auth/MyAccountsContext' +import { equalAddresses } from 'src/components/substrate' +import { useSelectProfile } from 'src/rtk/app/hooks' import { SelectAddressPreview } from '../../profile-selector/MyAccountMenu' -import { isDef, isEmptyArray, toSubsocialAddress } from '@subsocial/utils' -import registry from '@subsocial/api/utils/registry' -import { GenericAccountId } from '@polkadot/types' import Avatar from '../../profiles/address-views/Avatar' import styles from './inputs.module.sass' -import { useSelectProfile } from 'src/rtk/app/hooks' -import { useMyAccounts } from 'src/components/auth/MyAccountsContext' -import { equalAddresses } from 'src/components/substrate' -import clsx from 'clsx' export const isValidAccount = (address?: string) => { try { @@ -52,27 +52,26 @@ export const SelectAccountInput = ({ className, withAvatar = true, network, - disabled + disabled, }: SelectAccountInputProps) => { const { accounts, status } = useMyAccounts() - const allExtensionAccounts = accounts.map(x => x.address && toSubsocialAddress(x.address) as string).filter(isDef) + const allExtensionAccounts = accounts + .map(x => x.address && (toSubsocialAddress(x.address) as string)) + .filter(isDef) - const [ defaultOptions, setDefaultOptions ] = useState([]) - const [ selectOptions, setSelectOptions ] = useState(defaultOptions) + const [defaultOptions, setDefaultOptions] = useState([]) + const [selectOptions, setSelectOptions] = useState(defaultOptions) const profile = useSelectProfile(value) useEffect(() => { - const options = - status === 'OK' - ? filterSelectOptions(allExtensionAccounts, value, network) - : [] + const options = status === 'OK' ? filterSelectOptions(allExtensionAccounts, value, network) : [] if (!defaultOptions || isEmptyArray(defaultOptions)) { setDefaultOptions(options) } setSelectOptions(options) - }, [ value, status ]) + }, [value, status]) const onSelectChange = (value: string) => { setValue(value) @@ -106,9 +105,7 @@ export const SelectAccountInput = ({ } const onSelectHandler = (searchValue: any) => { - const filterOptions = defaultOptions.filter( - (x: any) => !x.value.includes(searchValue) - ) + const filterOptions = defaultOptions.filter((x: any) => !x.value.includes(searchValue)) if (filterOptions) { setSelectOptions(filterOptions as any[]) @@ -121,9 +118,11 @@ export const SelectAccountInput = ({ const avatar = (
- {value - ? - :
} + {value ? ( + + ) : ( +
+ )}
) diff --git a/src/components/domains/DomainInput.tsx b/src/components/domains/DomainInput.tsx index 0c2c37f1a..57409bc13 100644 --- a/src/components/domains/DomainInput.tsx +++ b/src/components/domains/DomainInput.tsx @@ -1,5 +1,5 @@ import { SearchOutlined } from '@ant-design/icons' -import { isEmptyStr, nonEmptyStr } from '@subsocial/utils' +import { isEmptyStr, nonEmptyStr, parseDomain } from '@subsocial/utils' import { Button, Form, Input } from 'antd' import { useRouter } from 'next/router' import { useEffect, useRef } from 'react' @@ -37,6 +37,8 @@ export const InputDomain = ({ onChange }: DomainInputProps) => { onChange && onChange(domain) } + const inputDefaultValue = domain ? parseDomain(domain as string).domain : '' + return (
@@ -45,8 +47,15 @@ export const InputDomain = ({ onChange }: DomainInputProps) => { ref={refInput} onPressEnter={onSearchHandler} size='large' + defaultValue={inputDefaultValue} /> - diff --git a/src/components/domains/EligibleDomainsSection.tsx b/src/components/domains/EligibleDomainsSection.tsx index 58370df60..faec7936d 100644 --- a/src/components/domains/EligibleDomainsSection.tsx +++ b/src/components/domains/EligibleDomainsSection.tsx @@ -4,6 +4,7 @@ import { parseDomain } from '@subsocial/utils' import { Button, Card, Radio, RadioChangeEvent, Result, Row, Tag, Tooltip } from 'antd' import React, { FC, useEffect } from 'react' import config from 'src/config' +import { useChainInfo } from 'src/rtk/features/chainsInfo/chainsInfoHooks' import { DomainEntity } from 'src/rtk/features/domains/domainsSlice' import { useSelectSellerConfig } from 'src/rtk/features/sellerConfig/sellerConfigHooks' import { useSelectPendingOrderById } from '../../rtk/features/domainPendingOrders/pendingOrdersHooks' @@ -57,9 +58,10 @@ const UnavailableBtn = ({ domain: { owner, id } }: DomainProps) => { type DomainActionProps = DomainProps & { domainPrice?: string + loadingPrice: boolean } -const DomainAction = ({ domain, domainPrice }: DomainActionProps) => { +const DomainAction = ({ domain, domainPrice, loadingPrice }: DomainActionProps) => { const { owner } = domain const { domainSellerKind } = useManageDomainContext() const sellerConfig = useSelectSellerConfig() @@ -108,6 +110,7 @@ const DomainAction = ({ domain, domainPrice }: DomainActionProps) => { domainName={domain.id} domainSellerKind={domainSellerKind} domainPrice={domainPrice} + loadingPrice={loadingPrice} /> ) } @@ -116,13 +119,16 @@ type FoundDomainsProps = { structs?: DomainEntity[] Action?: FC domainPrice?: string + loadingPrice: boolean } -const FoundDomains = ({ structs = [], Action, domainPrice }: FoundDomainsProps) => { +const FoundDomains = ({ structs = [], Action, domainPrice, loadingPrice }: FoundDomainsProps) => { return ( <> {structs.map(d => { - const action = Action ? : null + const action = Action ? ( + + ) : null return })} @@ -155,7 +161,7 @@ const ReservedDomainCard = ({ domain }: Omit) => { const SuggestedDomains = ({ domain: { id }, rightElement, withDivider }: DomainProps) => { const { domain } = parseDomain(id) const domains = config.suggestedTlds?.map(tld => `${domain}.${tld}`) - const domainPrice = useGetDomainPrice(id) + const { price: domainPrice, loading: loadingPrice } = useGetDomainPrice(id) const { loading, domainsStruct = [] } = useFetchDomains(domains || []) @@ -169,7 +175,12 @@ const SuggestedDomains = ({ domain: { id }, rightElement, withDivider }: DomainP withDivider={withDivider} icon={} > - + ) } @@ -233,8 +244,8 @@ const SuggestedDomains = ({ domain: { id }, rightElement, withDivider }: DomainP // } const variantOpt = [ - { value: 'SUB', label: 'SUB' }, - { value: 'DOT', label: 'DOT' }, + { value: 'SUB', label: 'SUB', chain: 'subsocial' }, + { value: 'DOT', label: 'DOT', chain: 'polkadot' }, ] // const ChooseDomain = ({ domain, rightElement }: DomainProps) => { @@ -274,6 +285,7 @@ export const EligibleDomainsSection = ({ domain }: FoundDomainCardProps) => { // const domains = useBuildDomainsWithTldByDomain(domain) const { isReserved, loading: checkingWord } = useIsReservedWord(domain.id) const { setVariant } = useManageDomainContext() + const chainsInfo = useChainInfo() // if (isEmptyArray(domains)) return null @@ -298,11 +310,15 @@ export const EligibleDomainsSection = ({ domain }: FoundDomainCardProps) => { defaultValue={'SUB'} className={styles.BuyWithRadio} > - {variantOpt.map(({ value, label }, i) => ( - - {label} - - ))} + {variantOpt.map(({ value, label, chain }, i) => { + const disable = chain !== 'subsocial' && !chainsInfo[chain]?.tokenSymbols + + return ( + + {label} + + ) + })} diff --git a/src/components/domains/pendingOrders/index.tsx b/src/components/domains/pendingOrders/index.tsx index 37c418604..e6bf0b09e 100644 --- a/src/components/domains/pendingOrders/index.tsx +++ b/src/components/domains/pendingOrders/index.tsx @@ -29,7 +29,7 @@ const PendingDomain = ({ pendingDomain, time }: PendingDomainProps) => { const sellerConfig = useSelectSellerConfig() const reloadPendingOrders = useCreateReloadPendingOrders() const myAddress = useMyAddress() - const domainPrice = useGetDomainPrice(pendingDomain.id) + const { price: domainPrice, loading: loadingPrice } = useGetDomainPrice(pendingDomain.id) const { processingDomains } = useManageDomainContext() @@ -59,6 +59,7 @@ const PendingDomain = ({ pendingDomain, time }: PendingDomainProps) => { withPrice={false} domainSellerKind={destination as DomainSellerKind} domainPrice={domainPrice} + loadingPrice={loadingPrice} /> ) diff --git a/src/components/domains/registerDomainModal/Index.module.sass b/src/components/domains/registerDomainModal/Index.module.sass index 45019aec2..53c2501f3 100644 --- a/src/components/domains/registerDomainModal/Index.module.sass +++ b/src/components/domains/registerDomainModal/Index.module.sass @@ -8,7 +8,6 @@ $element_height: 40px \:global .ant-select-selector border-radius: $border_radius_large !important - .DomainModal \:global .ant-modal-footer padding-top: 0 !important @@ -27,6 +26,16 @@ $element_height: 40px button height: $element_height +.PriceSkeleton + width: 60px !important + + \:global .ant-skeleton-content + ul + margin-bottom: 0 + + li + width: 100% !important + .ModalTitle h2 color: #222 @@ -82,4 +91,4 @@ $element_height: 40px .RecipientFieldDesc font-size: $font_small - line-height: $font_small \ No newline at end of file + line-height: $font_small diff --git a/src/components/domains/registerDomainModal/RegisterDomainModal.tsx b/src/components/domains/registerDomainModal/RegisterDomainModal.tsx index 3fc6c64fd..36f70d52f 100644 --- a/src/components/domains/registerDomainModal/RegisterDomainModal.tsx +++ b/src/components/domains/registerDomainModal/RegisterDomainModal.tsx @@ -1,4 +1,4 @@ -import { Button, Modal, Space, Tag } from 'antd' +import { Button, Modal, Skeleton, Space, Tag } from 'antd' import clsx from 'clsx' import { useEffect, useState } from 'react' import { useIsMyAddress } from 'src/components/auth/MyAccountsContext' @@ -165,6 +165,7 @@ type BuyByDotButtonProps = { withPrice?: boolean domainSellerKind: DomainSellerKind domainPrice?: string + loadingPrice: boolean } const RegisterDomainButton = ({ @@ -173,6 +174,7 @@ const RegisterDomainButton = ({ withPrice = true, domainSellerKind, domainPrice, + loadingPrice, }: BuyByDotButtonProps) => { const sellerConfig = useSelectSellerConfig() const { sellerChain } = sellerConfig || {} @@ -188,12 +190,28 @@ const RegisterDomainButton = ({ const chainProps = isSub ? {} : { decimals: decimal, currency: symbol } - const isProcessingDomain = processingDomains[domainName] + const disableRegisterButton = processingDomains[domainName] || loadingPrice || price === '0' + + const priceValue = withPrice && ( +
{price ? : <>-}
+ ) return ( - {withPrice &&
{price ? : <>-}
} - { export const useGetDomainPrice = (domain: string) => { const [price, setPrice] = useState() + const [loading, setLoading] = useState(false) useEffect(() => { const getPrice = async () => { - const subsocialRpc = getOrInitSubsocialRpc() - - const { domain: domainPart } = parseDomain(domain) - const price = await subsocialRpc.calculatePrice(domainPart) - - setPrice(price) + setLoading(true) + try { + const subsocialRpc = getOrInitSubsocialRpc() + + const { domain: domainPart } = parseDomain(domain) + const price = await subsocialRpc.calculatePrice(domainPart) + + if (price) { + setPrice(price) + } + setLoading(false) + } catch (err) { + log.error('Failed to get domain price', err) + setLoading(false) + setPrice(undefined) + } } getPrice().catch(err => log.error('Failed to get domain price', err)) }, [domain]) - return price + return { loading, price } } export const useGetPrice = (domainSellerKind: DomainSellerKind, domainPrice?: string) => { diff --git a/src/components/lazy-connection/LazyConnectionContext.tsx b/src/components/lazy-connection/LazyConnectionContext.tsx index b67fa8096..ad3705f87 100644 --- a/src/components/lazy-connection/LazyConnectionContext.tsx +++ b/src/components/lazy-connection/LazyConnectionContext.tsx @@ -40,7 +40,8 @@ export const LazyConnectionsProvider = React.memo((props: React.PropsWithChildre if (api) return api waitMessage.open(`Connecting to ${network || 'the network'}...`) - const node = nodeByNetwork[network] || chainsInfo[network]?.node + const node = + nodeByNetwork[network] || chainsInfo[network]?.wsNode || chainsInfo[network]?.node const provider = new WsProvider(node) api = new ApiPromise({ provider } as any) diff --git a/src/rtk/features/chainsInfo/chainsInfoSlice.ts b/src/rtk/features/chainsInfo/chainsInfoSlice.ts index b4bcbf233..e818705e4 100644 --- a/src/rtk/features/chainsInfo/chainsInfoSlice.ts +++ b/src/rtk/features/chainsInfo/chainsInfoSlice.ts @@ -15,6 +15,7 @@ export type ChainInfo = { icon: string name: string node: string + wsNode?: string paraId: string relayChain?: RelayChain connected?: boolean