import { useEffect, useMemo, useState } from "react"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { ServiceProvider, ServiceConfig, ServiceError, HandleAvailability, ServiceErrorCode, HandleRegisterResponse, CheckRegisterResponse } from "Nip05/ServiceProvider"; import AsyncButton from "Element/AsyncButton"; import LNURLTip from "Element/LNURLTip"; import Copy from "Element/Copy"; import useProfile from "Feed/ProfileFeed"; import useEventPublisher from "Feed/EventPublisher"; import { debounce, hexToBech32 } from "Util"; import { UserMetadata } from "Nostr"; type Nip05ServiceProps = { name: string, service: URL | string, about: JSX.Element, link: string, supportLink: string }; type ReduxStore = any; export default function Nip5Service(props: Nip05ServiceProps) { const navigate = useNavigate(); const pubkey = useSelector(s => s.login.publicKey); const user = useProfile(pubkey); const publisher = useEventPublisher(); const svc = useMemo(() => new ServiceProvider(props.service), [props.service]); const [serviceConfig, setServiceConfig] = useState(); const [error, setError] = useState(); const [handle, setHandle] = useState(""); const [domain, setDomain] = useState(""); const [availabilityResponse, setAvailabilityResponse] = useState(); const [registerResponse, setRegisterResponse] = useState(); const [showInvoice, setShowInvoice] = useState(false); const [registerStatus, setRegisterStatus] = useState(); const domainConfig = useMemo(() => serviceConfig?.domains.find(a => a.name === domain), [domain, serviceConfig]); useEffect(() => { svc.GetConfig() .then(a => { if ('error' in a) { setError(a as ServiceError) } else { let svc = a as ServiceConfig; setServiceConfig(svc); let defaultDomain = svc.domains.find(a => a.default)?.name || svc.domains[0].name; setDomain(defaultDomain); } }) .catch(console.error) }, [props, svc]); useEffect(() => { setError(undefined); setAvailabilityResponse(undefined); if (handle && domain) { if (handle.length < (domainConfig?.length[0] ?? 2)) { setAvailabilityResponse({ available: false, why: "TOO_SHORT" }); return; } if (handle.length > (domainConfig?.length[1] ?? 20)) { setAvailabilityResponse({ available: false, why: "TOO_LONG" }); return; } let rx = new RegExp(domainConfig?.regex[0] ?? "", domainConfig?.regex[1] ?? ""); if (!rx.test(handle)) { setAvailabilityResponse({ available: false, why: "REGEX" }); return; } return debounce(500, () => { svc.CheckAvailable(handle, domain) .then(a => { if ('error' in a) { setError(a as ServiceError); } else { setAvailabilityResponse(a as HandleAvailability); } }) .catch(console.error); }); } }, [handle, domain, domainConfig, svc]); useEffect(() => { if (registerResponse && showInvoice) { let t = setInterval(async () => { let status = await svc.CheckRegistration(registerResponse.token); if ('error' in status) { setError(status); setRegisterResponse(undefined); setShowInvoice(false); } else { let result: CheckRegisterResponse = status; if (result.available && result.paid) { setShowInvoice(false); setRegisterStatus(status); setRegisterResponse(undefined); setError(undefined); } } }, 2_000); return () => clearInterval(t); } }, [registerResponse, showInvoice, svc]) function mapError(e: ServiceErrorCode, t: string | null): string | undefined { let whyMap = new Map([ ["TOO_SHORT", "name too short"], ["TOO_LONG", "name too long"], ["REGEX", "name has disallowed characters"], ["REGISTERED", "name is registered"], ["DISALLOWED_null", "name is blocked"], ["DISALLOWED_later", "name will be available later"], ]); return whyMap.get(e === "DISALLOWED" ? `${e}_${t}` : e); } async function startBuy(handle: string, domain: string) { if (registerResponse) { setShowInvoice(true); return; } let rsp = await svc.RegisterHandle(handle, domain, pubkey); if ('error' in rsp) { setError(rsp); } else { setRegisterResponse(rsp); setShowInvoice(true); } } async function updateProfile(handle: string, domain: string) { if (user) { let newProfile = { ...user, nip05: `${handle}@${domain}` } as UserMetadata; let ev = await publisher.metadata(newProfile); publisher.broadcast(ev); navigate("/settings"); } } return ( <>

{props.name}

{props.about}

Find out more info about {props.name} at {props.link}

{error && {error.error}} {!registerStatus &&
setHandle(e.target.value.toLowerCase())} />  @ 
} {availabilityResponse?.available && !registerStatus &&
{availabilityResponse.quote?.price.toLocaleString()} sats
{availabilityResponse.quote?.data.type}
startBuy(handle, domain)}>Buy Now
} {availabilityResponse?.available === false && !registerStatus &&
Not available: {mapError(availabilityResponse.why!, availabilityResponse.reasonTag || null)}
} setShowInvoice(false)} title={`Buying ${handle}@${domain}`} notice="DO NOT CLOSE THIS POPUP OR YOUR ORDER WILL GET STUCK" /> {registerStatus?.paid &&

Order Paid!

Your new NIP-05 handle is: {handle}@{domain}

Account Support

Please make sure to save the following password in order to manage your handle in the future

Go to account page

Activate Now

updateProfile(handle, domain)}>Add to Profile
} ) }