From c65bb7a9928e150ec0c1c45d25c63d6da3b73753 Mon Sep 17 00:00:00 2001 From: Kieran Date: Wed, 1 Nov 2023 00:40:12 +0900 Subject: [PATCH] chore: cleanup --- packages/app/config/default.json | 7 ++- packages/app/config/iris.json | 7 ++- packages/app/custom.d.ts | 6 +++ packages/app/public/icons.svg | 6 +++ packages/app/src/Const.ts | 18 +------- packages/app/src/Element/AsyncButton.css | 1 - packages/app/src/Element/Copy.css | 1 - packages/app/src/Element/Copy.tsx | 2 +- packages/app/src/Element/ErrorOrOffline.tsx | 11 +++++ .../app/src/Element/Event/NoteCreator.css | 2 +- packages/app/src/Element/Event/Reveal.tsx | 23 +++------- packages/app/src/Element/Offline.tsx | 17 +++++++ packages/app/src/Element/PageSpinner.tsx | 2 +- .../app/src/Element/Relay/RelaysMetadata.tsx | 4 +- packages/app/src/Element/Reveal.css | 15 ------- packages/app/src/Element/SearchBox.css | 5 --- .../app/src/Element/SuggestedProfiles.tsx | 16 ++++--- .../src/Element/{Feed => }/TrendingPosts.tsx | 9 +++- packages/app/src/Element/TrendingUsers.tsx | 9 +++- .../app/src/Element/{ => User}/Bookmarks.tsx | 6 +-- packages/app/src/Element/User/MutedList.tsx | 2 +- packages/app/src/Element/WarningNotice.css | 18 ++++++++ packages/app/src/Element/WarningNotice.tsx | 12 +++++ packages/app/src/External/NostrBand.ts | 2 + packages/app/src/External/SemisolDev.ts | 3 ++ packages/app/src/External/SnortApi.ts | 2 + packages/app/src/Feed/LoginFeed.ts | 22 ++++++--- packages/app/src/Feed/TimelineFeed.ts | 8 +--- packages/app/src/Hooks/useLoginHandler.tsx | 9 ++-- packages/app/src/Hooks/useLoginRelays.tsx | 28 +++++++----- packages/app/src/Login/Functions.ts | 9 ++++ packages/app/src/Login/MultiAccountStore.ts | 3 +- packages/app/src/Nip05/ServiceProvider.ts | 3 ++ packages/app/src/Nip05/Verifier.ts | 3 ++ packages/app/src/Pages/Discover.tsx | 2 +- packages/app/src/Pages/Layout.tsx | 2 +- packages/app/src/Pages/LoginPage.tsx | 15 ------- packages/app/src/Pages/NostrLinkHandler.tsx | 4 +- packages/app/src/Pages/Notifications.tsx | 20 ++++++--- .../app/src/Pages/Profile/ProfilePage.tsx | 22 ++++----- packages/app/src/Pages/Profile/ProfileTab.tsx | 6 +-- packages/app/src/Pages/Root.tsx | 45 ++++++++++--------- packages/app/src/Pages/SearchPage.tsx | 35 +++++++-------- packages/app/src/Pages/settings/Profile.tsx | 26 +++++++---- .../src/Pages/settings/handle/ListHandles.tsx | 9 +++- .../Pages/subscribe/ManageSubscription.tsx | 29 +++++++----- packages/app/src/SnortUtils/index.ts | 3 ++ packages/app/src/Upload/NostrBuild.ts | 2 + packages/app/src/Upload/NostrImg.ts | 2 + packages/app/src/Upload/VoidCat.ts | 2 + packages/app/src/Wallet/LNDHub.ts | 2 + packages/app/src/index.css | 21 +++------ packages/app/src/index.tsx | 10 +++-- packages/app/webpack.config.js | 1 + packages/shared/src/lnurl.ts | 4 +- packages/shared/src/utils.ts | 12 +++++ 56 files changed, 344 insertions(+), 221 deletions(-) create mode 100644 packages/app/src/Element/ErrorOrOffline.tsx create mode 100644 packages/app/src/Element/Offline.tsx delete mode 100644 packages/app/src/Element/Reveal.css rename packages/app/src/Element/{Feed => }/TrendingPosts.tsx (74%) rename packages/app/src/Element/{ => User}/Bookmarks.tsx (95%) create mode 100644 packages/app/src/Element/WarningNotice.css create mode 100644 packages/app/src/Element/WarningNotice.tsx diff --git a/packages/app/config/default.json b/packages/app/config/default.json index 841ad304..9c97a1b4 100644 --- a/packages/app/config/default.json +++ b/packages/app/config/default.json @@ -15,5 +15,10 @@ "zapPool": true }, "eventLinkPrefix": "nevent", - "profileLinkPrefix": "nprofile" + "profileLinkPrefix": "nprofile", + "defaultRelays": { + "wss://relay.snort.social/": { "read": true, "write": true }, + "wss://nostr.wine/": { "read": true, "write": false }, + "wss://nos.lol/": { "read": true, "write": true } + } } diff --git a/packages/app/config/iris.json b/packages/app/config/iris.json index d063c3f0..9ddd1c9b 100644 --- a/packages/app/config/iris.json +++ b/packages/app/config/iris.json @@ -15,5 +15,10 @@ "zapPool": true }, "eventLinkPrefix": "note", - "profileLinkPrefix": "npub" + "profileLinkPrefix": "npub", + "defaultRelays": { + "wss://relay.snort.social/": { "read": true, "write": true }, + "wss://nostr.wine/": { "read": true, "write": false }, + "wss://nos.lol/": { "read": true, "write": true } + } } diff --git a/packages/app/custom.d.ts b/packages/app/custom.d.ts index c0b207a4..11bcbf5a 100644 --- a/packages/app/custom.d.ts +++ b/packages/app/custom.d.ts @@ -54,4 +54,10 @@ declare const CONFIG: { }; eventLinkPrefix: NostrPrefix; profileLinkPrefix: NostrPrefix; + defaultRelays: Record; }; + +/** + * Single relay (Debug) + */ +declare const SINGLE_RELAY: string | undefined; \ No newline at end of file diff --git a/packages/app/public/icons.svg b/packages/app/public/icons.svg index 33fb6f94..8f36b012 100644 --- a/packages/app/public/icons.svg +++ b/packages/app/public/icons.svg @@ -376,5 +376,11 @@ + + + + + + \ No newline at end of file diff --git a/packages/app/src/Const.ts b/packages/app/src/Const.ts index f0b47144..76695747 100644 --- a/packages/app/src/Const.ts +++ b/packages/app/src/Const.ts @@ -1,5 +1,3 @@ -import { RelaySettings } from "@snort/system"; - /** * 1 Hour in seconds */ @@ -35,24 +33,10 @@ export const KieranPubKey = "npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7v */ export const SnortPubKey = "npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws"; -/** - * Websocket re-connect timeout - */ -export const DefaultConnectTimeout = 2000; - -/** - * How long profile cache should be considered valid for - */ -export const ProfileCacheExpire = 1_000 * 60 * 60 * 6; - /** * Default bootstrap relays */ -export const DefaultRelays = new Map([ - ["wss://relay.snort.social/", { read: true, write: true }], - ["wss://nostr.wine/", { read: true, write: false }], - ["wss://nos.lol/", { read: true, write: true }], -]); +export const DefaultRelays = new Map(Object.entries(CONFIG.defaultRelays)); /** * Default search relays diff --git a/packages/app/src/Element/AsyncButton.css b/packages/app/src/Element/AsyncButton.css index ebf60b69..21b5ffa1 100644 --- a/packages/app/src/Element/AsyncButton.css +++ b/packages/app/src/Element/AsyncButton.css @@ -14,7 +14,6 @@ } .light .spinner-button { - background: #fff; border: 1px solid var(--border-color); color: var(--font-secondary); box-shadow: rgba(0, 0, 0, 0.08) 0 1px 1px; diff --git a/packages/app/src/Element/Copy.css b/packages/app/src/Element/Copy.css index a85f8aaa..ad6d30a1 100644 --- a/packages/app/src/Element/Copy.css +++ b/packages/app/src/Element/Copy.css @@ -1,5 +1,4 @@ .copy .copy-body { font-size: var(--font-size-small); color: var(--font-color); - margin-right: 6px; } diff --git a/packages/app/src/Element/Copy.tsx b/packages/app/src/Element/Copy.tsx index b8269f2f..897a815c 100644 --- a/packages/app/src/Element/Copy.tsx +++ b/packages/app/src/Element/Copy.tsx @@ -14,7 +14,7 @@ export default function Copy({ text, maxSize = 32, className }: CopyProps) { const trimmed = text.length > maxSize ? `${text.slice(0, sliceLength)}...${text.slice(-sliceLength)}` : text; return ( -
copy(text)}> +
copy(text)}> {trimmed} {copied ? : } diff --git a/packages/app/src/Element/ErrorOrOffline.tsx b/packages/app/src/Element/ErrorOrOffline.tsx new file mode 100644 index 00000000..5de062b8 --- /dev/null +++ b/packages/app/src/Element/ErrorOrOffline.tsx @@ -0,0 +1,11 @@ +import { OfflineError } from "@snort/shared"; +import { Offline } from "./Offline"; +import classNames from "classnames"; + +export function ErrorOrOffline({ error, onRetry, className }: { error: Error, onRetry?: () => void | Promise, className?: string }) { + if (error instanceof OfflineError) { + return ; + } else { + return {error.message} + } +} \ No newline at end of file diff --git a/packages/app/src/Element/Event/NoteCreator.css b/packages/app/src/Element/Event/NoteCreator.css index bfb98022..01dd8abc 100644 --- a/packages/app/src/Element/Event/NoteCreator.css +++ b/packages/app/src/Element/Event/NoteCreator.css @@ -86,7 +86,7 @@ } .light .note-creator textarea { - background-color: #fff; + background-color: var(--gray-superdark); } .light .note-creator { diff --git a/packages/app/src/Element/Event/Reveal.tsx b/packages/app/src/Element/Event/Reveal.tsx index 0eb2d9f3..9edd3b48 100644 --- a/packages/app/src/Element/Event/Reveal.tsx +++ b/packages/app/src/Element/Event/Reveal.tsx @@ -1,5 +1,4 @@ -import "../Reveal.css"; -import Icon from "Icons/Icon"; +import { WarningNotice } from "Element/WarningNotice"; import { useState } from "react"; interface RevealProps { @@ -7,22 +6,14 @@ interface RevealProps { children: React.ReactNode; } -export default function Reveal(props: RevealProps): JSX.Element { +export default function Reveal(props: RevealProps) { const [reveal, setReveal] = useState(false); if (!reveal) { - return ( -
{ - e.stopPropagation(); - setReveal(true); - }} - className="note-notice flex g8"> - -
{props.message}
-
- ); - } else { - return <>{props.children}; + return setReveal(true)}> + {props.message} + + } else if (props.children) { + return props.children; } } diff --git a/packages/app/src/Element/Offline.tsx b/packages/app/src/Element/Offline.tsx new file mode 100644 index 00000000..30cd35ad --- /dev/null +++ b/packages/app/src/Element/Offline.tsx @@ -0,0 +1,17 @@ +import Icon from "Icons/Icon"; +import AsyncButton from "./AsyncButton"; +import { FormattedMessage } from "react-intl"; +import classNames from "classnames"; + +export function Offline({ onRetry, className }: { onRetry?: () => void | Promise, className?: string }) { + return
+ +
+ +
+ {onRetry && + + + } +
+} \ No newline at end of file diff --git a/packages/app/src/Element/PageSpinner.tsx b/packages/app/src/Element/PageSpinner.tsx index 5020bd4e..af1a6077 100644 --- a/packages/app/src/Element/PageSpinner.tsx +++ b/packages/app/src/Element/PageSpinner.tsx @@ -2,7 +2,7 @@ import Spinner from "Icons/Spinner"; export default function PageSpinner() { return ( -
+
); diff --git a/packages/app/src/Element/Relay/RelaysMetadata.tsx b/packages/app/src/Element/Relay/RelaysMetadata.tsx index 5df1631e..5d83c5dc 100644 --- a/packages/app/src/Element/Relay/RelaysMetadata.tsx +++ b/packages/app/src/Element/Relay/RelaysMetadata.tsx @@ -22,7 +22,7 @@ interface RelaysMetadataProps { const RelaysMetadata = ({ relays }: RelaysMetadataProps) => { return ( -
+ <> {relays?.map(({ url, settings }) => { return (
@@ -35,7 +35,7 @@ const RelaysMetadata = ({ relays }: RelaysMetadataProps) => {
); })} -
+ ); }; diff --git a/packages/app/src/Element/Reveal.css b/packages/app/src/Element/Reveal.css deleted file mode 100644 index c305313e..00000000 --- a/packages/app/src/Element/Reveal.css +++ /dev/null @@ -1,15 +0,0 @@ -.note-notice { - color: var(--font-tertiary-color); - border: 1px solid var(--border-color); - padding: 8px 16px; - border-radius: 12px; -} - -.note-notice i { - font-style: normal; - color: var(--font-color); -} - -.note-notice svg { - color: var(--warning); -} diff --git a/packages/app/src/Element/SearchBox.css b/packages/app/src/Element/SearchBox.css index 88e63a06..9b70ce70 100644 --- a/packages/app/src/Element/SearchBox.css +++ b/packages/app/src/Element/SearchBox.css @@ -5,11 +5,6 @@ border-radius: 1000px; } -.light .search { - background: #fff; - border: 1px solid var(--border-color); -} - .search input { border: none !important; border-radius: 0 !important; diff --git a/packages/app/src/Element/SuggestedProfiles.tsx b/packages/app/src/Element/SuggestedProfiles.tsx index 282c17bc..98b84b5c 100644 --- a/packages/app/src/Element/SuggestedProfiles.tsx +++ b/packages/app/src/Element/SuggestedProfiles.tsx @@ -8,6 +8,7 @@ import NostrBandApi from "External/NostrBand"; import SemisolDevApi from "External/SemisolDev"; import useLogin from "Hooks/useLogin"; import { hexToBech32 } from "SnortUtils"; +import { ErrorOrOffline } from "./ErrorOrOffline"; enum Provider { NostrBand = 1, @@ -18,12 +19,12 @@ export default function SuggestedProfiles() { const login = useLogin(); const [userList, setUserList] = useState(); const [provider, setProvider] = useState(Provider.NostrBand); - const [error, setError] = useState(""); + const [error, setError] = useState(); async function loadSuggestedProfiles() { if (!login.publicKey) return; setUserList(undefined); - setError(""); + setError(undefined); try { switch (provider) { @@ -44,26 +45,27 @@ export default function SuggestedProfiles() { } } catch (e) { if (e instanceof Error) { - setError(e.message); + setError(e); } } } useEffect(() => { - loadSuggestedProfiles().catch(console.error); + loadSuggestedProfiles(); }, [login, provider]); return ( <> -
+
- {error && {error}} - {userList ? : } + {error && } + {userList && } + {!userList && !error && } ); } diff --git a/packages/app/src/Element/Feed/TrendingPosts.tsx b/packages/app/src/Element/TrendingPosts.tsx similarity index 74% rename from packages/app/src/Element/Feed/TrendingPosts.tsx rename to packages/app/src/Element/TrendingPosts.tsx index 6c9bfcb5..405ca67b 100644 --- a/packages/app/src/Element/Feed/TrendingPosts.tsx +++ b/packages/app/src/Element/TrendingPosts.tsx @@ -5,9 +5,11 @@ import PageSpinner from "Element/PageSpinner"; import Note from "Element/Event/Note"; import NostrBandApi from "External/NostrBand"; import { useReactions } from "Feed/Reactions"; +import { ErrorOrOffline } from "Element/ErrorOrOffline"; export default function TrendingNotes() { const [posts, setPosts] = useState>(); + const [error, setError] = useState(); const related = useReactions("trending", posts?.map(a => NostrLink.fromEvent(a)) ?? []); async function loadTrendingNotes() { @@ -17,9 +19,14 @@ export default function TrendingNotes() { } useEffect(() => { - loadTrendingNotes().catch(console.error); + loadTrendingNotes().catch(e => { + if (e instanceof Error) { + setError(e); + } + }); }, []); + if (error) return ; if (!posts) return ; return ( diff --git a/packages/app/src/Element/TrendingUsers.tsx b/packages/app/src/Element/TrendingUsers.tsx index 3f07f9aa..a174fcf7 100644 --- a/packages/app/src/Element/TrendingUsers.tsx +++ b/packages/app/src/Element/TrendingUsers.tsx @@ -4,9 +4,11 @@ import { HexKey } from "@snort/system"; import FollowListBase from "Element/User/FollowListBase"; import PageSpinner from "Element/PageSpinner"; import NostrBandApi from "External/NostrBand"; +import { ErrorOrOffline } from "./ErrorOrOffline"; export default function TrendingUsers() { const [userList, setUserList] = useState(); + const [error, setError] = useState(); async function loadTrendingUsers() { const api = new NostrBandApi(); @@ -16,9 +18,14 @@ export default function TrendingUsers() { } useEffect(() => { - loadTrendingUsers().catch(console.error); + loadTrendingUsers().catch(e => { + if (e instanceof Error) { + setError(e); + } + }); }, []); + if (error) return ; if (!userList) return ; return ; diff --git a/packages/app/src/Element/Bookmarks.tsx b/packages/app/src/Element/User/Bookmarks.tsx similarity index 95% rename from packages/app/src/Element/Bookmarks.tsx rename to packages/app/src/Element/User/Bookmarks.tsx index 5a985ba6..26a3b298 100644 --- a/packages/app/src/Element/Bookmarks.tsx +++ b/packages/app/src/Element/User/Bookmarks.tsx @@ -6,7 +6,7 @@ import Note from "Element/Event/Note"; import useLogin from "Hooks/useLogin"; import { UserCache } from "Cache"; -import messages from "./messages"; +import messages from "../messages"; interface BookmarksProps { pubkey: HexKey; @@ -27,7 +27,7 @@ const Bookmarks = ({ pubkey, bookmarks, related }: BookmarksProps) => { } return ( -
+ <>
setRelay(allRelays.find(a => a.url === e.target.value))} @@ -113,10 +107,12 @@ export const GlobalTab = () => { return debounce(500, () => { const ret: RelayOption[] = []; system.Sockets.forEach(v => { - ret.push({ - url: v.address, - paid: v.info?.limitation?.payment_required ?? false, - }); + if (v.connected) { + ret.push({ + url: v.address, + paid: v.info?.limitation?.payment_required ?? false, + }); + } }); ret.sort(a => (a.paid ? -1 : 1)); @@ -130,7 +126,14 @@ export const GlobalTab = () => { return ( <> {globalRelaySelector()} - {relay && } + {relay && } ); }; @@ -148,8 +151,8 @@ export const NotesTab = () => { noteOnClick={ deckContext ? ev => { - deckContext.setThread(NostrLink.fromEvent(ev)); - } + deckContext.setThread(NostrLink.fromEvent(ev)); + } : undefined } /> diff --git a/packages/app/src/Pages/SearchPage.tsx b/packages/app/src/Pages/SearchPage.tsx index 782e964f..ec6b932b 100644 --- a/packages/app/src/Pages/SearchPage.tsx +++ b/packages/app/src/Pages/SearchPage.tsx @@ -1,13 +1,13 @@ import { useIntl, FormattedMessage } from "react-intl"; import { useParams } from "react-router-dom"; import Timeline from "Element/Feed/Timeline"; -import { Tab, TabElement } from "Element/Tabs"; +import Tabs, { Tab } from "Element/Tabs"; import { useEffect, useState } from "react"; import { debounce } from "SnortUtils"; import { router } from "index"; import TrendingUsers from "Element/TrendingUsers"; -import TrendingNotes from "Element/Feed/TrendingPosts"; +import TrendingNotes from "Element/TrendingPosts"; const NOTES = 0; const PROFILES = 1; @@ -19,11 +19,11 @@ const SearchPage = () => { const [keyword, setKeyword] = useState(params.keyword); const [sortPopular, setSortPopular] = useState(true); // tabs - const SearchTab = { - Posts: { text: formatMessage({ defaultMessage: "Notes" }), value: NOTES }, - Profiles: { text: formatMessage({ defaultMessage: "People" }), value: PROFILES }, - }; - const [tab, setTab] = useState(SearchTab.Posts); + const SearchTab = [ + { text: formatMessage({ defaultMessage: "Notes" }), value: NOTES }, + { text: formatMessage({ defaultMessage: "People" }), value: PROFILES }, + ]; + const [tab, setTab] = useState(SearchTab[0]); useEffect(() => { if (keyword) { @@ -72,9 +72,8 @@ const SearchPage = () => { function sortOptions() { if (tab.value != PROFILES) return null; return ( -
+
-   setSearch(e.target.value)} autoFocus={true} /> +
-
{[SearchTab.Posts, SearchTab.Profiles].map(renderTab)}
{tabContent()}
); diff --git a/packages/app/src/Pages/settings/Profile.tsx b/packages/app/src/Pages/settings/Profile.tsx index 58417139..ad9f7b52 100644 --- a/packages/app/src/Pages/settings/Profile.tsx +++ b/packages/app/src/Pages/settings/Profile.tsx @@ -13,6 +13,7 @@ import useLogin from "Hooks/useLogin"; import Icon from "Icons/Icon"; import Avatar from "Element/User/Avatar"; import { FormattedMessage } from "react-intl"; +import { ErrorOrOffline } from "Element/ErrorOrOffline"; export interface ProfileSettingsProps { avatar?: boolean; @@ -25,6 +26,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) { const user = useUserProfile(id ?? ""); const { publisher, system } = useEventPublisher(); const uploader = useFileUpload(); + const [error, setError] = useState(); const [name, setName] = useState(); const [picture, setPicture] = useState(); @@ -79,15 +81,20 @@ export default function ProfileSettings(props: ProfileSettingsProps) { } async function uploadFile() { - const file = await openFile(); - if (file) { - console.log(file); - const rsp = await uploader.upload(file, file.name); - console.log(rsp); - if (typeof rsp?.error === "string") { - throw new Error(`Upload failed ${rsp.error}`); + try { + setError(undefined); + const file = await openFile(); + if (file) { + const rsp = await uploader.upload(file, file.name); + if (typeof rsp?.error === "string") { + throw new Error(`Upload failed ${rsp.error}`); + } + return rsp.url; + } + } catch (e) { + if (e instanceof Error) { + setError(e); } - return rsp.url; } } @@ -210,7 +217,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) { setNewAvatar()} disabled={readonly}> @@ -218,6 +225,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
)}
+ {error && } {editor()} ); diff --git a/packages/app/src/Pages/settings/handle/ListHandles.tsx b/packages/app/src/Pages/settings/handle/ListHandles.tsx index ad5b3acd..772199f5 100644 --- a/packages/app/src/Pages/settings/handle/ListHandles.tsx +++ b/packages/app/src/Pages/settings/handle/ListHandles.tsx @@ -5,14 +5,20 @@ import { Link, useNavigate } from "react-router-dom"; import { ApiHost } from "Const"; import useEventPublisher from "Hooks/useEventPublisher"; import SnortServiceProvider, { ManageHandle } from "Nip05/SnortServiceProvider"; +import { ErrorOrOffline } from "Element/ErrorOrOffline"; export default function ListHandles() { const navigate = useNavigate(); const { publisher } = useEventPublisher(); const [handles, setHandles] = useState>([]); + const [error, setError] = useState(); useEffect(() => { - loadHandles().catch(console.error); + loadHandles().catch(e => { + if (e instanceof Error) { + setError(e); + } + }); }, [publisher]); async function loadHandles() { @@ -60,6 +66,7 @@ export default function ListHandles() { )} + {error && } ); } diff --git a/packages/app/src/Pages/subscribe/ManageSubscription.tsx b/packages/app/src/Pages/subscribe/ManageSubscription.tsx index 13e2436f..08b993b1 100644 --- a/packages/app/src/Pages/subscribe/ManageSubscription.tsx +++ b/packages/app/src/Pages/subscribe/ManageSubscription.tsx @@ -7,6 +7,7 @@ import useEventPublisher from "Hooks/useEventPublisher"; import SnortApi, { Subscription, SubscriptionError } from "External/SnortApi"; import { mapSubscriptionErrorCode } from "."; import SubscriptionCard from "./SubscriptionCard"; +import { ErrorOrOffline } from "Element/ErrorOrOffline"; export default function ManageSubscriptionPage() { const { publisher } = useEventPublisher(); @@ -14,21 +15,25 @@ export default function ManageSubscriptionPage() { const navigate = useNavigate(); const [subs, setSubs] = useState>(); - const [error, setError] = useState(); + const [error, setError] = useState(); - useEffect(() => { - (async () => { - try { - const s = await api.listSubscriptions(); - setSubs(s); - } catch (e) { - if (e instanceof SubscriptionError) { - setError(e); - } + async function loadSubs() { + setError(undefined); + try { + const s = await api.listSubscriptions(); + setSubs(s); + } catch (e) { + if (e instanceof Error) { + setError(e); } - })(); + } + } + useEffect(() => { + loadSubs(); }, []); + + if (!(error instanceof SubscriptionError) && error instanceof Error) return ; if (subs === undefined) { return ; } @@ -59,7 +64,7 @@ export default function ManageSubscriptionPage() { />

)} - {error && {mapSubscriptionErrorCode(error)}} + {error instanceof SubscriptionError && {mapSubscriptionErrorCode(error)}}
); } diff --git a/packages/app/src/SnortUtils/index.ts b/packages/app/src/SnortUtils/index.ts index e7d82df0..e227e6d3 100644 --- a/packages/app/src/SnortUtils/index.ts +++ b/packages/app/src/SnortUtils/index.ts @@ -1,3 +1,4 @@ +import Nostrich from "../nostrich.webp"; import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; import { sha256 as hash } from "@noble/hashes/sha256"; @@ -18,6 +19,7 @@ import { } from "@snort/system"; import { Day } from "Const"; import AnimalName from "Element/User/AnimalName"; +import { isOffline } from "@snort/shared"; export const sha256 = (str: string | Uint8Array): u256 => { return utils.bytesToHex(hash(str)); @@ -464,6 +466,7 @@ export function kvToObject(o: string, sep?: string) { } export function defaultAvatar(input?: string) { + if (isOffline()) return Nostrich; return `https://robohash.v0l.io/${input ?? "missing"}.png${isHalloween() ? "?set=set2" : ""}`; } diff --git a/packages/app/src/Upload/NostrBuild.ts b/packages/app/src/Upload/NostrBuild.ts index 324bf560..be337919 100644 --- a/packages/app/src/Upload/NostrBuild.ts +++ b/packages/app/src/Upload/NostrBuild.ts @@ -1,4 +1,5 @@ import { base64 } from "@scure/base"; +import { throwIfOffline } from "@snort/shared"; import { EventKind, EventPublisher } from "@snort/system"; import { UploadResult } from "Upload"; @@ -30,6 +31,7 @@ export default async function NostrBuild(file: File | Blob, publisher?: EventPub headers, }); if (rsp.ok) { + throwIfOffline(); const data = (await rsp.json()) as NostrBuildUploadResponse; const res = data.data[0]; return { diff --git a/packages/app/src/Upload/NostrImg.ts b/packages/app/src/Upload/NostrImg.ts index 705d51ff..2e609e25 100644 --- a/packages/app/src/Upload/NostrImg.ts +++ b/packages/app/src/Upload/NostrImg.ts @@ -1,6 +1,8 @@ +import { throwIfOffline } from "@snort/shared"; import { UploadResult } from "Upload"; export default async function NostrImg(file: File | Blob): Promise { + throwIfOffline(); const fd = new FormData(); fd.append("image", file); diff --git a/packages/app/src/Upload/VoidCat.ts b/packages/app/src/Upload/VoidCat.ts index ce969386..14b99142 100644 --- a/packages/app/src/Upload/VoidCat.ts +++ b/packages/app/src/Upload/VoidCat.ts @@ -4,6 +4,7 @@ import { UploadState, VoidApi } from "@void-cat/api"; import { FileExtensionRegex, VoidCatHost } from "Const"; import { UploadResult } from "Upload"; import { base64 } from "@scure/base"; +import { throwIfOffline } from "@snort/shared"; /** * Upload file to void.cat @@ -16,6 +17,7 @@ export default async function VoidCatUpload( progress?: (n: number) => void, stage?: (n: "starting" | "hashing" | "uploading" | "done" | undefined) => void, ): Promise { + throwIfOffline(); const auth = publisher ? async (url: string, method: string) => { const auth = await publisher.generic(eb => { diff --git a/packages/app/src/Wallet/LNDHub.ts b/packages/app/src/Wallet/LNDHub.ts index 738dfeb9..fb09907e 100644 --- a/packages/app/src/Wallet/LNDHub.ts +++ b/packages/app/src/Wallet/LNDHub.ts @@ -1,3 +1,4 @@ +import { throwIfOffline } from "@snort/shared"; import { InvoiceRequest, LNWallet, @@ -120,6 +121,7 @@ export default class LNDHubWallet implements LNWallet { } private async getJson(method: "GET" | "POST", path: string, body?: unknown): Promise { + throwIfOffline(); const auth = `Bearer ${this.auth?.access_token}`; const url = `${this.url.pathname === "/" ? this.url.toString().slice(0, -1) : this.url.toString()}${path}`; const rsp = await fetch(url, { diff --git a/packages/app/src/index.css b/packages/app/src/index.css index 3d2ca953..b17028ff 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -92,8 +92,8 @@ html.light { --gray-superlight: #333; --gray-light: #555; --gray-dark: #ccc; - --gray-superdark: #fff; - --gray-ultradark: #fff; + --gray-superdark: #f0f0f0; + --gray-ultradark: #fefefe; --dm-gradient: var(--gray); --invoice-gradient: linear-gradient(45deg, var(--gray-superdark) 50%, #f7b73333, #fc4a1a33); --paid-invoice-gradient: linear-gradient(45deg, var(--gray-superdark) 50%, #f7b73399, #fc4a1a99); @@ -908,22 +908,15 @@ svg.zap-solid { border: 1px solid var(--border-color); } -.light .spinner-button { - background: #fff; - border: 1px solid var(--border-color); - color: var(--font-secondary); - box-shadow: rgba(0, 0, 0, 0.08) 0 1px 1px; -} - -.light .spinner-button:hover { - box-shadow: rgba(0, 0, 0, 0.2) 0 1px 3px; -} - -.main-content.p { +.main-content.p:not(:last-of-type) { border-bottom: 0; border-top: 0; } +.main-content.p:last-of-type { + border-top: 0; +} + .light button.icon { border: 1px solid var(--border-color); box-shadow: rgba(0, 0, 0, 0.08) 0 1px 1px; diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 83a1688a..738c21fa 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -20,7 +20,7 @@ import { PowWorker, } from "@snort/system"; import { SnortContext } from "@snort/system-react"; -import { removeUndefined } from "@snort/shared"; +import { removeUndefined, throwIfOffline } from "@snort/shared"; import * as serviceWorkerRegistration from "serviceWorkerRegistration"; import { IntlProvider } from "IntlProvider"; @@ -49,6 +49,7 @@ import { LoginStore } from "Login"; import { SnortDeckLayout } from "Pages/DeckLayout"; import FreeNostrAddressPage from "./Pages/FreeNostrAddressPage"; import { ListFeedPage } from "Pages/ListFeedPage"; +import { updateRelayConnections } from "Hooks/useLoginRelays"; const WasmQueryOptimizer = { expandFilter: (f: ReqFilter) => { @@ -96,6 +97,7 @@ const System = new NostrSystem({ async function fetchProfile(key: string) { try { + throwIfOffline(); const rsp = await fetch(`${CONFIG.httpCache}/profile/${key}`); if (rsp.ok) { const data = (await rsp.json()) as NostrEvent; @@ -134,9 +136,9 @@ async function initSite() { await preload(login.follows.item); } - for (const [k, v] of Object.entries(login.relays.item)) { - System.ConnectToRelay(k, v); - } + updateRelayConnections(System, login.relays.item) + .catch(console.error); + try { if ("registerProtocolHandler" in window.navigator) { window.navigator.registerProtocolHandler("web+nostr", `${window.location.protocol}//${window.location.host}/%s`); diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js index 35d4d060..652f5ad0 100644 --- a/packages/app/webpack.config.js +++ b/packages/app/webpack.config.js @@ -90,6 +90,7 @@ const config = { : false, new DefinePlugin({ CONFIG: JSON.stringify(appConfig), + SINGLE_RELAY: JSON.stringify(process.env.SINGLE_RELAY), }), ], module: { diff --git a/packages/shared/src/lnurl.ts b/packages/shared/src/lnurl.ts index dbb27ee8..b9be66fe 100644 --- a/packages/shared/src/lnurl.ts +++ b/packages/shared/src/lnurl.ts @@ -1,5 +1,5 @@ import { EmailRegex } from "./const"; -import { bech32ToText, unwrap } from "./utils"; +import { bech32ToText, throwIfOffline, unwrap } from "./utils"; const PayServiceTag = "payRequest"; @@ -93,6 +93,7 @@ export class LNURL { } async load() { + throwIfOffline(); const rsp = await fetch(this.#url); if (rsp.ok) { this.#service = await rsp.json(); @@ -108,6 +109,7 @@ export class LNURL { * @returns */ async getInvoice(amount: number, comment?: string, zap?: object) { + throwIfOffline(); const callback = new URL(unwrap(this.#service?.callback)); const query = new Map(); diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 7dd9ebe5..80d04af5 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -215,3 +215,15 @@ export function normalizeReaction(content: string) { return Reaction.Positive; } } + +export class OfflineError extends Error {} + +export function throwIfOffline() { + if (isOffline()) { + throw new OfflineError("Offline"); + } +} + +export function isOffline() { + return !("navigator" in globalThis && globalThis.navigator.onLine); +}