mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-20 13:01:07 +02:00
add nip05 input to profile edit form
This commit is contained in:
@@ -34,28 +34,31 @@ async function fetchAllIdentities(domain: string) {
|
||||
async function fetchIdentity(address: string) {
|
||||
const { name, domain } = parseAddress(address);
|
||||
if (!name || !domain) throw new Error("invalid address");
|
||||
const json = await fetch(`https://${domain}/.well-known/nostr.json?name=${name}`)
|
||||
.then((res) => res.json() as Promise<IdentityJson>)
|
||||
.then((json) => {
|
||||
// convert all keys in names, and relays to lower case
|
||||
if (json.names) {
|
||||
for (const [name, pubkey] of Object.entries(json.names)) {
|
||||
delete json.names[name];
|
||||
json.names[name.toLowerCase()] = pubkey;
|
||||
}
|
||||
}
|
||||
if (json.relays) {
|
||||
for (const [name, pubkey] of Object.entries(json.relays)) {
|
||||
delete json.relays[name];
|
||||
json.relays[name.toLowerCase()] = pubkey;
|
||||
}
|
||||
}
|
||||
return json;
|
||||
});
|
||||
|
||||
await addToCache(domain, json);
|
||||
try {
|
||||
const json = await fetch(`https://${domain}/.well-known/nostr.json?name=${name}`)
|
||||
.then((res) => res.json() as Promise<IdentityJson>)
|
||||
.then((json) => {
|
||||
// convert all keys in names, and relays to lower case
|
||||
if (json.names) {
|
||||
for (const [name, pubkey] of Object.entries(json.names)) {
|
||||
delete json.names[name];
|
||||
json.names[name.toLowerCase()] = pubkey;
|
||||
}
|
||||
}
|
||||
if (json.relays) {
|
||||
for (const [name, pubkey] of Object.entries(json.relays)) {
|
||||
delete json.relays[name];
|
||||
json.relays[name.toLowerCase()] = pubkey;
|
||||
}
|
||||
}
|
||||
return json;
|
||||
});
|
||||
|
||||
return getIdentityFromJson(name, domain, json);
|
||||
await addToCache(domain, json);
|
||||
|
||||
return getIdentityFromJson(name, domain, json);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function addToCache(domain: string, json: IdentityJson) {
|
||||
@@ -75,7 +78,6 @@ async function getIdentity(address: string, alwaysFetch = false) {
|
||||
const cached = await db.get("dnsIdentifiers", address);
|
||||
if (cached && !alwaysFetch) return cached;
|
||||
|
||||
// TODO: if it fails, maybe cache a failure message
|
||||
return fetchIdentity(address);
|
||||
}
|
||||
|
||||
@@ -94,7 +96,10 @@ async function pruneCache() {
|
||||
const pending: Record<string, ReturnType<typeof getIdentity> | undefined> = {};
|
||||
function dedupedGetIdentity(address: string, alwaysFetch = false) {
|
||||
if (pending[address]) return pending[address];
|
||||
return (pending[address] = getIdentity(address, alwaysFetch));
|
||||
return (pending[address] = getIdentity(address, alwaysFetch).then((v) => {
|
||||
delete pending[address];
|
||||
return v;
|
||||
}));
|
||||
}
|
||||
|
||||
export const dnsIdentityService = {
|
||||
|
@@ -41,7 +41,10 @@ const pending: Record<string, ReturnType<typeof getInfo> | undefined> = {};
|
||||
function dedupedGetIdentity(relay: string) {
|
||||
const request = pending[relay];
|
||||
if (request) return request;
|
||||
return (pending[relay] = getInfo(relay));
|
||||
return (pending[relay] = getInfo(relay).then((v) => {
|
||||
delete pending[relay];
|
||||
return v;
|
||||
}));
|
||||
}
|
||||
|
||||
export const relayInfoService = {
|
||||
|
@@ -1,4 +1,14 @@
|
||||
import { Avatar, Button, Flex, FormControl, FormLabel, Input, Textarea, useToast } from "@chakra-ui/react";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
FormLabel,
|
||||
Input,
|
||||
Textarea,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -9,6 +19,7 @@ import { useReadRelayUrls, useWriteRelayUrls } from "../../hooks/use-client-rela
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import { useIsMobile } from "../../hooks/use-is-mobile";
|
||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||
import dnsIdentityService from "../../services/dns-identity";
|
||||
import signingService from "../../services/signing";
|
||||
import userMetadataService from "../../services/user-metadata";
|
||||
import { DraftNostrEvent } from "../../types/nostr-event";
|
||||
@@ -26,6 +37,7 @@ type FormData = {
|
||||
picture?: string;
|
||||
about?: string;
|
||||
website?: string;
|
||||
nip05?: string;
|
||||
lightningAddress?: string;
|
||||
};
|
||||
|
||||
@@ -35,12 +47,13 @@ type MetadataFormProps = {
|
||||
};
|
||||
|
||||
const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
|
||||
const account = useCurrentAccount();
|
||||
const isMobile = useIsMobile();
|
||||
const {
|
||||
register,
|
||||
reset,
|
||||
handleSubmit,
|
||||
getValues,
|
||||
watch,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<FormData>({
|
||||
mode: "onBlur",
|
||||
@@ -56,61 +69,89 @@ const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Flex direction="column" gap="2" pt="4">
|
||||
<Flex gap="2">
|
||||
<FormControl>
|
||||
<FormControl isInvalid={!!errors.displayName}>
|
||||
<FormLabel>Display Name</FormLabel>
|
||||
<Input autoComplete="off" isDisabled={isSubmitting} {...register("displayName", { maxLength: 50 })} />
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("displayName", {
|
||||
minLength: 2,
|
||||
maxLength: 64,
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.displayName?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormControl isInvalid={!!errors.username} isRequired>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isRequired
|
||||
isDisabled={isSubmitting}
|
||||
isInvalid={!!errors.username}
|
||||
{...register("username", {
|
||||
minLength: 2,
|
||||
maxLength: 256,
|
||||
maxLength: 64,
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z0-9_-]{4,16}$/,
|
||||
})}
|
||||
/>
|
||||
{errors.username?.message}
|
||||
<FormErrorMessage>{errors.username?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
<Flex gap="2" alignItems="center">
|
||||
<FormControl>
|
||||
<FormControl isInvalid={!!errors.picture}>
|
||||
<FormLabel>Picture</FormLabel>
|
||||
<Input autoComplete="off" isDisabled={isSubmitting} {...register("picture", { maxLength: 150 })} />
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isDisabled={isSubmitting}
|
||||
placeholder="https://domain.com/path/picture.png"
|
||||
{...register("picture", { maxLength: 150 })}
|
||||
/>
|
||||
</FormControl>
|
||||
<Avatar src={getValues("picture")} size="md" ignoreFallback />
|
||||
<Avatar src={watch("picture")} size="lg" ignoreFallback />
|
||||
</Flex>
|
||||
<FormControl>
|
||||
<FormControl isInvalid={!!errors.nip05}>
|
||||
<FormLabel>NIP-05 ID</FormLabel>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="user@domain.com"
|
||||
isDisabled={isSubmitting}
|
||||
{...register("nip05", {
|
||||
minLength: 5,
|
||||
validate: async (address) => {
|
||||
if (!address) return true;
|
||||
if (!address.includes("@")) return "invalid address";
|
||||
const id = await dnsIdentityService.getIdentity(address);
|
||||
if (!id) return "cant find NIP-05 ID";
|
||||
if (id.pubkey !== account.pubkey) return "Pubkey dose not match";
|
||||
return true;
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<FormErrorMessage>{errors.nip05?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={!!errors.website}>
|
||||
<FormLabel>Website</FormLabel>
|
||||
<Input
|
||||
type="url"
|
||||
autoComplete="off"
|
||||
placeholder="https://example.com"
|
||||
isDisabled={isSubmitting}
|
||||
isInvalid={!!errors.website}
|
||||
{...register("website", { maxLength: 300 })}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormControl isInvalid={!!errors.about}>
|
||||
<FormLabel>About</FormLabel>
|
||||
<Textarea
|
||||
placeholder="A short description"
|
||||
resize="vertical"
|
||||
rows={6}
|
||||
isDisabled={isSubmitting}
|
||||
isInvalid={!!errors.about}
|
||||
{...register("about")}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormControl isInvalid={!!errors.lightningAddress}>
|
||||
<FormLabel>Lightning Address (or LNURL)</FormLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
isInvalid={!!errors.lightningAddress}
|
||||
isDisabled={isSubmitting}
|
||||
{...register("lightningAddress", {
|
||||
validate: (v) => {
|
||||
@@ -121,7 +162,7 @@ const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{/* <FormHelperText>Don't forget the https://</FormHelperText> */}
|
||||
<FormErrorMessage>{errors.lightningAddress?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Flex alignSelf="flex-end" gap="2">
|
||||
<Button onClick={() => reset()}>Reset</Button>
|
||||
@@ -149,6 +190,7 @@ export const ProfileEditView = () => {
|
||||
picture: metadata?.picture,
|
||||
about: metadata?.about,
|
||||
website: metadata?.website,
|
||||
nip05: metadata?.nip05,
|
||||
lightningAddress: metadata?.lud16 || metadata?.lud06,
|
||||
}),
|
||||
[metadata]
|
||||
@@ -163,6 +205,7 @@ export const ProfileEditView = () => {
|
||||
if (data.displayName) metadata.display_name = data.displayName;
|
||||
if (data.about) metadata.about = data.about;
|
||||
if (data.website) metadata.website = data.website;
|
||||
if (data.nip05) metadata.nip05 = data.nip05;
|
||||
|
||||
if (data.lightningAddress) {
|
||||
if (isLNURL(data.lightningAddress)) {
|
||||
|
Reference in New Issue
Block a user