wip: relay manegament screen
This commit is contained in:
29
src/app/relays/components/relayEventList.tsx
Normal file
29
src/app/relays/components/relayEventList.tsx
Normal file
@@ -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 (
|
||||
<div>
|
||||
<p>TODO</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
66
src/app/relays/components/relayForm.tsx
Normal file
66
src/app/relays/components/relayForm.tsx
Normal file
@@ -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 (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex h-10 items-center justify-between rounded-lg bg-white/10 pr-1.5">
|
||||
<input
|
||||
className="h-full w-full bg-transparent pl-3 pr-1.5 placeholder:text-white/70 focus:outline-none"
|
||||
type="url"
|
||||
placeholder="wss://"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => createRelay()}
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded bg-fuchsia-500 text-white hover:bg-fuchsia-600"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-sm text-red-400">{error}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="col-span-2 border-r border-white/5">
|
||||
{status === 'loading' ? (
|
||||
@@ -49,7 +63,7 @@ export function RelayList() {
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openRelay(key)}
|
||||
onClick={() => inspectRelay(key)}
|
||||
className="inline-flex h-6 items-center justify-center gap-1 rounded bg-white/10 px-1.5 text-sm font-medium hover:bg-white/20"
|
||||
>
|
||||
<ShareIcon className="h-3 w-3" />
|
||||
@@ -57,6 +71,7 @@ export function RelayList() {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => connectRelay(key)}
|
||||
className="inline-flex h-6 w-6 items-center justify-center rounded hover:bg-white/10"
|
||||
>
|
||||
<PlusIcon className="h-3 w-3" />
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { RelayForm } from '@app/relays/components/relayForm';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { CancelIcon } from '@shared/icons';
|
||||
|
||||
export function UserRelay() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { relayUrls } = useNDK();
|
||||
const { db } = useStorage();
|
||||
const { status, data } = useQuery(
|
||||
@@ -14,6 +20,11 @@ export function UserRelay() {
|
||||
{ refetchOnWindowFocus: false }
|
||||
);
|
||||
|
||||
const removeRelay = async (relayUrl: string) => {
|
||||
await db.removeRelay(relayUrl);
|
||||
queryClient.invalidateQueries(['user-relay']);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-3 px-3">
|
||||
{status === 'loading' ? (
|
||||
@@ -23,22 +34,34 @@ export function UserRelay() {
|
||||
{data.map((item) => (
|
||||
<div
|
||||
key={item}
|
||||
className="inline-flex h-10 items-center gap-2.5 rounded-lg bg-white/10 px-3"
|
||||
className="group flex h-10 items-center justify-between rounded-lg bg-white/10 pl-3 pr-1.5"
|
||||
>
|
||||
{relayUrls.includes(item) ? (
|
||||
<span className="relative flex h-3 w-3">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex h-3 w-3 rounded-full bg-green-500"></span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="relative flex h-3 w-3">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-400 opacity-75"></span>
|
||||
<span className="relative inline-flex h-3 w-3 rounded-full bg-red-500"></span>
|
||||
</span>
|
||||
)}
|
||||
<p className="text-sm font-medium">{item}</p>
|
||||
<div className="inline-flex items-center gap-2.5">
|
||||
{relayUrls.includes(item) ? (
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-green-500"></span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-400 opacity-75"></span>
|
||||
<span className="relative inline-flex h-2 w-2 rounded-full bg-red-500"></span>
|
||||
</span>
|
||||
)}
|
||||
<p className="max-w-[20rem] truncate text-sm font-medium leading-none">
|
||||
{item}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeRelay(item)}
|
||||
className="hidden h-6 w-6 items-center justify-center rounded hover:bg-white/10 group-hover:inline-flex"
|
||||
>
|
||||
<CancelIcon className="h-4 w-4 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<RelayForm />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<Await
|
||||
resolve={data.relay}
|
||||
errorElement={<div>Could not load relay information 😬</div>}
|
||||
>
|
||||
{(resolvedRelay) => <p>{JSON.stringify(resolvedRelay)}</p>}
|
||||
</Await>
|
||||
</Suspense>
|
||||
<div className="grid h-full w-full grid-cols-3">
|
||||
<div className="col-span-2 border-r border-white/5"></div>
|
||||
<div className="col-span-1">
|
||||
<div className="inline-flex h-16 w-full items-center border-b border-white/5 px-3">
|
||||
<h3 className="font-semibold text-white">Information</h3>
|
||||
</div>
|
||||
<div className="mt-4 px-3">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-white">
|
||||
<LoaderIcon className="h-4 w-4 animate-spin" />
|
||||
Loading...
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Await
|
||||
resolve={data.relay}
|
||||
errorElement={
|
||||
<div className="text-sm font-medium">
|
||||
<p>Could not load relay information 😬</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{(resolvedRelay) => (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
<span className="font-semibold">Name</span> : {resolvedRelay.name}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">Description</span> :{' '}
|
||||
{resolvedRelay.description}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">Contact</span> :{' '}
|
||||
{resolvedRelay.contact}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">Software</span> : [open website]
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">Version</span> :{' '}
|
||||
{resolvedRelay.version}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Await>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user