review / cleanup

This commit is contained in:
2023-02-09 12:26:54 +00:00
parent a03b385e55
commit dbae89837f
129 changed files with 678 additions and 2303 deletions

View File

@ -9,40 +9,30 @@ import { bech32ToHex } from "Util";
import useEventPublisher from "Feed/EventPublisher";
import DM from "Element/DM";
import { RawEvent, TaggedRawEvent } from "Nostr";
import { TaggedRawEvent } from "Nostr";
import { dmsInChat, isToSelf } from "Pages/MessagesPage";
import NoteToSelf from "Element/NoteToSelf";
import { RootState } from "State/Store";
type RouterParams = {
id: string;
};
interface State {
login: {
dms: TaggedRawEvent[];
};
}
export default function ChatPage() {
const params = useParams<RouterParams>();
const publisher = useEventPublisher();
const id = bech32ToHex(params.id ?? "");
const pubKey = useSelector<{ login: { publicKey: string } }>(
(s) => s.login.publicKey
);
const dms = useSelector<State, RawEvent[]>((s) => filterDms(s.login.dms));
const pubKey = useSelector((s: RootState) => s.login.publicKey);
const dms = useSelector((s: RootState) => filterDms(s.login.dms));
const [content, setContent] = useState<string>();
const { ref, inView } = useInView();
const dmListRef = useRef<HTMLDivElement>(null);
function filterDms(dms: TaggedRawEvent[]) {
return dmsInChat(
id === pubKey ? dms.filter((d) => isToSelf(d, pubKey)) : dms,
id
);
return dmsInChat(id === pubKey ? dms.filter(d => isToSelf(d, pubKey)) : dms, id);
}
const sortedDms = useMemo<RawEvent[]>(() => {
const sortedDms = useMemo(() => {
return [...dms].sort((a, b) => a.created_at - b.created_at);
}, [dms]);
@ -70,13 +60,12 @@ export default function ChatPage() {
return (
<>
{(id === pubKey && (
<NoteToSelf className="f-grow mb-10" pubkey={id} />
)) || <ProfileImage pubkey={id} className="f-grow mb10" />}
{(id === pubKey && <NoteToSelf className="f-grow mb-10" pubkey={id} />) || (
<ProfileImage pubkey={id} className="f-grow mb10" />
)}
<div className="dm-list" ref={dmListRef}>
<div>
{/* TODO I need to look into this again, something's being bricked with the RawEvent and TaggedRawEvent */}
{sortedDms.map((a) => (
{sortedDms.map(a => (
<DM data={a as TaggedRawEvent} key={a.id} />
))}
<div ref={ref} className="mb10"></div>
@ -87,9 +76,8 @@ export default function ChatPage() {
<textarea
className="f-grow mr10"
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyDown={(e) => onEnter(e)}
></textarea>
onChange={e => setContent(e.target.value)}
onKeyDown={e => onEnter(e)}></textarea>
<button type="button" onClick={() => sendDm()}>
Send
</button>

View File

@ -7,27 +7,15 @@ import { bech32ToHex } from "Util";
const Developers = [
bech32ToHex(KieranPubKey), // kieran
bech32ToHex(
"npub107jk7htfv243u0x5ynn43scq9wrxtaasmrwwa8lfu2ydwag6cx2quqncxg"
), // verbiricha
bech32ToHex(
"npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"
), // Karnage
bech32ToHex("npub107jk7htfv243u0x5ynn43scq9wrxtaasmrwwa8lfu2ydwag6cx2quqncxg"), // verbiricha
bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"), // Karnage
];
const Contributors = [
bech32ToHex(
"npub10djxr5pvdu97rjkde7tgcsjxzpdzmdguwacfjwlchvj7t88dl7nsdl54nf"
), // ivan
bech32ToHex(
"npub148jmlutaa49y5wl5mcll003ftj59v79vf7wuv3apcwpf75hx22vs7kk9ay"
), // liran cohen
bech32ToHex(
"npub1xdtducdnjerex88gkg2qk2atsdlqsyxqaag4h05jmcpyspqt30wscmntxy"
), // artur
bech32ToHex(
"npub1vp8fdcyejd4pqjyrjk9sgz68vuhq7pyvnzk8j0ehlljvwgp8n6eqsrnpsw"
), // samsamskies
bech32ToHex("npub10djxr5pvdu97rjkde7tgcsjxzpdzmdguwacfjwlchvj7t88dl7nsdl54nf"), // ivan
bech32ToHex("npub148jmlutaa49y5wl5mcll003ftj59v79vf7wuv3apcwpf75hx22vs7kk9ay"), // liran cohen
bech32ToHex("npub1xdtducdnjerex88gkg2qk2atsdlqsyxqaag4h05jmcpyspqt30wscmntxy"), // artur
bech32ToHex("npub1vp8fdcyejd4pqjyrjk9sgz68vuhq7pyvnzk8j0ehlljvwgp8n6eqsrnpsw"), // samsamskies
];
interface Splits {
@ -60,7 +48,7 @@ const DonatePage = () => {
}, []);
function actions(pk: HexKey) {
const split = splits.find((a) => bech32ToHex(a.pubKey) === pk);
const split = splits.find(a => bech32ToHex(a.pubKey) === pk);
if (split) {
return <>{(100 * split.split).toLocaleString()}%</>;
}
@ -70,44 +58,29 @@ const DonatePage = () => {
return (
<div className="main-content m5">
<h2>Help fund the development of Snort</h2>
<p>
Snort is an open source project built by passionate people in their free
time
</p>
<p>Snort is an open source project built by passionate people in their free time</p>
<p>Your donations are greatly appreciated</p>
<p>
Check out the code here:{" "}
<a
className="highlight"
href="https://github.com/v0l/snort"
rel="noreferrer"
target="_blank"
>
<a className="highlight" href="https://github.com/v0l/snort" rel="noreferrer" target="_blank">
https://github.com/v0l/snort
</a>
</p>
<p>
Each contributor will get paid a percentage of all donations and NIP-05
orders, you can see the split amounts below
Each contributor will get paid a percentage of all donations and NIP-05 orders, you can see the split amounts
below
</p>
<div className="flex">
<div className="mr10">Lightning Donation: </div>
<ZapButton
pubkey={bech32ToHex(SnortPubKey)}
svc={"donate@snort.social"}
/>
<ZapButton pubkey={bech32ToHex(SnortPubKey)} svc={"donate@snort.social"} />
</div>
{today && (
<small>
Total today (UTC): {today.donations.toLocaleString()} sats
</small>
)}
{today && <small>Total today (UTC): {today.donations.toLocaleString()} sats</small>}
<h3>Primary Developers</h3>
{Developers.map((a) => (
{Developers.map(a => (
<ProfilePreview pubkey={a} key={a} actions={actions(a)} />
))}
<h4>Contributors</h4>
{Contributors.map((a) => (
{Contributors.map(a => (
<ProfilePreview pubkey={a} key={a} actions={actions(a)} />
))}
</div>

View File

@ -1,10 +1,9 @@
import { useParams } from "react-router-dom";
import Timeline from "Element/Timeline";
import { unwrap } from "Util";
const HashTagsPage = () => {
const params = useParams();
const tag = unwrap(params.tag).toLowerCase();
const tag = (params.tag ?? "").toLowerCase();
return (
<>

View File

@ -6,8 +6,7 @@ export default function HelpPage() {
<>
<h2>NIP-05</h2>
<p>
If you have an enquiry about your NIP-05 order please DM{" "}
<Link to={`/messages/${KieranPubKey}`}>Kieran</Link>
If you have an enquiry about your NIP-05 order please DM <Link to={`/messages/${KieranPubKey}`}>Kieran</Link>
</p>
</>
);

View File

@ -29,16 +29,8 @@ export default function Layout() {
const [show, setShow] = useState(false);
const dispatch = useDispatch();
const navigate = useNavigate();
const {
loggedOut,
publicKey,
relays,
latestNotification,
readNotifications,
dms,
preferences,
newUserKey,
} = useSelector((s: RootState) => s.login);
const { loggedOut, publicKey, relays, latestNotification, readNotifications, dms, preferences, newUserKey } =
useSelector((s: RootState) => s.login);
const { isMuted } = useModeration();
const usingDb = useDb();
@ -47,7 +39,7 @@ export default function Layout() {
const shouldHideNoteCreator = useMemo(() => {
const hideNoteCreator = ["/settings", "/messages", "/new"];
return hideNoteCreator.some((a) => location.pathname.startsWith(a));
return hideNoteCreator.some(a => location.pathname.startsWith(a));
}, [location]);
const hasNotifications = useMemo(
@ -58,7 +50,7 @@ export default function Layout() {
() =>
publicKey
? totalUnread(
dms.filter((a) => !isMuted(a.pubkey)),
dms.filter(a => !isMuted(a.pubkey)),
publicKey
)
: 0,
@ -98,14 +90,10 @@ export default function Layout() {
useEffect(() => {
const osTheme = window.matchMedia("(prefers-color-scheme: light)");
setTheme(
preferences.theme === "system" && osTheme.matches
? "light"
: preferences.theme === "light"
? "light"
: "dark"
preferences.theme === "system" && osTheme.matches ? "light" : preferences.theme === "light" ? "light" : "dark"
);
osTheme.onchange = (e) => {
osTheme.onchange = e => {
if (preferences.theme === "system") {
setTheme(e.matches ? "light" : "dark");
}
@ -117,7 +105,7 @@ export default function Layout() {
useEffect(() => {
// check DB support then init
IndexedUDB.isAvailable().then(async (a) => {
IndexedUDB.isAvailable().then(async a => {
const dbType = a ? "indexdDb" : "redux";
// cleanup on load
@ -145,14 +133,9 @@ export default function Layout() {
const rsp = await fetch("https://api.nostr.watch/v1/online");
if (rsp.ok) {
const online: string[] = await rsp.json();
const pickRandom = online
.sort(() => (Math.random() >= 0.5 ? 1 : -1))
.slice(0, 4); // pick 4 random relays
const pickRandom = online.sort(() => (Math.random() >= 0.5 ? 1 : -1)).slice(0, 4); // pick 4 random relays
const relayObjects = pickRandom.map((a) => [
a,
{ read: true, write: true },
]);
const relayObjects = pickRandom.map(a => [a, { read: true, write: true }]);
newRelays = Object.fromEntries(relayObjects);
dispatch(
setRelays({
@ -233,19 +216,10 @@ export default function Layout() {
{!shouldHideNoteCreator && (
<>
<button
className="note-create-button"
type="button"
onClick={() => setShow(!show)}
>
<button className="note-create-button" type="button" onClick={() => setShow(!show)}>
<Plus />
</button>
<NoteCreator
replyTo={undefined}
autoFocus={true}
show={show}
setShow={setShow}
/>
<NoteCreator replyTo={undefined} autoFocus={true} show={show} setShow={setShow} />
</>
)}
</div>

View File

@ -4,12 +4,7 @@ import { useNavigate } from "react-router-dom";
import * as secp from "@noble/secp256k1";
import { RootState } from "State/Store";
import {
setPrivateKey,
setPublicKey,
setRelays,
setGeneratedPrivateKey,
} from "State/Login";
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
import { DefaultRelays, EmailRegex } from "Const";
import { bech32ToHex } from "Util";
import { HexKey } from "Nostr";
@ -17,9 +12,7 @@ import { HexKey } from "Nostr";
export default function LoginPage() {
const dispatch = useDispatch();
const navigate = useNavigate();
const publicKey = useSelector<RootState, HexKey | undefined>(
(s) => s.login.publicKey
);
const publicKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const [key, setKey] = useState("");
const [error, setError] = useState("");
@ -31,11 +24,7 @@ export default function LoginPage() {
async function getNip05PubKey(addr: string) {
const [username, domain] = addr.split("@");
const rsp = await fetch(
`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(
username
)}`
);
const rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`);
if (rsp.ok) {
const data = await rsp.json();
const pKey = data.names[username];
@ -124,7 +113,7 @@ export default function LoginPage() {
type="text"
placeholder="nsec / npub / nip-05 / hex private key..."
className="f-grow"
onChange={(e) => setKey(e.target.value)}
onChange={e => setKey(e.target.value)}
/>
</div>
{error.length > 0 ? <b className="error">{error}</b> : null}

View File

@ -21,18 +21,14 @@ type DmChat = {
export default function MessagesPage() {
const dispatch = useDispatch();
const myPubKey = useSelector<RootState, HexKey | undefined>(
(s) => s.login.publicKey
);
const dms = useSelector<RootState, RawEvent[]>((s) => s.login.dms);
const dmInteraction = useSelector<RootState, number>(
(s) => s.login.dmInteraction
);
const myPubKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const dms = useSelector<RootState, RawEvent[]>(s => s.login.dms);
const dmInteraction = useSelector<RootState, number>(s => s.login.dmInteraction);
const { isMuted } = useModeration();
const chats = useMemo(() => {
return extractChats(
dms.filter((a) => !isMuted(a.pubkey)),
dms.filter(a => !isMuted(a.pubkey)),
myPubKey ?? ""
);
}, [dms, myPubKey, dmInteraction]);
@ -54,11 +50,7 @@ export default function MessagesPage() {
if (chat.pubkey === myPubKey) return noteToSelf(chat);
return (
<div className="flex mb10" key={chat.pubkey}>
<ProfileImage
pubkey={chat.pubkey}
className="f-grow"
link={`/messages/${hexToBech32("npub", chat.pubkey)}`}
/>
<ProfileImage pubkey={chat.pubkey} className="f-grow" link={`/messages/${hexToBech32("npub", chat.pubkey)}`} />
<UnreadCount unread={chat.unreadMessages} />
</div>
);
@ -83,11 +75,7 @@ export default function MessagesPage() {
</div>
{chats
.sort((a, b) => {
return a.pubkey === myPubKey
? -1
: b.pubkey === myPubKey
? 1
: b.newestMessage - a.newestMessage;
return a.pubkey === myPubKey ? -1 : b.pubkey === myPubKey ? 1 : b.newestMessage - a.newestMessage;
})
.map(person)}
</div>
@ -111,7 +99,7 @@ export function setLastReadDm(pk: HexKey) {
}
export function dmTo(e: RawEvent) {
const firstP = e.tags.find((b) => b[0] === "p");
const firstP = e.tags.find(b => b[0] === "p");
return firstP ? firstP[1] : "";
}
@ -120,42 +108,34 @@ export function isToSelf(e: RawEvent, pk: HexKey) {
}
export function dmsInChat(dms: RawEvent[], pk: HexKey) {
return dms.filter((a) => a.pubkey === pk || dmTo(a) === pk);
return dms.filter(a => a.pubkey === pk || dmTo(a) === pk);
}
export function totalUnread(dms: RawEvent[], myPubKey: HexKey) {
return extractChats(dms, myPubKey).reduce(
(acc, v) => (acc += v.unreadMessages),
0
);
return extractChats(dms, myPubKey).reduce((acc, v) => (acc += v.unreadMessages), 0);
}
function unreadDms(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
if (pk === myPubKey) return 0;
const lastRead = lastReadDm(pk);
return dmsInChat(dms, pk).filter(
(a) => a.created_at >= lastRead && a.pubkey !== myPubKey
).length;
return dmsInChat(dms, pk).filter(a => a.created_at >= lastRead && a.pubkey !== myPubKey).length;
}
function newestMessage(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
if (pk === myPubKey) {
return dmsInChat(
dms.filter((d) => isToSelf(d, myPubKey)),
dms.filter(d => isToSelf(d, myPubKey)),
pk
).reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
}
return dmsInChat(dms, pk).reduce(
(acc, v) => (acc = v.created_at > acc ? v.created_at : acc),
0
);
return dmsInChat(dms, pk).reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
}
export function extractChats(dms: RawEvent[], myPubKey: HexKey) {
const keys = dms.map((a) => [a.pubkey, dmTo(a)]).flat();
const keys = dms.map(a => [a.pubkey, dmTo(a)]).flat();
const filteredKeys = Array.from(new Set<string>(keys));
return filteredKeys.map((a) => {
return filteredKeys.map(a => {
return {
pubkey: a,
unreadMessages: unreadDms(dms, myPubKey, a),

View File

@ -7,9 +7,7 @@ import Timeline from "Element/Timeline";
export default function NotificationsPage() {
const dispatch = useDispatch();
const pubkey = useSelector<RootState, HexKey | undefined>(
(s) => s.login.publicKey
);
const pubkey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
useEffect(() => {
dispatch(markNotificationsRead());

View File

@ -53,13 +53,9 @@ export default function ProfilePage() {
const navigate = useNavigate();
const id = useMemo(() => parseId(params.id ?? ""), [params]);
const user = useUserProfile(id);
const loggedOut = useSelector<RootState, boolean | undefined>(
(s) => s.login.loggedOut
);
const loginPubKey = useSelector<RootState, HexKey | undefined>(
(s) => s.login.publicKey
);
const follows = useSelector<RootState, HexKey[]>((s) => s.login.follows);
const loggedOut = useSelector<RootState, boolean | undefined>(s => s.login.loggedOut);
const loginPubKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const follows = useSelector<RootState, HexKey[]>(s => s.login.follows);
const isMe = loginPubKey === id;
const [showLnQr, setShowLnQr] = useState<boolean>(false);
const [showProfileQr, setShowProfileQr] = useState<boolean>(false);
@ -72,14 +68,10 @@ export default function ProfilePage() {
});
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
const website_url =
user?.website && !user.website.startsWith("http")
? "https://" + user.website
: user?.website || "";
user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || "";
const zapFeed = useZapsFeed(id);
const zaps = useMemo(() => {
const profileZaps = zapFeed.store.notes
.map(parseZap)
.filter((z) => z.valid && z.p === id && !z.e && z.zapper !== id);
const profileZaps = zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e && z.zapper !== id);
profileZaps.sort((a, b) => b.amount - a.amount);
return profileZaps;
}, [zapFeed.store, id]);
@ -178,12 +170,9 @@ export default function ProfilePage() {
return (
<div className="main-content">
<h4 className="zaps-total">
<FormattedMessage
{...messages.Sats}
values={{ n: formatShort(zapsTotal) }}
/>
<FormattedMessage {...messages.Sats} values={{ n: formatShort(zapsTotal) }} />
</h4>
{zaps.map((z) => (
{zaps.map(z => (
<ZapElement showZapped={false} zap={z} />
))}
</div>
@ -195,17 +184,10 @@ export default function ProfilePage() {
return (
<div className="main-content">
<h4>
<FormattedMessage
{...messages.Following}
values={{ n: follows.length }}
/>
<FormattedMessage {...messages.Following} values={{ n: follows.length }} />
</h4>
{follows.map((a) => (
<ProfilePreview
key={a}
pubkey={a.toLowerCase()}
options={{ about: false }}
/>
{follows.map(a => (
<ProfilePreview key={a} pubkey={a.toLowerCase()} options={{ about: false }} />
))}
</div>
);
@ -242,11 +224,7 @@ export default function ProfilePage() {
{showProfileQr && (
<Modal className="qr-modal" onClose={() => setShowProfileQr(false)}>
<ProfileImage pubkey={id} />
<QrCode
data={`nostr:${hexToBech32("npub", id)}`}
link={undefined}
className="m10"
/>
<QrCode data={`nostr:${hexToBech32("npub", id)}`} link={undefined} className="m10" />
</Modal>
)}
{isMe ? (
@ -265,11 +243,7 @@ export default function ProfilePage() {
)}
{!loggedOut && (
<>
<IconButton
onClick={() =>
navigate(`/messages/${hexToBech32("npub", id)}`)
}
>
<IconButton onClick={() => navigate(`/messages/${hexToBech32("npub", id)}`)}>
<Envelope width={16} height={13} />
</IconButton>
</>
@ -301,27 +275,14 @@ export default function ProfilePage() {
return (
<>
<div className="profile flex">
{user?.banner && (
<ProxyImg
alt="banner"
className="banner"
src={user.banner}
size={w}
/>
)}
{user?.banner && <ProxyImg alt="banner" className="banner" src={user.banner} size={w} />}
<div className="profile-wrapper flex">
{avatar()}
{userDetails()}
</div>
</div>
<div className="tabs main-content" ref={horizontalScroll}>
{[
ProfileTab.Notes,
ProfileTab.Followers,
ProfileTab.Follows,
ProfileTab.Zaps,
ProfileTab.Muted,
].map(renderTab)}
{[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Zaps, ProfileTab.Muted].map(renderTab)}
{isMe && renderTab(ProfileTab.Blocked)}
</div>
{tabContent()}

View File

@ -28,10 +28,9 @@ const RootTab: Record<string, Tab> = {
};
export default function RootPage() {
const [loggedOut, pubKey, follows] = useSelector<
RootState,
[boolean | undefined, HexKey | undefined, HexKey[]]
>((s) => [s.login.loggedOut, s.login.publicKey, s.login.follows]);
const [loggedOut, pubKey, follows] = useSelector<RootState, [boolean | undefined, HexKey | undefined, HexKey[]]>(
s => [s.login.loggedOut, s.login.publicKey, s.login.follows]
);
const [tab, setTab] = useState<Tab>(RootTab.Posts);
function followHints() {
@ -58,13 +57,7 @@ export default function RootPage() {
return (
<>
<div className="main-content">
{pubKey && (
<Tabs
tabs={[RootTab.Posts, RootTab.PostsAndReplies, RootTab.Global]}
tab={tab}
setTab={setTab}
/>
)}
{pubKey && <Tabs tabs={[RootTab.Posts, RootTab.PostsAndReplies, RootTab.Global]} tab={tab} setTab={setTab} />}
</div>
{followHints()}
<Timeline

View File

@ -52,7 +52,7 @@ const SearchPage = () => {
className="f-grow mr10"
placeholder={formatMessage(messages.SearchPlaceholder)}
value={search}
onChange={(e) => setSearch(e.target.value)}
onChange={e => setSearch(e.target.value)}
/>
</div>
{keyword && (

View File

@ -48,7 +48,7 @@ export default function VerificationPage() {
</li>
</ul>
{services.map((a) => (
{services.map(a => (
<Nip5Service key={a.name} {...a} />
))}
</div>

View File

@ -7,8 +7,7 @@ const messages = defineMessages({
Conversations: "Conversations",
Global: "Global",
NewUsers: "New users page",
NoFollows:
"Hmm nothing here.. Checkout {newUsersPage} to follow some recommended nostrich's!",
NoFollows: "Hmm nothing here.. Checkout {newUsersPage} to follow some recommended nostrich's!",
Notes: "Notes",
Reactions: "Reactions",
Followers: "Followers",
@ -28,8 +27,7 @@ const messages = defineMessages({
Nip05Pros: `Getting NIP-05 verified can help:`,
AvoidImpersonators: "Prevent fake accounts from imitating you",
EasierToFind: "Make your profile easier to find and share",
Funding:
"Fund developers and platforms providing NIP-05 verification services",
Funding: "Fund developers and platforms providing NIP-05 verification services",
SnortSocialNip: `Our very own NIP-05 verification service, help support the development of this site and get a shiny special badge on our site!`,
NostrPlebsNip: `Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices`,
});

View File

@ -14,9 +14,7 @@ export default function DiscoverFollows() {
<>
<h2>Follow some popular accounts</h2>
<button onClick={() => navigate("/")}>Skip</button>
{sortedRecommends.length > 0 && (
<FollowListBase pubkeys={sortedRecommends} />
)}
{sortedRecommends.length > 0 && <FollowListBase pubkeys={sortedRecommends} />}
<button onClick={() => navigate("/")}>Done!</button>
</>
);

View File

@ -18,18 +18,14 @@ export default function ImportFollows() {
const [error, setError] = useState<string>("");
const sortedTwitterFollows = useMemo(() => {
return follows
.map((a) => bech32ToHex(a))
.sort((a) => (currentFollows.includes(a) ? 1 : -1));
return follows.map(a => bech32ToHex(a)).sort(a => (currentFollows.includes(a) ? 1 : -1));
}, [follows, currentFollows]);
async function loadFollows() {
setFollows([]);
setError("");
try {
const rsp = await fetch(
`${TwitterFollowsApi}?username=${twitterUsername}`
);
const rsp = await fetch(`${TwitterFollowsApi}?username=${twitterUsername}`);
const data = await rsp.json();
if (rsp.ok) {
if (Array.isArray(data) && data.length === 0) {
@ -64,14 +60,12 @@ export default function ImportFollows() {
placeholder="Twitter username.."
className="f-grow mr10"
value={twitterUsername}
onChange={(e) => setTwitterUsername(e.target.value)}
onChange={e => setTwitterUsername(e.target.value)}
/>
<AsyncButton onClick={loadFollows}>Check</AsyncButton>
</div>
{error.length > 0 && <b className="error">{error}</b>}
{sortedTwitterFollows.length > 0 && (
<FollowListBase pubkeys={sortedTwitterFollows} />
)}
{sortedTwitterFollows.length > 0 && <FollowListBase pubkeys={sortedTwitterFollows} />}
<button onClick={() => navigate("/new/discover")}>Next</button>
</>

View File

@ -34,44 +34,33 @@ export default function NewUserFlow() {
return (
<>
<h1>Welcome to Snort!</h1>
<p>
Snort is a Nostr UI, nostr is a decentralised protocol for saving and
distributing "notes".
</p>
<p>
Notes hold text content, the most popular usage of these notes is to
store "tweet like" messages.
</p>
<p>Snort is a Nostr UI, nostr is a decentralised protocol for saving and distributing "notes".</p>
<p>Notes hold text content, the most popular usage of these notes is to store "tweet like" messages.</p>
<p>Snort is designed to have a similar experience to Twitter.</p>
<h2>Keys</h2>
<p>
Nostr uses digital signature technology to provide tamper proof notes
which can safely be replicated to many relays to provide redundant
storage of your content.
Nostr uses digital signature technology to provide tamper proof notes which can safely be replicated to many
relays to provide redundant storage of your content.
</p>
<p>
This means that nobody can modify notes which you have created and
everybody can easily verify that the notes they are reading are created
by you.
</p>
<p>
This is the same technology which is used by Bitcoin and has been proven
to be extremely secure.
This means that nobody can modify notes which you have created and everybody can easily verify that the notes
they are reading are created by you.
</p>
<p>This is the same technology which is used by Bitcoin and has been proven to be extremely secure.</p>
<h2>Your Key</h2>
<p>
When you want to author new notes you need to sign them with your
private key, as with Bitcoin private keys these need to be kept secure.
When you want to author new notes you need to sign them with your private key, as with Bitcoin private keys
these need to be kept secure.
</p>
<p>Please now copy your private key and save it somewhere secure:</p>
<div className="card">
<Copy text={hexToBech32("nsec", privateKey ?? "")} />
</div>
<p>
It is also recommended to use one of the following browser extensions if
you are on a desktop computer to secure your key:
It is also recommended to use one of the following browser extensions if you are on a desktop computer to secure
your key:
</p>
<ul>
<li>
@ -80,11 +69,7 @@ export default function NewUserFlow() {
</a>
</li>
<li>
<a
href="https://github.com/fiatjaf/nos2x"
target="_blank"
rel="noreferrer"
>
<a href="https://github.com/fiatjaf/nos2x" target="_blank" rel="noreferrer">
nos2x
</a>
</li>

View File

@ -12,9 +12,7 @@ import "./Preferences.css";
const PreferencesPage = () => {
const dispatch = useDispatch();
const perf = useSelector<RootState, UserPreferences>(
(s) => s.login.preferences
);
const perf = useSelector<RootState, UserPreferences>(s => s.login.preferences);
return (
<div className="preferences">
@ -31,15 +29,14 @@ const PreferencesPage = () => {
<div>
<select
value={perf.theme}
onChange={(e) =>
onChange={e =>
dispatch(
setPreferences({
...perf,
theme: e.target.value,
} as UserPreferences)
)
}
>
}>
<option value="system">
<FormattedMessage {...messages.System} />
</option>
@ -64,15 +61,14 @@ const PreferencesPage = () => {
<div>
<select
value={perf.autoLoadMedia}
onChange={(e) =>
onChange={e =>
dispatch(
setPreferences({
...perf,
autoLoadMedia: e.target.value,
} as UserPreferences)
)
}
>
}>
<option value="none">
<FormattedMessage {...messages.None} />
</option>
@ -99,7 +95,7 @@ const PreferencesPage = () => {
<input
type="checkbox"
checked={perf.imgProxyConfig !== null}
onChange={(e) =>
onChange={e =>
dispatch(
setPreferences({
...perf,
@ -121,7 +117,7 @@ const PreferencesPage = () => {
type="text"
value={perf.imgProxyConfig?.url}
placeholder="URL.."
onChange={(e) =>
onChange={e =>
dispatch(
setPreferences({
...perf,
@ -144,7 +140,7 @@ const PreferencesPage = () => {
type="password"
value={perf.imgProxyConfig?.key}
placeholder="Hex key.."
onChange={(e) =>
onChange={e =>
dispatch(
setPreferences({
...perf,
@ -167,7 +163,7 @@ const PreferencesPage = () => {
type="password"
value={perf.imgProxyConfig?.salt}
placeholder="Hex salt.."
onChange={(e) =>
onChange={e =>
dispatch(
setPreferences({
...perf,
@ -197,11 +193,7 @@ const PreferencesPage = () => {
<input
type="checkbox"
checked={perf.enableReactions}
onChange={(e) =>
dispatch(
setPreferences({ ...perf, enableReactions: e.target.checked })
)
}
onChange={e => dispatch(setPreferences({ ...perf, enableReactions: e.target.checked }))}
/>
</div>
</div>
@ -218,11 +210,7 @@ const PreferencesPage = () => {
<input
type="checkbox"
checked={perf.confirmReposts}
onChange={(e) =>
dispatch(
setPreferences({ ...perf, confirmReposts: e.target.checked })
)
}
onChange={e => dispatch(setPreferences({ ...perf, confirmReposts: e.target.checked }))}
/>
</div>
</div>
@ -239,11 +227,7 @@ const PreferencesPage = () => {
<input
type="checkbox"
checked={perf.autoShowLatest}
onChange={(e) =>
dispatch(
setPreferences({ ...perf, autoShowLatest: e.target.checked })
)
}
onChange={e => dispatch(setPreferences({ ...perf, autoShowLatest: e.target.checked }))}
/>
</div>
</div>
@ -259,15 +243,14 @@ const PreferencesPage = () => {
<div>
<select
value={perf.fileUploader}
onChange={(e) =>
onChange={e =>
dispatch(
setPreferences({
...perf,
fileUploader: e.target.value,
} as UserPreferences)
)
}
>
}>
<option value="void.cat">
void.cat <FormattedMessage {...messages.Default} />
</option>
@ -289,11 +272,7 @@ const PreferencesPage = () => {
<input
type="checkbox"
checked={perf.showDebugMenus}
onChange={(e) =>
dispatch(
setPreferences({ ...perf, showDebugMenus: e.target.checked })
)
}
onChange={e => dispatch(setPreferences({ ...perf, showDebugMenus: e.target.checked }))}
/>
</div>
</div>

View File

@ -25,12 +25,8 @@ export interface ProfileSettingsProps {
export default function ProfileSettings(props: ProfileSettingsProps) {
const navigate = useNavigate();
const id = useSelector<RootState, HexKey | undefined>(
(s) => s.login.publicKey
);
const privKey = useSelector<RootState, HexKey | undefined>(
(s) => s.login.privateKey
);
const id = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const privKey = useSelector<RootState, HexKey | undefined>(s => s.login.privateKey);
const user = useUserProfile(id ?? "");
const publisher = useEventPublisher();
const uploader = useFileUpload();
@ -118,11 +114,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<FormattedMessage {...messages.Name} />:
</div>
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input type="text" value={name} onChange={e => setName(e.target.value)} />
</div>
</div>
<div className="form-group">
@ -130,11 +122,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<FormattedMessage {...messages.DisplayName} />:
</div>
<div>
<input
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
/>
<input type="text" value={displayName} onChange={e => setDisplayName(e.target.value)} />
</div>
</div>
<div className="form-group form-col">
@ -142,11 +130,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<FormattedMessage {...messages.About} />:
</div>
<div className="w-max">
<textarea
className="w-max"
onChange={(e) => setAbout(e.target.value)}
value={about}
></textarea>
<textarea className="w-max" onChange={e => setAbout(e.target.value)} value={about}></textarea>
</div>
</div>
<div className="form-group">
@ -154,11 +138,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<FormattedMessage {...messages.Website} />:
</div>
<div>
<input
type="text"
value={website}
onChange={(e) => setWebsite(e.target.value)}
/>
<input type="text" value={website} onChange={e => setWebsite(e.target.value)} />
</div>
</div>
<div className="form-group">
@ -166,12 +146,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<FormattedMessage {...messages.Nip05} />:
</div>
<div>
<input
type="text"
className="mr10"
value={nip05}
onChange={(e) => setNip05(e.target.value)}
/>
<input type="text" className="mr10" value={nip05} onChange={e => setNip05(e.target.value)} />
<button type="button" onClick={() => navigate("/verification")}>
<FontAwesomeIcon icon={faShop} />
&nbsp; <FormattedMessage {...messages.Buy} />
@ -183,11 +158,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<FormattedMessage {...messages.LnAddress} />:
</div>
<div>
<input
type="text"
value={lud16}
onChange={(e) => setLud16(e.target.value)}
/>
<input type="text" value={lud16} onChange={e => setLud16(e.target.value)} />
</div>
</div>
<div className="form-group">
@ -212,10 +183,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<h2>
<FormattedMessage {...messages.Avatar} />
</h2>
<div
style={{ backgroundImage: `url(${avatarPicture})` }}
className="avatar"
>
<div style={{ backgroundImage: `url(${avatarPicture})` }} className="avatar">
<div className="edit" onClick={() => setNewAvatar()}>
<FormattedMessage {...messages.Edit} />
</div>
@ -229,12 +197,9 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
</h2>
<div
style={{
backgroundImage: `url(${
(banner?.length ?? 0) === 0 ? Nostrich : banner
})`,
backgroundImage: `url(${(banner?.length ?? 0) === 0 ? Nostrich : banner})`,
}}
className="banner"
>
className="banner">
<div className="edit" onClick={() => setNewBanner()}>
<FormattedMessage {...messages.Edit} />
</div>

View File

@ -14,9 +14,7 @@ const RelayInfo = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const conn = Array.from(System.Sockets.values()).find(
(a) => a.Id === params.id
);
const conn = Array.from(System.Sockets.values()).find(a => a.Id === params.id);
console.debug(conn);
const stats = useRelayState(conn?.Address ?? "");
@ -63,12 +61,9 @@ const RelayInfo = () => {
<FormattedMessage {...messages.Contact} />
</h4>
<a
href={`${
stats.info.contact.startsWith("mailto:") ? "" : "mailto:"
}${stats.info.contact}`}
href={`${stats.info.contact.startsWith("mailto:") ? "" : "mailto:"}${stats.info.contact}`}
target="_blank"
rel="noreferrer"
>
rel="noreferrer">
{stats.info.contact}
</a>
</div>
@ -79,17 +74,12 @@ const RelayInfo = () => {
<FormattedMessage {...messages.Supports} />
</h4>
<div className="f-grow">
{stats.info.supported_nips.map((a) => (
{stats.info.supported_nips.map(a => (
<span
className="pill"
onClick={() =>
navigate(
`https://github.com/nostr-protocol/nips/blob/master/${a
.toString()
.padStart(2, "0")}.md`
)
}
>
navigate(`https://github.com/nostr-protocol/nips/blob/master/${a.toString().padStart(2, "0")}.md`)
}>
NIP-{a.toString().padStart(2, "0")}
</span>
))}
@ -102,8 +92,7 @@ const RelayInfo = () => {
onClick={() => {
dispatch(removeRelay(unwrap(conn).Address));
navigate("/settings/relays");
}}
>
}}>
<FormattedMessage {...messages.Remove} />
</div>
</div>

View File

@ -13,9 +13,7 @@ import messages from "./messages";
const RelaySettingsPage = () => {
const dispatch = useDispatch();
const publisher = useEventPublisher();
const relays = useSelector<RootState, Record<string, RelaySettings>>(
(s) => s.login.relays
);
const relays = useSelector<RootState, Record<string, RelaySettings>>(s => s.login.relays);
const [newRelay, setNewRelay] = useState<string>();
async function saveRelays() {
@ -36,7 +34,7 @@ const RelaySettingsPage = () => {
className="f-grow"
placeholder="wss://my-relay.com"
value={newRelay}
onChange={(e) => setNewRelay(e.target.value)}
onChange={e => setNewRelay(e.target.value)}
/>
</div>
<button className="secondary mb10" onClick={() => addNewRelay()}>
@ -64,7 +62,7 @@ const RelaySettingsPage = () => {
<>
<h3>Relays</h3>
<div className="flex f-col mb10">
{Object.keys(relays || {}).map((a) => (
{Object.keys(relays || {}).map(a => (
<Relay addr={a} key={a} />
))}
</div>

View File

@ -28,8 +28,7 @@ const messages = defineMessages({
ServiceKey: "Service Key",
ServiceSalt: "Service Salt",
EnableReactions: "Enable reactions",
EnableReactionsHelp:
"Reactions will be shown on every page, if disabled no reactions will be shown",
EnableReactionsHelp: "Reactions will be shown on every page, if disabled no reactions will be shown",
ConfirmReposts: "Confirm Reposts",
ConfirmRepostsHelp: "Reposts need to be manually confirmed",
ShowLatest: "Automatically show latest notes",