chore: register domain

This commit is contained in:
Fernando Falci
2024-04-22 11:42:54 +02:00
parent eb210480bc
commit ac6095e2b2
20 changed files with 658 additions and 38 deletions

View File

@@ -0,0 +1,78 @@
import { useState } from 'react';
import { useDebounce } from '@uidotdev/usehooks';
import { SearchInput } from './SearchInput';
import { SearchStatus } from './SearchStatus';
import { SearchCTA } from './SearchCTA';
import { SearchPrice } from './SearchPrice';
import { DEV_MODE, TLD } from '../../constants';
import { useDomainStatus } from '../../hooks/useDomainStatus';
export const Search = () => {
// "term" is the value from the input (first part if it contains a ".")
const [term, setTerm] = useState('');
// label is the same as term, but delayed 300ms (while the user is typing)
const label = useDebounce(term, 300);
// data contains all the info about the domain
const { data, failureReason } = useDomainStatus({ label });
const error = failureReason?.cause?.shortMessage;
// only _show_ the second line if we have a label
const show = !!label;
// if there's a "." (like "foo.bar"), we only use the first part (foo)
const onChange = (term) => setTerm(term.split('.').at(0));
// ensure the current data is related to the final search term
const safeData = term === data?.label ? data : undefined;
return (
<>
<div className="w-full bg-white rounded-2xl border border-zinc-300 shadow">
<SearchInput expand={show} onChange={onChange} />
{show && (
<div className="w-full px-5 pt-3 pb-4 bg-white flex-col justify-start items-start gap-4 inline-flex rounded-b-2xl">
<div className="self-stretch py-1 justify-between items-center inline-flex">
<div className="rounded justify-start items-center gap-2 flex">
<div>
<span className="text-neutral-950 text-xl font-medium leading-loose">
{label}
</span>
<span className="text-neutral-400 text-xl font-medium leading-loose">
.{TLD}
</span>
</div>
<SearchStatus details={safeData} />
</div>
<div className="justify-start items-center gap-4 flex">
<SearchPrice details={safeData} />
<SearchCTA details={safeData} />
</div>
</div>
</div>
)}
</div>
{DEV_MODE && (
<div className="flex flex-col gap-2">
<span className="text-red-600 font-semibold bg-yellow-300 px-4 py-2 rounded-md">
TESTNET
</span>
{error && (
<div className="p-2 text-red-600 bg-red-100 rounded-md">
{error}
</div>
)}
{data && (
<pre className="p-2 text-gray-500 bg-gray-100 border border-gray-500 rounded-lg">
{JSON.stringify(data, null, 2)}
</pre>
)}
</div>
)}
</>
);
};

View File

@@ -0,0 +1,49 @@
import { ConnectButton } from '@rainbow-me/rainbowkit';
import Skeleton from 'react-loading-skeleton';
import { useAccount } from 'wagmi';
import { TLD } from '../../constants';
import { domainDetails } from '../../types';
import { RegisterButton } from '../RegisterButton';
export const SearchCTA = ({ details }) => {
const { address, isConnected } = useAccount();
if (!details) {
return <Skeleton className="w-24 h-7" />;
}
if (isConnected && details.owner === address) {
const url = `https://hns.id/domain/${details.label}.${TLD}`;
return (
<a
className=" px-3 py-2 bg-white rounded-full border border-blue-600 justify-center items-center gap-2.5 inline-flex"
href={url}
>
<div className="text-blue-600 text-sm font-semibold leading-none">
Manage Domain
</div>
</a>
);
}
const canRegister =
details.publicRegistrationOpen &&
details.priceInWei > 0n &&
details.labelValid &&
details.isAvailable &&
(!details.reservedAddress || details.reservedAddress === address);
if (!canRegister) {
return null;
}
if (!isConnected) {
return <ConnectButton />;
}
return <RegisterButton details={details} />;
};
SearchCTA.propTypes = {
details: domainDetails,
};

View File

@@ -0,0 +1,34 @@
import PropTypes from 'prop-types';
import cn from 'classnames';
import searchIcon from '../../assets/search.png';
import { SEARCH_PLACEHOLDER } from '../../constants';
export const SearchInput = ({ expand, onChange }) => {
const handleChange = (e) => onChange(e.target.value);
return (
<div
className={cn(
'w-full justify-between items-center inline-flex relative',
expand ? 'rounded-t-2xl' : 'rounded-2xl'
)}
>
<input
className={cn(
'grow shrink basis-0 rounded-t-2xl text-neutral-400 text-base font-medium leading-tight tracking-tight p-5 border-zinc-300 focus:outline-none',
expand ? 'rounded-t-2xl border-b' : 'rounded-2xl'
)}
onChange={handleChange}
placeholder={SEARCH_PLACEHOLDER}
/>
<div className="w-5 h-5 absolute right-4">
<img src={searchIcon} />
</div>
</div>
);
};
SearchInput.propTypes = {
expand: PropTypes.bool,
onChange: PropTypes.func,
};

View File

@@ -0,0 +1,27 @@
import Skeleton from 'react-loading-skeleton';
import { domainDetails } from '../../types';
export const SearchPrice = ({ details }) => {
if (!details) {
return <Skeleton className="w-24 h-7" />;
}
if (!details.isAvailable || details.priceInWei === 0n) {
return null;
}
return (
<div className="flex h-5 items-center gap-1">
<span className="text-neutral-900 text-sm font-bold leading-normal">
${details.priceInDollars}
</span>
<span className="text-neutral-400 text-xs font-bold leading-none">
/ year
</span>
</div>
);
};
SearchPrice.propTypes = {
details: domainDetails,
};

View File

@@ -0,0 +1,51 @@
import { useAccount } from 'wagmi';
import Skeleton from 'react-loading-skeleton';
import { Badge } from '../Badge';
import { domainDetails } from '../../types';
import checkMark from '../../assets/check-mark.png';
export const SearchStatus = ({ details }) => {
const { address, isConnected } = useAccount();
if (!details) {
return <Skeleton className="w-20 h-7" />;
}
if (isConnected && details.owner === address) {
return <img src={checkMark} className="w-6" />;
}
if (!details.isAvailable) {
return <Badge variant="taken">Taken</Badge>;
}
if (!details.labelValid) {
return <Badge variant="taken">Invalid Domain</Badge>;
}
if (!details.publicRegistrationOpen) {
return <Badge variant="taken">Coming Soon</Badge>;
}
if (details.reservedAddress && details.reservedAddress !== address) {
return (
<Badge
variant="premium"
title={`Reserved for: ${details.reservedAddress}`}
>
Reserved
</Badge>
);
}
if (details.isPremium) {
return <Badge variant="premium">Premium</Badge>;
}
return <Badge variant="success">Available</Badge>;
};
SearchStatus.propTypes = {
details: domainDetails,
};