import { useEffect, useRef, useState } from "react"; import { NDKEvent } from "@nostr-dev-kit/ndk"; import { useUIStore } from "../../stores/ui"; import { useUserStore } from "../../stores/user"; import { useMuteStore } from "../../stores/mute"; import { useProfile, invalidateProfileCache } from "../../hooks/useProfile"; import { fetchUserNotesNIP65, publishProfile, getNDK } from "../../lib/nostr"; import { shortenPubkey } from "../../lib/utils"; import { uploadImage } from "../../lib/upload"; import { NoteCard } from "../feed/NoteCard"; import { ZapModal } from "../zap/ZapModal"; // ── Profile helper sub-components ──────────────────────────────────────────── function ImageField({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) { const [uploading, setUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const fileRef = useRef(null); const handleFile = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setUploading(true); setUploadError(null); try { const url = await uploadImage(file); onChange(url); } catch (err) { setUploadError(String(err)); } finally { setUploading(false); if (fileRef.current) fileRef.current.value = ""; } }; return (
onChange(e.target.value)} placeholder="https://… or click upload →" className="flex-1 bg-bg border border-border px-3 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent/50" style={{ WebkitUserSelect: "text", userSelect: "text" } as React.CSSProperties} />
{uploadError &&

{uploadError}

}
); } type Nip05Status = "idle" | "checking" | "valid" | "mismatch" | "notfound"; function Nip05Field({ value, onChange, pubkey }: { value: string; onChange: (v: string) => void; pubkey: string }) { const [status, setStatus] = useState("idle"); useEffect(() => { if (!value.includes("@")) { setStatus("idle"); return; } setStatus("checking"); const t = setTimeout(async () => { const [name, domain] = value.trim().split("@"); if (!name || !domain) { setStatus("notfound"); return; } try { const resp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(name)}`); const data = await resp.json(); const resolved = data.names?.[name]; if (!resolved) setStatus("notfound"); else if (resolved === pubkey) setStatus("valid"); else setStatus("mismatch"); } catch { setStatus("notfound"); } }, 900); return () => clearTimeout(t); }, [value, pubkey]); const badge = { idle: null, checking: checking…, valid: ✓ verified, mismatch: ✗ pubkey mismatch, notfound: ✗ not found, }[status]; return (
{badge}
onChange(e.target.value)} placeholder="you@domain.com" className="w-full bg-bg border border-border px-3 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent/50" style={{ WebkitUserSelect: "text", userSelect: "text" } as React.CSSProperties} />

Proves your identity via a domain you control.{" "} How to get verified ↗

); } function EditProfileForm({ pubkey, onSaved }: { pubkey: string; onSaved: () => void }) { const { profile, fetchOwnProfile } = useUserStore(); const [name, setName] = useState(profile?.name || ""); const [displayName, setDisplayName] = useState(profile?.displayName || ""); const [about, setAbout] = useState(profile?.about || ""); const [picture, setPicture] = useState(profile?.picture || ""); const [banner, setBanner] = useState(profile?.banner || ""); const [website, setWebsite] = useState(profile?.website || ""); const [nip05, setNip05] = useState(profile?.nip05 || ""); const [lud16, setLud16] = useState(profile?.lud16 || ""); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [saved, setSaved] = useState(false); const handleSave = async () => { setSaving(true); setError(null); try { invalidateProfileCache(pubkey); await publishProfile({ name: name.trim() || undefined, display_name: displayName.trim() || undefined, about: about.trim() || undefined, picture: picture.trim() || undefined, banner: banner.trim() || undefined, website: website.trim() || undefined, nip05: nip05.trim() || undefined, lud16: lud16.trim() || undefined, }); await fetchOwnProfile(); setSaved(true); setTimeout(onSaved, 1000); } catch (err) { setError(`Failed to save: ${err}`); } finally { setSaving(false); } }; const field = (label: string, value: string, onChange: (v: string) => void, placeholder = "") => (
onChange(e.target.value)} placeholder={placeholder} className="w-full bg-bg border border-border px-3 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent/50" style={{ WebkitUserSelect: "text", userSelect: "text" } as React.CSSProperties} />
); return (
{field("Display name", displayName, setDisplayName, "Square that Circle")} {field("Username", name, setName, "squarethecircle")} {field("Lightning address (lud16)", lud16, setLud16, "you@walletofsatoshi.com")} {field("Website", website, setWebsite, "https://…")}