diff --git a/src/app.tsx b/src/app.tsx index b99127cf..bcec589e 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -117,16 +117,14 @@ export default function App() { const { RelaysScreen } = await import('@app/relays'); return { Component: RelaysScreen }; }, - children: [ - { - path: ':url', - loader: relayLoader, - async lazy() { - const { RelayScreen } = await import('@app/relays/relay'); - return { Component: RelayScreen }; - }, - }, - ], + }, + { + path: 'relays/:url', + loader: relayLoader, + async lazy() { + const { RelayScreen } = await import('@app/relays/relay'); + return { Component: RelayScreen }; + }, }, ], }, diff --git a/src/app/relays/components/relayEventList.tsx b/src/app/relays/components/relayEventList.tsx new file mode 100644 index 00000000..f373ebf4 --- /dev/null +++ b/src/app/relays/components/relayEventList.tsx @@ -0,0 +1,29 @@ +import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; +import { useQuery } from '@tanstack/react-query'; + +import { useNDK } from '@libs/ndk/provider'; + +export function RelayEventList({ relayUrl }: { relayUrl: string }) { + const { fetcher } = useNDK(); + const { status, data } = useQuery( + ['relay-event'], + async () => { + const events = await fetcher.fetchLatestEvents( + [relayUrl], + { + kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article], + }, + 100 + ); + + return events as unknown as NDKEvent[]; + }, + { refetchOnWindowFocus: false } + ); + + return ( +
+

TODO

+
+ ); +} diff --git a/src/app/relays/components/relayForm.tsx b/src/app/relays/components/relayForm.tsx new file mode 100644 index 00000000..6796d286 --- /dev/null +++ b/src/app/relays/components/relayForm.tsx @@ -0,0 +1,66 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { useState } from 'react'; + +import { useStorage } from '@libs/storage/provider'; + +import { PlusIcon } from '@shared/icons'; + +const domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/; + +export function RelayForm() { + const { db } = useStorage(); + const queryClient = useQueryClient(); + + const [url, setUrl] = useState(''); + const [error, setError] = useState(''); + + const createRelay = async () => { + if (url.length < 1) return setError('Please enter relay url'); + try { + const relay = new URL(url.replace(/\s/g, '')); + if ( + domainRegex.test(relay.host) && + (relay.protocol === 'wss:' || relay.protocol === 'ws:') + ) { + const res = await db.createRelay(url); + if (!res) return setError("You're already using this relay"); + + queryClient.invalidateQueries(['user-relay']); + setError(''); + setUrl(''); + } else { + return setError( + 'URL is invalid, a relay must use websocket protocol (start with wss:// or ws://). Please check again' + ); + } + } catch { + return setError('Relay URL is not valid. Please check again'); + } + }; + + return ( +
+
+ setUrl(e.target.value)} + /> + +
+ {error} +
+ ); +} diff --git a/src/app/relays/components/relayList.tsx b/src/app/relays/components/relayList.tsx index 08084521..e59d9d96 100644 --- a/src/app/relays/components/relayList.tsx +++ b/src/app/relays/components/relayList.tsx @@ -1,7 +1,11 @@ -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { message } from '@tauri-apps/api/dialog'; +import { normalizeRelayUrl } from 'nostr-fetch'; import { useNavigate } from 'react-router-dom'; import { VList } from 'virtua'; +import { useStorage } from '@libs/storage/provider'; + import { LoaderIcon, PlusIcon, ShareIcon } from '@shared/icons'; import { User } from '@shared/user'; @@ -9,21 +13,31 @@ import { useNostr } from '@utils/hooks/useNostr'; export function RelayList() { const navigate = useNavigate(); + const queryClient = useQueryClient(); const { getAllRelaysByUsers } = useNostr(); + const { db } = useStorage(); const { status, data } = useQuery( ['relays'], async () => { return await getAllRelaysByUsers(); }, - { refetchOnMount: false } + { refetchOnWindowFocus: false } ); - const openRelay = (relayUrl: string) => { + const inspectRelay = (relayUrl: string) => { const url = new URL(relayUrl); navigate(`/relays/${url.hostname}`); }; + const connectRelay = async (relayUrl: string) => { + const url = normalizeRelayUrl(relayUrl); + const res = await db.createRelay(url); + + if (!res) await message("You're aldready connected to this relay"); + queryClient.invalidateQueries(['user-relay']); + }; + return (
{status === 'loading' ? ( @@ -49,7 +63,7 @@ export function RelayList() {
))} +
)} diff --git a/src/app/relays/relay.tsx b/src/app/relays/relay.tsx index 210423b3..4940ff8d 100644 --- a/src/app/relays/relay.tsx +++ b/src/app/relays/relay.tsx @@ -1,19 +1,61 @@ import { Suspense } from 'react'; import { Await, useLoaderData } from 'react-router-dom'; +import { LoaderIcon } from '@shared/icons'; + export function RelayScreen() { const data: { relay?: { [key: string]: string } } = useLoaderData(); return ( -
- Loading...

}> - Could not load relay information 😬
} - > - {(resolvedRelay) =>

{JSON.stringify(resolvedRelay)}

} - - +
+
+
+
+

Information

+
+
+ + + Loading... +
+ } + > + +

Could not load relay information 😬

+
+ } + > + {(resolvedRelay) => ( +
+

+ Name : {resolvedRelay.name} +

+

+ Description :{' '} + {resolvedRelay.description} +

+

+ Contact :{' '} + {resolvedRelay.contact} +

+

+ Software : [open website] +

+

+ Version :{' '} + {resolvedRelay.version} +

+
+ )} + + +
+ ); } diff --git a/src/libs/storage/instance.ts b/src/libs/storage/instance.ts index 45f170c8..f6071ec0 100644 --- a/src/libs/storage/instance.ts +++ b/src/libs/storage/instance.ts @@ -310,6 +310,13 @@ export class LumeStorage { } public async createRelay(relay: string, purpose?: string) { + const existRelays: Relays[] = await this.db.select( + 'SELECT * FROM relays WHERE relay = $1 AND account_id = $2 ORDER BY id DESC LIMIT 1;', + [relay, this.account.id] + ); + + if (existRelays.length > 0) return false; + return await this.db.execute( 'INSERT OR IGNORE INTO relays (account_id, relay, purpose) VALUES ($1, $2, $3);', [this.account.id, relay, purpose || ''] diff --git a/src/shared/icons/index.ts b/src/shared/icons/index.ts index eb6bf2ca..d0dd3ebc 100644 --- a/src/shared/icons/index.ts +++ b/src/shared/icons/index.ts @@ -67,3 +67,4 @@ export * from './nwc'; export * from './timeline'; export * from './dots'; export * from './handArrowDown'; +export * from './relay'; diff --git a/src/shared/icons/relay.tsx b/src/shared/icons/relay.tsx new file mode 100644 index 00000000..3c30c9c6 --- /dev/null +++ b/src/shared/icons/relay.tsx @@ -0,0 +1,28 @@ +import { SVGProps } from 'react'; + +export function RelayIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + ); +} diff --git a/src/shared/navigation.tsx b/src/shared/navigation.tsx index 14fe54cf..5e1f29d7 100644 --- a/src/shared/navigation.tsx +++ b/src/shared/navigation.tsx @@ -15,6 +15,7 @@ import { BellIcon, NavArrowDownIcon, NwcIcon, + RelayIcon, SpaceIcon, WorldIcon, } from '@shared/icons'; @@ -102,7 +103,7 @@ export function Navigation() { } > - + Relays