diff --git a/package.json b/package.json index 2796e56..abb54e7 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "preview": "vite preview" }, "dependencies": { + "@snort/shared": "^1.0.10", + "@snort/system": "^1.1.8", + "@snort/system-react": "^1.1.8", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.1" diff --git a/src/element/button.tsx b/src/element/button.tsx index da226de..3d13463 100644 --- a/src/element/button.tsx +++ b/src/element/button.tsx @@ -1,8 +1,20 @@ -import { HTMLProps } from "react"; +import { HTMLProps, useState } from "react"; +import Spinner from "./spinner"; // eslint-disable-next-line @typescript-eslint/no-unused-vars -export default function Button({ children, type, ...props }: HTMLProps) { - return } \ No newline at end of file diff --git a/src/element/event.tsx b/src/element/event.tsx new file mode 100644 index 0000000..3aa4731 --- /dev/null +++ b/src/element/event.tsx @@ -0,0 +1,74 @@ +import { hexToBech32 } from "@snort/shared"; +import { EventKind, NostrEvent, transformText } from "@snort/system"; +import { useUserProfile } from "@snort/system-react"; +import { useMemo } from "react"; +import { Mention } from "./mention"; + +export function SimpleEvent({ ev }: { ev: NostrEvent }) { + const profile = useUserProfile(ev.pubkey); + + function renderContent() { + switch (ev.kind) { + case EventKind.TextNote: return + case EventKind.SetMetadata: return "Profile Update"; + case EventKind.Reaction: return ev.content; + case EventKind.DirectMessage: return
+ DM with a[0] === "p")![1]} /> +
; + case EventKind.ZapReceipt: return
+ Zapping a[0] === "p")![1]} /> +
+ case EventKind.ContactList: return
+ Following: {ev.tags.filter(a => a[0] === "p").length.toLocaleString()} +
; + case EventKind.Repost: return ""; + case EventKind.Relays: return ev.tags.filter(a => a[0] === "r").map(a => a[1]).join(", "); + default: return
{JSON.stringify(ev, undefined, 2)}
; + } + } + + function kindName() { + switch (ev.kind) { + case 0: return "Profile"; + case 1: return "TextNote"; + case 3: return "Contact List"; + case 4: return "DM"; + case 5: return "Deletion"; + case 6: return "Repost"; + case 7: return "Reaction"; + case 10_002: return "Relays"; + case 9735: return "Zap"; + default: return `Kind ${ev.kind}`; + } + } + return
+
+
+ +
+
+ {profile?.display_name ?? profile?.name ?? hexToBech32("npub", ev.pubkey).slice(0, 12)} +
+
+ {kindName()} +
+
+ {renderContent()} +
+} + +function TextNote({ ev }: { ev: NostrEvent }) { + const fragments = useMemo(() => transformText(ev.content, ev.tags), [ev.content, ev.tags]); + return
{fragments.map((f, i) => { + switch (f.type) { + case "link": + case "media": { + return {f.content} + } + default: { + return {f.content} + } + } + })} +
; +} \ No newline at end of file diff --git a/src/element/mention.tsx b/src/element/mention.tsx new file mode 100644 index 0000000..d6ad020 --- /dev/null +++ b/src/element/mention.tsx @@ -0,0 +1,11 @@ +import { hexToBech32 } from "@snort/shared"; +import { NostrLink, NostrPrefix } from "@snort/system"; +import { useUserProfile } from "@snort/system-react"; +import { Link } from "react-router-dom"; + +export function Mention({ pubkey }: { pubkey: string }) { + const profile = useUserProfile(pubkey); + return + {profile?.display_name ?? profile?.name ?? hexToBech32("npub", pubkey).slice(0, 12)} + +} \ No newline at end of file diff --git a/src/element/relay.tsx b/src/element/relay.tsx index 5c14150..230295a 100644 --- a/src/element/relay.tsx +++ b/src/element/relay.tsx @@ -1,6 +1,8 @@ +import { useNavigate } from "react-router-dom"; import { Relay } from "../api"; export default function RelayItem({ relay, index }: { relay: Relay, index: number }) { + const navigate = useNavigate(); const kv = (key: string, value: string) => { return
{key}: @@ -8,16 +10,27 @@ export default function RelayItem({ relay, index }: { relay: Relay, index: numbe
} - return
+ const u = new URL(relay.url); + + return
{ + navigate(`/r/${encodeURIComponent(relay.url)}`); + }}>
-
#{index + 1}
-
{relay.url}
+
#{index + 1}
+
+ {u.protocol}// + {u.host}{u.pathname !== "/" ? u.pathname : ""} +
{relay.description &&
{relay.description}
}
{kv("Users", relay.users.toLocaleString())} {relay.country && kv("Country", relay.country)} -
+
+ {relay.distance !== 0 &&
+ {Math.ceil(relay.distance / 1000).toLocaleString()} + Km +
} {relay.is_paid === true ? Paid : Free}
diff --git a/src/element/spinner.css b/src/element/spinner.css new file mode 100644 index 0000000..e29cc8c --- /dev/null +++ b/src/element/spinner.css @@ -0,0 +1,34 @@ +.spinner_V8m1 { + transform-origin: center; + animation: spinner_zKoa 2s linear infinite; + } + + .spinner_V8m1 circle { + stroke-linecap: round; + animation: spinner_YpZS 1.5s ease-in-out infinite; + } + + @keyframes spinner_zKoa { + 100% { + transform: rotate(360deg); + } + } + + @keyframes spinner_YpZS { + 0% { + stroke-dasharray: 0 150; + stroke-dashoffset: 0; + } + + 47.5% { + stroke-dasharray: 42 150; + stroke-dashoffset: -16; + } + + 95%, + 100% { + stroke-dasharray: 42 150; + stroke-dashoffset: -59; + } + } + \ No newline at end of file diff --git a/src/element/spinner.tsx b/src/element/spinner.tsx new file mode 100644 index 0000000..bdd01e1 --- /dev/null +++ b/src/element/spinner.tsx @@ -0,0 +1,17 @@ +import "./spinner.css"; + +export interface IconProps { + className?: string; + width?: number; + height?: number; +} + +const Spinner = (props: IconProps) => ( + + + + + +); + +export default Spinner; diff --git a/src/index.css b/src/index.css index 6e20009..eb00dcf 100644 --- a/src/index.css +++ b/src/index.css @@ -2,6 +2,23 @@ @tailwind components; @tailwind utilities; -body { +body, +html, +:host { @apply text-white bg-slate-950; + line-height: 1; +} + +h1 { + font-size: 2rem; +} +h2 { + font-size: 1.75rem; +} +h3 { + font-size: 1.5rem; +} + +a { + @apply text-red-300; } \ No newline at end of file diff --git a/src/layout.tsx b/src/layout.tsx index 5facbd6..d405426 100644 --- a/src/layout.tsx +++ b/src/layout.tsx @@ -6,7 +6,9 @@ export default function Layout() { return
Snort Relays
-