1
0
forked from Kieran/snort

General improvements

This commit is contained in:
Kieran 2023-01-12 12:00:44 +00:00
parent 46df3a64c7
commit 783dbf8b68
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
19 changed files with 642 additions and 261 deletions

View File

@ -8,6 +8,10 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@noble/secp256k1": "^1.7.0",
"@reduxjs/toolkit": "^1.9.1",
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"bech32": "^2.0.0",
"light-bolt11-decoder": "^2.1.0",
"qr-code-styling": "^1.6.0-rc.1",
@ -18,6 +22,7 @@
"react-router-dom": "^6.5.0",
"react-scripts": "5.0.1",
"react-twitter-embed": "^4.0.4",
"typescript": "^4.9.4",
"uuid": "^9.0.0"
},
"scripts": {

View File

@ -2,13 +2,22 @@
/**
* Websocket re-connect timeout
*/
export const DefaultConnectTimeout = 1000;
export const DefaultConnectTimeout = 2000;
/**
* How long profile cache should be considered valid for
*/
export const ProfileCacheExpire = (1_000 * 60 * 5);
/**
* Default bootstrap relays
*/
export const DefaultRelays = {
"wss://relay.snort.social": { read: true, write: true },
"wss://relay.damus.io": { read: true, write: true },
"wss://nostr-pub.wellorder.net": { read: true, write: true }
};
/**
* List of recommended follows for new users
*/
@ -17,7 +26,7 @@ export const RecommendedFollows = [
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", // fiatjaf
"020f2d21ae09bf35fcdfb65decf1478b846f5f728ab30c5eaabcd6d081a81c3e", // adam3us
"6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93", // gigi
"217e3d8b61c087b10422427e114737a4a4a4b1e15f22301fb4b07e1f33204d7c", // Kieran
"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed", // Kieran
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", // jb55
"e33fe65f1fde44c6dc17eeb38fdad0fceaf1cae8722084332ed1e32496291d42", // wiz
"00000000827ffaa94bfea288c3dfce4422c794fbb96625b6b31e9049f729d700", // cameri
@ -31,6 +40,7 @@ export const RecommendedFollows = [
"472F440F29EF996E92A186B8D320FF180C855903882E59D50DE1B8BD5669301E", // MartyBent
"1577e4599dd10c863498fe3c20bd82aafaf829a595ce83c5cf8ac3463531b09b", // yegorpetrov
"04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9", // ODELL
"7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", // verbiricha
];
/**

View File

@ -15,7 +15,7 @@ export default function FollowListBase({ pubkeys, title}) {
<div className="f-grow">{title}</div>
<div className="btn" onClick={() => followAll()}>Follow All</div>
</div>
{pubkeys?.map(a => <ProfilePreview pubkey={a} key={a} options={{ about: false }} />)}
{pubkeys?.map(a => <ProfilePreview pubkey={a} key={a} />)}
</>
)
}

View File

@ -20,7 +20,8 @@ export default function Invoice(props) {
let ret = {
amount: !isNaN(amount) ? (amount / 1000) : 0,
expire: !isNaN(timestamp) && !isNaN(expire) ? timestamp + expire : null,
description
description,
expired: false
};
if (ret.expire) {
ret.expired = ret.expire < (new Date().getTime() / 1000);

View File

@ -14,9 +14,5 @@ export default function LazyImage(props) {
})
}, [inView]);
return (
<div ref={ref}>
{shown ? <img {...props} /> : null}
</div>
)
return shown ? <img {...props} /> : <div ref={ref}></div>
}

View File

@ -7,7 +7,7 @@ import useProfile from "../feed/ProfileFeed";
import { hexToBech32, profileLink } from "../Util";
import LazyImage from "./LazyImage";
export default function ProfileImage({ pubkey, subHeader, showUsername = true }) {
export default function ProfileImage({ pubkey, subHeader, showUsername = true, className }) {
const navigate = useNavigate();
const user = useProfile(pubkey);
@ -23,11 +23,11 @@ export default function ProfileImage({ pubkey, subHeader, showUsername = true })
}, [user]);
return (
<div className="pfp">
<div className={`pfp ${className ?? ""}`}>
<LazyImage src={hasImage ? user.picture : Nostrich} onClick={() => navigate(profileLink(pubkey))} />
{showUsername && (<div>
{showUsername && (<div className="f-grow">
<Link key={pubkey} to={profileLink(pubkey)}>{name}</Link>
{subHeader ? <div>{subHeader}</div> : null}
{subHeader ? <>{subHeader}</> : null}
</div>
)}
</div>

View File

@ -7,4 +7,9 @@
.profile-preview .pfp {
flex-grow: 1;
min-width: 200px;
}
.profile-preview .about {
font-size: small;
color: var(--gray-light);
}

View File

@ -13,10 +13,10 @@ export default function ProfilePreview(props) {
return (
<div className="profile-preview">
<ProfileImage pubkey={pubkey} />
{options.about ? <div className="f-ellipsis">
{user?.about}
</div> : null}
<ProfileImage pubkey={pubkey} subHeader=
{options.about ? <div className="f-ellipsis about">
{user?.about}
</div> : null} />
<FollowButton pubkey={pubkey} className="ml5" />
</div>
)

View File

@ -15,7 +15,7 @@ export default function useLoginFeed() {
const [pubKey, readNotifications] = useSelector(s => [s.login.publicKey, s.login.readNotifications]);
const sub = useMemo(() => {
if (pubKey === null) {
if (!pubKey) {
return null;
}

View File

@ -7,7 +7,7 @@ export default function useProfile(pubKey) {
const user = useSelector(s => s.users.users[pubKey]);
useEffect(() => {
if (pubKey !== "") {
if (pubKey) {
dispatch(addPubKey(pubKey));
}
}, [pubKey]);

View File

@ -139,6 +139,11 @@ textarea:placeholder {
.f-grow {
flex-grow: 1;
min-width: 0;
}
.f-shrink {
flex-shrink: 1;
}
.f-ellipsis {
@ -146,7 +151,6 @@ textarea:placeholder {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding: 0 15px;
}
.f-col {
@ -161,6 +165,13 @@ textarea:placeholder {
width: fill-available;
}
.w-max-w {
max-width: 100%;
max-width: -moz-available;
max-width: -webkit-fill-available;
max-width: fill-available;
}
a {
color: inherit;
line-height: 1.3em;

View File

@ -4,9 +4,8 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux'
import {
BrowserRouter as Router,
Routes,
Route,
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import { NostrSystem } from './nostr/System';
@ -19,28 +18,55 @@ import Store from "./state/Store";
import NotificationsPage from './pages/Notifications';
import NewUserPage from './pages/NewUserPage';
import SettingsPage from './pages/SettingsPage';
import ErrorPage from './pages/ErrorPage.tsx';
/**
* Nostr websocket managment system
*/
export const System = new NostrSystem();
const router = createBrowserRouter([
{
element: <Layout />,
errorElement: <ErrorPage />,
children: [
{
path: "/",
element: <RootPage />
},
{
path: "/login",
element: <LoginPage />
},
{
path: "/e/:id",
element: <EventPage />
},
{
path: "/p/:id",
element: <ProfilePage />
},
{
path: "/notifications",
element: <NotificationsPage />
},
{
path: "/new",
element: <NewUserPage />
},
{
path: "/settings",
element: <SettingsPage />
}
]
}
]);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={Store}>
<Router>
<Layout>
<Routes>
<Route path="/" exact element={<RootPage />} />
<Route path="/login" exact element={<LoginPage />} />
<Route path="/e/:id" exact element={<EventPage />} />
<Route path="/p/:id" exact element={<ProfilePage />} />
<Route path="/notifications" exact element={<NotificationsPage />} />
<Route path="/new" exact element={<NewUserPage />} />
<Route path="/settings" exact element={<SettingsPage />}>
<Route path="/settings/relays" exact element={<h2>Relays</h2>} />
</Route>
</Routes>
</Layout>
</Router>
<RouterProvider router={router} />
</Provider>
</React.StrictMode>
);

18
src/pages/ErrorPage.tsx Normal file
View File

@ -0,0 +1,18 @@
import React, { FC } from "react";
import { useRouteError } from "react-router-dom";
const ErrorPage: FC = () => {
const error = useRouteError();
console.error(error);
return (
<>
<h4>{error?.message ?? "Uknown error"}</h4>
<pre>
{JSON.stringify(error)}
</pre>
</>
);
};
export default ErrorPage;

View File

@ -1,7 +1,7 @@
import "./Layout.css";
import { useEffect } from "react"
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Outlet, useNavigate } from "react-router-dom";
import { faBell } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -85,7 +85,7 @@ export default function Layout(props) {
</div>
</div>
{props.children}
<Outlet/>
</div>
)
}

