From 5976315595021dd6b9f33ed403681eacafe7c94b Mon Sep 17 00:00:00 2001
From: hzrd149 <github@hzrd149.com>
Date: Tue, 11 Apr 2023 07:51:04 -0500
Subject: [PATCH] added slightly better relay picker

---
 src/components/relay-url-input.tsx | 111 ++++++++++++++++++++++++++---
 src/views/login/nip05.tsx          |   2 +-
 src/views/login/npub.tsx           |   2 +-
 src/views/login/nsec.tsx           |   2 +-
 src/views/relays/index.tsx         |   2 +-
 5 files changed, 105 insertions(+), 14 deletions(-)

diff --git a/src/components/relay-url-input.tsx b/src/components/relay-url-input.tsx
index 5f896fdf4..350f06cc1 100644
--- a/src/components/relay-url-input.tsx
+++ b/src/components/relay-url-input.tsx
@@ -1,10 +1,95 @@
-import { Input, InputProps } from "@chakra-ui/react";
+import {
+  Badge,
+  Box,
+  Button,
+  Flex,
+  Highlight,
+  IconButton,
+  Input,
+  InputGroup,
+  InputLeftElement,
+  InputProps,
+  InputRightElement,
+  Modal,
+  ModalBody,
+  ModalCloseButton,
+  ModalContent,
+  ModalHeader,
+  ModalOverlay,
+  ModalProps,
+  Text,
+  useDisclosure,
+} from "@chakra-ui/react";
+import { useState } from "react";
 import { useAsync } from "react-use";
 import { unique } from "../helpers/array";
+import { RelayIcon, SearchIcon } from "./icons";
+
+function RelayPickerModal({
+  onSelect,
+  onClose,
+  ...props
+}: { onSelect: (relay: string) => void } & Omit<ModalProps, "children">) {
+  const [search, setSearch] = useState("");
+  const { value: onlineRelays } = useAsync(async () =>
+    fetch("https://api.nostr.watch/v1/online").then((res) => res.json() as Promise<string[]>)
+  );
+  const { value: paidRelays } = useAsync(async () =>
+    fetch("https://api.nostr.watch/v1/paid").then((res) => res.json() as Promise<string[]>)
+  );
+  const relayList = unique(onlineRelays ?? []);
+
+  const filteredRelays = search ? relayList.filter((url) => url.includes(search)) : relayList;
+
+  return (
+    <Modal onClose={onClose} {...props}>
+      <ModalOverlay />
+      <ModalContent>
+        <ModalHeader>Pick Relay</ModalHeader>
+        <ModalCloseButton />
+        <ModalBody pt="0" px="4" pb="4">
+          <InputGroup mb="2">
+            <InputLeftElement pointerEvents="none" children={<SearchIcon />} />
+            <Input
+              type="search"
+              placeholder="Search"
+              name="relay-search"
+              value={search}
+              onChange={(e) => setSearch(e.target.value)}
+            />
+          </InputGroup>
+          <Flex gap="2" direction="column">
+            {filteredRelays.map((url) => (
+              <Flex gap="2" alignItems="center">
+                <Button
+                  key={url}
+                  value={url}
+                  onClick={() => {
+                    onSelect(url);
+                    onClose();
+                  }}
+                  variant="outline"
+                  size="sm"
+                >
+                  {url}
+                </Button>
+                {paidRelays?.includes(url) && <Badge colorScheme="green">Paid</Badge>}
+              </Flex>
+            ))}
+          </Flex>
+        </ModalBody>
+      </ModalContent>
+    </Modal>
+  );
+}
 
 export type RelayUrlInputProps = Omit<InputProps, "type">;
 
-export const RelayUrlInput = ({ ...props }: RelayUrlInputProps) => {
+export const RelayUrlInput = ({
+  onChange,
+  ...props
+}: Omit<RelayUrlInputProps, "onChange"> & { onChange: (url: string) => void }) => {
+  const { isOpen, onClose, onOpen } = useDisclosure();
   const { value: relaysJson } = useAsync(async () =>
     fetch("https://api.nostr.watch/v1/online").then((res) => res.json() as Promise<string[]>)
   );
@@ -12,14 +97,20 @@ export const RelayUrlInput = ({ ...props }: RelayUrlInputProps) => {
 
   return (
     <>
-      <Input list="relay-suggestions" type="url" {...props} />
-      <datalist id="relay-suggestions">
-        {relaySuggestions.map((url) => (
-          <option key={url} value={url}>
-            {url}
-          </option>
-        ))}
-      </datalist>
+      <InputGroup>
+        <Input list="relay-suggestions" type="url" onChange={(e) => onChange(e.target.value)} {...props} />
+        <datalist id="relay-suggestions">
+          {relaySuggestions.map((url) => (
+            <option key={url} value={url}>
+              {url}
+            </option>
+          ))}
+        </datalist>
+        <InputRightElement>
+          <IconButton icon={<RelayIcon />} aria-label="Pick from list" size="sm" onClick={onOpen} />
+        </InputRightElement>
+      </InputGroup>
+      <RelayPickerModal onClose={onClose} isOpen={isOpen} onSelect={(url) => onChange(url)} size="2xl" />
     </>
   );
 };
diff --git a/src/views/login/nip05.tsx b/src/views/login/nip05.tsx
index 8e51a9036..92f2d4004 100644
--- a/src/views/login/nip05.tsx
+++ b/src/views/login/nip05.tsx
@@ -121,7 +121,7 @@ export default function LoginNip05View() {
               placeholder="wss://nostr.example.com"
               isRequired
               value={relayUrl}
-              onChange={(e) => setRelayUrl(e.target.value)}
+              onChange={(url) => setRelayUrl(url)}
             />
             <FormHelperText>The first relay to connect to.</FormHelperText>
           </FormControl>
diff --git a/src/views/login/npub.tsx b/src/views/login/npub.tsx
index ad511e256..8677d9b99 100644
--- a/src/views/login/npub.tsx
+++ b/src/views/login/npub.tsx
@@ -46,7 +46,7 @@ export default function LoginNpubView() {
           placeholder="wss://nostr.example.com"
           isRequired
           value={relayUrl}
-          onChange={(e) => setRelayUrl(e.target.value)}
+          onChange={(url) => setRelayUrl(url)}
         />
         <FormHelperText>The first relay to connect to.</FormHelperText>
       </FormControl>
diff --git a/src/views/login/nsec.tsx b/src/views/login/nsec.tsx
index fd4df736b..c9ea0415e 100644
--- a/src/views/login/nsec.tsx
+++ b/src/views/login/nsec.tsx
@@ -137,7 +137,7 @@ export default function LoginNsecView() {
           placeholder="wss://nostr.example.com"
           isRequired
           value={relayUrl}
-          onChange={(e) => setRelayUrl(e.target.value)}
+          onChange={(url) => setRelayUrl(url)}
         />
         <FormHelperText>The first relay to connect to.</FormHelperText>
       </FormControl>
diff --git a/src/views/relays/index.tsx b/src/views/relays/index.tsx
index 68653b6f1..0292934fc 100644
--- a/src/views/relays/index.tsx
+++ b/src/views/relays/index.tsx
@@ -134,7 +134,7 @@ export default function RelaysView() {
             <RelayUrlInput
               id="relay-url-input"
               value={relayInputValue}
-              onChange={(e) => setRelayInputValue(e.target.value)}
+              onChange={(url) => setRelayInputValue(url)}
               isRequired
             />
             <Button type="submit" isDisabled={saving}>