diff --git a/src/components/shared/SettingsView.tsx b/src/components/shared/SettingsView.tsx index 5c20725..7a99e7b 100644 --- a/src/components/shared/SettingsView.tsx +++ b/src/components/shared/SettingsView.tsx @@ -1,3 +1,126 @@ +import { useState } from "react"; +import { useUserStore } from "../../stores/user"; +import { getNDK, getStoredRelayUrls, addRelay, removeRelay } from "../../lib/nostr"; + +function RelayRow({ url, onRemove }: { url: string; onRemove: () => void }) { + const ndk = getNDK(); + const relay = ndk.pool?.relays.get(url); + const connected = relay?.connected ?? false; + + return ( +
+ + {url} + +
+ ); +} + +function RelaySection() { + const [relays, setRelays] = useState(() => getStoredRelayUrls()); + const [input, setInput] = useState(""); + const [error, setError] = useState(null); + + const handleAdd = () => { + const url = input.trim(); + if (!url) return; + if (!url.startsWith("ws://") && !url.startsWith("wss://")) { + setError("URL must start with ws:// or wss://"); + return; + } + if (relays.includes(url)) { + setError("Already in list"); + return; + } + addRelay(url); + setRelays(getStoredRelayUrls()); + setInput(""); + setError(null); + }; + + const handleRemove = (url: string) => { + removeRelay(url); + setRelays(getStoredRelayUrls()); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") handleAdd(); + if (e.key === "Escape") setInput(""); + }; + + return ( +
+

Relays

+
+ {relays.length === 0 && ( +

No relays configured.

+ )} + {relays.map((url) => ( + handleRemove(url)} /> + ))} +
+
+ { setInput(e.target.value); setError(null); }} + onKeyDown={handleKeyDown} + placeholder="wss://relay.example.com" + className="flex-1 bg-bg border border-border px-3 py-1.5 text-text text-[12px] font-mono focus:outline-none focus:border-accent/50 placeholder:text-text-dim" + /> + +
+ {error &&

{error}

} +
+ ); +} + +function IdentitySection() { + const { npub, loggedIn } = useUserStore(); + const [copied, setCopied] = useState(false); + + if (!loggedIn || !npub) { + return ( +
+

Identity

+

Not logged in.

+
+ ); + } + + const handleCopy = () => { + navigator.clipboard.writeText(npub).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; + + return ( +
+

Identity

+
+ {npub} + +
+

Your public key. Safe to share.

+
+ ); +} + export function SettingsView() { return (
@@ -5,10 +128,9 @@ export function SettingsView() {

Settings

-
-

- Settings will appear here — key management, relay config, Lightning wallet connection, appearance. -

+
+ +
); diff --git a/src/lib/nostr/client.ts b/src/lib/nostr/client.ts index 9894508..5071c61 100644 --- a/src/lib/nostr/client.ts +++ b/src/lib/nostr/client.ts @@ -1,23 +1,58 @@ -import NDK, { NDKEvent, NDKFilter, NDKKind, NDKSubscriptionCacheUsage } from "@nostr-dev-kit/ndk"; +import NDK, { NDKEvent, NDKFilter, NDKKind, NDKRelay, NDKSubscriptionCacheUsage } from "@nostr-dev-kit/ndk"; -const DEFAULT_RELAYS = [ - "ws://umbrel.local:4848", +const RELAY_STORAGE_KEY = "wrystr_relays"; + +const FALLBACK_RELAYS = [ "wss://relay.damus.io", "wss://nos.lol", "wss://relay.snort.social", ]; +export function getStoredRelayUrls(): string[] { + try { + const stored = localStorage.getItem(RELAY_STORAGE_KEY); + if (stored) return JSON.parse(stored); + } catch { /* ignore */ } + return FALLBACK_RELAYS; +} + +function saveRelayUrls(urls: string[]) { + localStorage.setItem(RELAY_STORAGE_KEY, JSON.stringify(urls)); +} + let ndk: NDK | null = null; export function getNDK(): NDK { if (!ndk) { ndk = new NDK({ - explicitRelayUrls: DEFAULT_RELAYS, + explicitRelayUrls: getStoredRelayUrls(), }); } return ndk; } +export function addRelay(url: string): void { + const instance = getNDK(); + const urls = getStoredRelayUrls(); + if (!urls.includes(url)) { + saveRelayUrls([...urls, url]); + } + if (!instance.pool?.relays.has(url)) { + const relay = new NDKRelay(url, undefined, instance); + instance.pool?.addRelay(relay, true); + } +} + +export function removeRelay(url: string): void { + const instance = getNDK(); + const relay = instance.pool?.relays.get(url); + if (relay) { + relay.disconnect(); + instance.pool?.relays.delete(url); + } + saveRelayUrls(getStoredRelayUrls().filter((u) => u !== url)); +} + function waitForConnectedRelay(instance: NDK, timeoutMs = 10000): Promise { return new Promise((resolve, _reject) => { const timer = setTimeout(() => { diff --git a/src/lib/nostr/index.ts b/src/lib/nostr/index.ts index 3a9b0e6..cf3c444 100644 --- a/src/lib/nostr/index.ts +++ b/src/lib/nostr/index.ts @@ -1 +1 @@ -export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishProfile, publishReaction, publishReply, publishContactList, fetchReactionCount, fetchUserNotes, fetchProfile } from "./client"; +export { getNDK, connectToRelays, fetchGlobalFeed, fetchFollowFeed, fetchReplies, publishNote, publishArticle, publishProfile, publishReaction, publishReply, publishContactList, fetchReactionCount, fetchUserNotes, fetchProfile, getStoredRelayUrls, addRelay, removeRelay } from "./client";