chore: rename Dirs
This commit is contained in:
193
src/Element/Nip5Service.tsx
Normal file
193
src/Element/Nip5Service.tsx
Normal file
@ -0,0 +1,193 @@
|
||||
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 { 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<ReduxStore, string>(s => s.login.publicKey);
|
||||
const user = useProfile(pubkey);
|
||||
const publisher = useEventPublisher();
|
||||
const svc = new ServiceProvider(props.service);
|
||||
const [serviceConfig, setServiceConfig] = useState<ServiceConfig>();
|
||||
const [error, setError] = useState<ServiceError>();
|
||||
const [handle, setHandle] = useState<string>("");
|
||||
const [domain, setDomain] = useState<string>("");
|
||||
const [availabilityResponse, setAvailabilityResponse] = useState<HandleAvailability>();
|
||||
const [registerResponse, setRegisterResponse] = useState<HandleRegisterResponse>();
|
||||
const [showInvoice, setShowInvoice] = useState<boolean>(false);
|
||||
const [registerStatus, setRegisterStatus] = useState<CheckRegisterResponse>();
|
||||
|
||||
const domainConfig = useMemo(() => serviceConfig?.domains.find(a => a.name === domain), [domain]);
|
||||
|
||||
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]);
|
||||
|
||||
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;
|
||||
}
|
||||
let t = setTimeout(() => {
|
||||
svc.CheckAvailable(handle, domain)
|
||||
.then(a => {
|
||||
if ('error' in a) {
|
||||
setError(a as ServiceError);
|
||||
} else {
|
||||
setAvailabilityResponse(a as HandleAvailability);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}, 500);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
}, [handle, domain]);
|
||||
|
||||
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])
|
||||
|
||||
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 (
|
||||
<>
|
||||
<h3>{props.name}</h3>
|
||||
{props.about}
|
||||
<p>Find out more info about {props.name} at <a href={props.link} target="_blank" rel="noreferrer">{props.link}</a></p>
|
||||
{error && <b className="error">{error.error}</b>}
|
||||
{!registerStatus && <div className="flex mb10">
|
||||
<input type="text" placeholder="Handle" value={handle} onChange={(e) => setHandle(e.target.value)} />
|
||||
@
|
||||
<select value={domain} onChange={(e) => setDomain(e.target.value)}>
|
||||
{serviceConfig?.domains.map(a => <option key={a.name}>{a.name}</option>)}
|
||||
</select>
|
||||
</div>}
|
||||
{availabilityResponse?.available && !registerStatus && <div className="flex">
|
||||
<div className="mr10">
|
||||
{availabilityResponse.quote?.price.toLocaleString()} sats<br />
|
||||
<small>{availabilityResponse.quote?.data.type}</small>
|
||||
</div>
|
||||
<input type="text" className="f-grow mr10" placeholder="pubkey" value={hexToBech32("npub", pubkey)} disabled />
|
||||
<AsyncButton onClick={() => startBuy(handle, domain)}>Buy Now</AsyncButton>
|
||||
</div>}
|
||||
{availabilityResponse?.available === false && !registerStatus && <div className="flex">
|
||||
<b className="error">Not available: {mapError(availabilityResponse.why!, availabilityResponse.reasonTag || null)}</b>
|
||||
</div>}
|
||||
<LNURLTip invoice={registerResponse?.invoice} show={showInvoice} onClose={() => setShowInvoice(false)} title={`Buying ${handle}@${domain}`} />
|
||||
{registerStatus?.paid && <div className="flex f-col">
|
||||
<h4>Order Paid!</h4>
|
||||
<p>Your new NIP-05 handle is: <code>{handle}@{domain}</code></p>
|
||||
<h3>Account Support</h3>
|
||||
<p>Please make sure to save the following password in order to manage your handle in the future</p>
|
||||
<Copy text={registerStatus.password} />
|
||||
<p>Go to <a href={props.supportLink} target="_blank" rel="noreferrer">account page</a></p>
|
||||
<h4>Activate Now</h4>
|
||||
<AsyncButton onClick={() => updateProfile(handle, domain)}>Add to Profile</AsyncButton>
|
||||
</div>}
|
||||
</>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user