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