View File

@ -1,14 +1,19 @@
import { Outlet } from "react-router-dom";
import { RecommendedFollows } from "../Const";
import ProfilePreview from "../element/ProfilePreview";
export default function NewUserPage(props) {
return (
<>
<h2>Hmm you're not following anybody?</h2>
<h4>Here are some suggestions:</h4>
{RecommendedFollows
.sort(a => Math.random() >= 0.5 ? -1 : 1)
.map(a => <ProfilePreview key={a} pubkey={a.toLowerCase()} />)}
</>
)
function followSomebody() {
return (
<>
<h2>Follow some popular accounts</h2>
<h4>Here are some suggestions:</h4>
{RecommendedFollows
.sort(a => Math.random() >= 0.5 ? -1 : 1)
.map(a => <ProfilePreview key={a} pubkey={a.toLowerCase()} />)}
</>
)
}
return followSomebody()
}

View File

@ -17,7 +17,7 @@ export default function RootPage() {
const [eop] = useScroll();
function followHints() {
if (follows?.length === 0 && pubKey) {
if (follows?.length === 0 && pubKey && tab !== RootTab.Global) {
return <>
Hmm nothing here.. Checkout <Link to={"/new"}>New users page</Link> to follow some recommended nostrich's!
</>

View File

@ -3,6 +3,7 @@ import Nostrich from "../nostrich.jpg";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import useEventPublisher from "../feed/EventPublisher";
import useProfile from "../feed/ProfileFeed";
@ -13,6 +14,7 @@ import { openFile } from "../Util";
import Relay from "../element/Relay";
export default function SettingsPage(props) {
const navigate = useNavigate();
const id = useSelector(s => s.login.publicKey);
const relays = useSelector(s => s.login.relays);
const dispatch = useDispatch();
@ -137,7 +139,7 @@ export default function SettingsPage(props) {
</div>
<div className="form-group">
<div>
<div className="btn" onClick={() => dispatch(logout())}>Logout</div>
<div className="btn" onClick={() => { dispatch(logout()); navigate("/"); }}>Logout</div>
</div>
<div>
<div className="btn" onClick={() => saveProfile()}>Save</div>
@ -159,15 +161,24 @@ export default function SettingsPage(props) {
)
}
function settings() {
if (!id) return null;
return (
<>
<h1>Settings</h1>
<div className="flex f-center">
<div style={{ backgroundImage: `url(${picture.length === 0 ? Nostrich : picture})` }} className="avatar">
<div className="edit" onClick={() => setNewAvatar()}>Edit</div>
</div>
</div>
{editor()}
</>
)
}
return (
<div className="settings">
<h1>Settings</h1>
<div className="flex f-center">
<div style={{ backgroundImage: `url(${picture.length === 0 ? Nostrich : picture})` }} className="avatar">
<div className="edit" onClick={() => setNewAvatar()}>Edit</div>
</div>
</div>
{editor()}
{settings()}
<h4>Relays</h4>
<div className="flex f-col">
{Object.keys(relays || {}).map(a => <Relay addr={a} key={a} />)}

View File

@ -1,5 +1,6 @@
import { createSlice } from '@reduxjs/toolkit'
import * as secp from '@noble/secp256k1';
import { DefaultRelays } from '../Const';
const PrivateKeyItem = "secret";
const PublicKeyItem = "pubkey";
@ -54,11 +55,7 @@ const LoginSlice = createSlice({
state.loggedOut = true;
}
state.relays = {
"wss://nostr.v0l.io": { read: true, write: true },
"wss://relay.damus.io": { read: true, write: true },
"wss://nostr-pub.wellorder.net": { read: true, write: true }
};
state.relays = DefaultRelays;
// check pub key only
let pubKey = window.localStorage.getItem(PublicKeyItem);

692
yarn.lock

File diff suppressed because it is too large Load Diff