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 (
+
+ );
+}
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