refactor: flex styles / fixes / profile links

This commit is contained in:
Kieran 2023-10-17 14:02:59 +01:00
parent 6479a18cb2
commit faaeb6af4a
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
108 changed files with 464 additions and 508 deletions

View File

@ -10,6 +10,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 8px;
} }
.light .spinner-button { .light .spinner-button {

View File

@ -1,5 +1,5 @@
import { useState, useMemo, ChangeEvent } from "react"; import { useState, useMemo, ChangeEvent } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { HexKey, TaggedNostrEvent } from "@snort/system"; import { HexKey, TaggedNostrEvent } from "@snort/system";
import Note from "Element/Event/Note"; import Note from "Element/Event/Note";

View File

@ -1,3 +1,5 @@
import { MetadataCache } from "@snort/system";
import { ChatParticipant } from "chat"; import { ChatParticipant } from "chat";
import NoteToSelf from "../User/NoteToSelf"; import NoteToSelf from "../User/NoteToSelf";
import ProfileImage from "../User/ProfileImage"; import ProfileImage from "../User/ProfileImage";
@ -6,7 +8,7 @@ import useLogin from "Hooks/useLogin";
export function ChatParticipantProfile({ participant }: { participant: ChatParticipant }) { export function ChatParticipantProfile({ participant }: { participant: ChatParticipant }) {
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey })); const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
if (participant.id === publicKey) { if (participant.id === publicKey) {
return <NoteToSelf className="f-grow" pubkey={participant.id} />; return <NoteToSelf className="grow" />;
} }
return <ProfileImage pubkey={participant.id} className="f-grow" profile={participant.profile} />; return <ProfileImage pubkey={participant.id} className="grow" profile={participant.profile as MetadataCache} />;
} }

View File

@ -6,7 +6,7 @@ import DM from "Element/Chat/DM";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import WriteMessage from "Element/Chat/WriteMessage"; import WriteMessage from "Element/Chat/WriteMessage";
import { Chat, createEmptyChatObject, useChatSystem } from "chat"; import { Chat, createEmptyChatObject, useChatSystem } from "chat";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { ChatParticipantProfile } from "./ChatParticipant"; import { ChatParticipantProfile } from "./ChatParticipant";
export default function DmWindow({ id }: { id: string }) { export default function DmWindow({ id }: { id: string }) {
@ -32,7 +32,7 @@ export default function DmWindow({ id }: { id: string }) {
<div className="dm-window"> <div className="dm-window">
<div>{sender()}</div> <div>{sender()}</div>
<div> <div>
<div className="flex f-col">{chat && <DmChatSelected chat={chat} />}</div> <div className="flex flex-col">{chat && <DmChatSelected chat={chat} />}</div>
</div> </div>
<div> <div>
<WriteMessage chat={chat} /> <WriteMessage chat={chat} />

View File

@ -80,7 +80,7 @@ export default function WriteMessage({ chat }: { chat: Chat }) {
return ( return (
<> <>
<button className="circle flex f-center" onClick={() => attachFile()}> <button className="circle flex items-center" onClick={() => attachFile()}>
{uploading ? <Spinner width={20} /> : <Icon name="attachment" size={20} />} {uploading ? <Spinner width={20} /> : <Icon name="attachment" size={20} />}
</button> </button>
<div className="w-max"> <div className="w-max">
@ -97,7 +97,7 @@ export default function WriteMessage({ chat }: { chat: Chat }) {
/> />
{error && <b className="error">{error}</b>} {error && <b className="error">{error}</b>}
</div> </div>
<button className="circle flex f-center" onClick={() => sendMessage()}> <button className="circle flex items-center" onClick={() => sendMessage()}>
{sending ? <Spinner width={20} /> : <Icon name="arrow-right" size={20} />} {sending ? <Spinner width={20} /> : <Icon name="arrow-right" size={20} />}
</button> </button>
</> </>

View File

@ -4,8 +4,8 @@ import useLogin from "Hooks/useLogin";
import "./Nav.css"; import "./Nav.css";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { profileLink } from "SnortUtils";
import { NoteCreatorButton } from "Element/Event/NoteCreatorButton"; import { NoteCreatorButton } from "Element/Event/NoteCreatorButton";
import { ProfileLink } from "Element/User/ProfileLink";
export function DeckNav() { export function DeckNav() {
const { publicKey } = useLogin(); const { publicKey } = useLogin();
@ -14,24 +14,24 @@ export function DeckNav() {
const unreadDms = 0; const unreadDms = 0;
return ( return (
<nav className="deck flex-column f-space"> <nav className="deck flex flex-col justify-between">
<div className="flex-column f-center g24"> <div className="flex flex-col items-center g24">
<Link className="btn" to="/messages"> <Link className="btn" to="/messages">
<Icon name="mail" size={24} /> <Icon name="mail" size={24} />
{unreadDms > 0 && <span className="has-unread"></span>} {unreadDms > 0 && <span className="has-unread"></span>}
</Link> </Link>
<NoteCreatorButton /> <NoteCreatorButton />
</div> </div>
<div className="flex-column f-center g16"> <div className="flex flex-col items-center g16">
{/*<Link className="btn" to="/"> {/*<Link className="btn" to="/">
<Icon name="grid-01" size={24} /> <Icon name="grid-01" size={24} />
</Link>*/} </Link>*/}
<Link className="btn" to="/settings"> <Link className="btn" to="/settings">
<Icon name="settings-02" size={24} /> <Icon name="settings-02" size={24} />
</Link> </Link>
<Link to={profileLink(publicKey ?? "")}> <ProfileLink pubkey={publicKey ?? ""} user={profile}>
<Avatar pubkey={publicKey ?? ""} user={profile} /> <Avatar pubkey={publicKey ?? ""} user={profile} />
</Link> </ProfileLink>
</div> </div>
</nav> </nav>
); );

View File

@ -52,9 +52,9 @@ export default function CashuNuts({ token }: { token: string }) {
const amount = cashu.token[0].proofs.reduce((acc, v) => acc + v.amount, 0); const amount = cashu.token[0].proofs.reduce((acc, v) => acc + v.amount, 0);
return ( return (
<div className="cashu flex f-space p24 br"> <div className="cashu flex justify-between p24 br">
<div className="flex-column g8 f-ellipsis"> <div className="flex flex-col g8 f-ellipsis">
<div className="flex f-center g16"> <div className="flex items-center g16">
<svg width="30" height="39" viewBox="0 0 30 39" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="30" height="39" viewBox="0 0 30 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 47711"> <g id="Group 47711">
<path <path

View File

@ -21,6 +21,8 @@
padding: 0; padding: 0;
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
line-height: initial;
margin: 0.5em 0;
} }
.link-preview-container:hover .link-preview-title > h1 { .link-preview-container:hover .link-preview-title > h1 {

View File

@ -81,7 +81,7 @@ const LinkPreview = ({ url }: { url: string }) => {
</div> </div>
</a> </a>
)} )}
{!preview && <Spinner className="f-center" />} {!preview && <Spinner className="items-center" />}
</div> </div>
); );
}; };

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Magnet } from "SnortUtils"; import { Magnet } from "SnortUtils";

View File

@ -1,16 +1,17 @@
import { Link } from "react-router-dom"; import { NostrLink, NostrPrefix } from "@snort/system";
import { HexKey } from "@snort/system";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
import { profileLink } from "SnortUtils";
import DisplayName from "../User/DisplayName";
export default function Mention({ pubkey, relays }: { pubkey: HexKey; relays?: Array<string> | string }) { import DisplayName from "Element/User/DisplayName";
const user = useUserProfile(pubkey); import { ProfileLink } from "Element/User/ProfileLink";
export default function Mention({ link }: { link: NostrLink }) {
const profile = useUserProfile(link.id);
if (link.type !== NostrPrefix.Profile && link.type !== NostrPrefix.PublicKey) return;
return ( return (
<Link to={profileLink(pubkey, relays)} onClick={e => e.stopPropagation()}> <ProfileLink pubkey={link.id} user={profile} onClick={e => e.stopPropagation()}>
@<DisplayName user={user} pubkey={pubkey} /> @<DisplayName user={profile} pubkey={link.id} />
</Link> </ProfileLink>
); );
} }

View File

@ -8,7 +8,7 @@ export default function NostrLink({ link, depth }: { link: string; depth?: numbe
const nav = tryParseNostrLink(link); const nav = tryParseNostrLink(link);
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) { if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
return <Mention pubkey={nav.id} relays={nav.relays} />; return <Mention link={nav} />;
} else if (nav?.type === NostrPrefix.Note || nav?.type === NostrPrefix.Event || nav?.type === NostrPrefix.Address) { } else if (nav?.type === NostrPrefix.Note || nav?.type === NostrPrefix.Event || nav?.type === NostrPrefix.Address) {
if ((depth ?? 0) > 0) { if ((depth ?? 0) > 0) {
const evLink = nav.encode(); const evLink = nav.encode();

View File

@ -4,7 +4,7 @@ import { NostrEvent, NostrLink } from "@snort/system";
import { ProxyImg } from "Element/ProxyImg"; import { ProxyImg } from "Element/ProxyImg";
import ProfileImage from "Element/User/ProfileImage"; import ProfileImage from "Element/User/ProfileImage";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
export default function ZapstrEmbed({ ev }: { ev: NostrEvent }) { export default function ZapstrEmbed({ ev }: { ev: NostrEvent }) {
const media = ev.tags.find(a => a[0] === "media"); const media = ev.tags.find(a => a[0] === "media");
@ -17,7 +17,7 @@ export default function ZapstrEmbed({ ev }: { ev: NostrEvent }) {
<> <>
<div className="flex zapstr mb10 card"> <div className="flex zapstr mb10 card">
<ProxyImg src={cover?.[1] ?? ""} size={100} /> <ProxyImg src={cover?.[1] ?? ""} size={100} />
<div className="flex f-col"> <div className="flex flex-col">
<div> <div>
<h3>{subject?.[1] ?? ""}</h3> <h3>{subject?.[1] ?? ""}</h3>
</div> </div>

View File

@ -3,9 +3,9 @@ import { UploadProgress } from "Upload";
export default function FileUploadProgress({ progress }: { progress: Array<UploadProgress> }) { export default function FileUploadProgress({ progress }: { progress: Array<UploadProgress> }) {
return ( return (
<div className="flex-column g8"> <div className="flex flex-col g8">
{progress.map(p => ( {progress.map(p => (
<div className="flex-column g2" id={p.id}> <div className="flex flex-col g2" id={p.id}>
{p.file.name} {p.file.name}
<Progress value={p.progress} status={p.stage} /> <Progress value={p.progress} status={p.stage} />
</div> </div>

View File

@ -121,7 +121,7 @@ export function LongFormText(props: LongFormTextProps) {
return ( return (
<div <div
className="long-form-note flex-column g16 p pointer" className="long-form-note flex flex-col g16 p pointer"
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
props.onClick?.(); props.onClick?.();

View File

@ -1,7 +1,7 @@
import "./Markdown.css"; import "./Markdown.css";
import { ReactNode, forwardRef, useMemo } from "react"; import { ReactNode, forwardRef, useMemo } from "react";
import { transformText } from "@snort/system"; import { parseNostrLink, transformText } from "@snort/system";
import { marked, Token } from "marked"; import { marked, Token } from "marked";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import markedFootnote, { Footnotes, Footnote, FootnoteRef } from "marked-footnote"; import markedFootnote, { Footnotes, Footnote, FootnoteRef } from "marked-footnote";
@ -106,7 +106,7 @@ function renderToken(t: Token | Footnotes | Footnote | FootnoteRef): ReactNode {
} }
} }
case "mention": { case "mention": {
return <Mention pubkey={v.content} />; return <Mention link={parseNostrLink(v.content)} />;
} }
default: { default: {
return v.content; return v.content;

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { NostrEvent, NostrLink } from "@snort/system"; import { NostrEvent, NostrLink } from "@snort/system";
import { findTag } from "SnortUtils"; import { findTag } from "SnortUtils";

View File

@ -71,7 +71,7 @@ export function NoteBroadcaster({
} }
return ( return (
<div className="flex-column g16"> <div className="flex flex-col g16">
<h3> <h3>
<FormattedMessage defaultMessage="Sending notes and other stuff" /> <FormattedMessage defaultMessage="Sending notes and other stuff" />
</h3> </h3>
@ -81,7 +81,7 @@ export function NoteBroadcaster({
.map(r => ( .map(r => (
<div className="flex-row g16"> <div className="flex-row g16">
<Icon name={r.ok ? "check" : "x"} className={r.ok ? "success" : "error"} size={24} /> <Icon name={r.ok ? "check" : "x"} className={r.ok ? "success" : "error"} size={24} />
<div className="flex-column f-grow g4"> <div className="flex flex-col grow g4">
<b>{getRelayName(r.relay)}</b> <b>{getRelayName(r.relay)}</b>
{r.message && <small>{r.message}</small>} {r.message && <small>{r.message}</small>}
</div> </div>
@ -89,7 +89,7 @@ export function NoteBroadcaster({
<div className="flex g8"> <div className="flex g8">
<AsyncButton <AsyncButton
onClick={() => retryPublish(r)} onClick={() => retryPublish(r)}
className="p4 br-compact flex f-center secondary" className="p4 br-compact flex items-center secondary"
title={formatMessage({ title={formatMessage({
defaultMessage: "Retry publishing", defaultMessage: "Retry publishing",
})}> })}>
@ -97,7 +97,7 @@ export function NoteBroadcaster({
</AsyncButton> </AsyncButton>
<AsyncButton <AsyncButton
onClick={() => removeRelayFromResult(r)} onClick={() => removeRelayFromResult(r)}
className="p4 br-compact flex f-center secondary" className="p4 br-compact flex items-center secondary"
title={formatMessage({ title={formatMessage({
defaultMessage: "Remove from my relays", defaultMessage: "Remove from my relays",
})}> })}>

View File

@ -291,11 +291,11 @@ export function NoteCreator() {
function renderRelayCustomisation() { function renderRelayCustomisation() {
return ( return (
<div className="flex-column g8"> <div className="flex flex-col g8">
{Object.keys(relays.item || {}) {Object.keys(relays.item || {})
.filter(el => relays.item[el].write) .filter(el => relays.item[el].write)
.map((r, i, a) => ( .map((r, i, a) => (
<div className="p flex f-space note-creator-relay"> <div className="p flex justify-between note-creator-relay">
<div>{r}</div> <div>{r}</div>
<div> <div>
<input <input
@ -353,15 +353,15 @@ export function NoteCreator() {
</p> </p>
{renderRelayCustomisation()} {renderRelayCustomisation()}
</div> </div>
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage defaultMessage="Zap Splits" /> <FormattedMessage defaultMessage="Zap Splits" />
</h4> </h4>
<FormattedMessage defaultMessage="Zaps on this note will be split to the following users." /> <FormattedMessage defaultMessage="Zaps on this note will be split to the following users." />
<div className="flex-column g8"> <div className="flex flex-col g8">
{[...(note.zapSplits ?? [])].map((v, i, arr) => ( {[...(note.zapSplits ?? [])].map((v, i, arr) => (
<div className="flex f-center g8"> <div className="flex items-center g8">
<div className="flex-column f-4 g4"> <div className="flex flex-col f-4 g4">
<h4> <h4>
<FormattedMessage defaultMessage="Recipient" /> <FormattedMessage defaultMessage="Recipient" />
</h4> </h4>
@ -376,7 +376,7 @@ export function NoteCreator() {
placeholder={formatMessage({ defaultMessage: "npub / nprofile / nostr address" })} placeholder={formatMessage({ defaultMessage: "npub / nprofile / nostr address" })}
/> />
</div> </div>
<div className="flex-column f-1 g4"> <div className="flex flex-col f-1 g4">
<h4> <h4>
<FormattedMessage defaultMessage="Weight" /> <FormattedMessage defaultMessage="Weight" />
</h4> </h4>
@ -394,7 +394,7 @@ export function NoteCreator() {
} }
/> />
</div> </div>
<div className="flex-column f-shrink g4"> <div className="flex flex-col s g4">
<div>&nbsp;</div> <div>&nbsp;</div>
<Icon <Icon
name="close" name="close"
@ -415,7 +415,7 @@ export function NoteCreator() {
<FormattedMessage defaultMessage="Not all clients support this, you may still receive some zaps as if zap splits was not configured" /> <FormattedMessage defaultMessage="Not all clients support this, you may still receive some zaps as if zap splits was not configured" />
</span> </span>
</div> </div>
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage defaultMessage="Sensitive Content" /> <FormattedMessage defaultMessage="Sensitive Content" />
</h4> </h4>
@ -441,7 +441,7 @@ export function NoteCreator() {
function noteCreatorFooter() { function noteCreatorFooter() {
return ( return (
<div className="flex f-space"> <div className="flex justify-between">
<div className="flex g8"> <div className="flex g8">
<ProfileImage <ProfileImage
pubkey={login.publicKey ?? ""} pubkey={login.publicKey ?? ""}

View File

@ -6,25 +6,26 @@ import classNames from "classnames";
import { EventExt, EventKind, HexKey, Lists, NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system"; import { EventExt, EventKind, HexKey, Lists, NostrLink, NostrPrefix, TaggedNostrEvent } from "@snort/system";
import { useEventReactions } from "@snort/system-react"; import { useEventReactions } from "@snort/system-react";
import { findTag, hexToBech32, profileLink } from "SnortUtils"; import { findTag, hexToBech32 } from "SnortUtils";
import useModeration from "Hooks/useModeration"; import useModeration from "Hooks/useModeration";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import useEventPublisher from "Hooks/useEventPublisher"; import useEventPublisher from "Hooks/useEventPublisher";
import { NoteContextMenu, NoteTranslation } from "./NoteContextMenu"; import { NoteContextMenu, NoteTranslation } from "./NoteContextMenu";
import { UserCache } from "../../Cache"; import { UserCache } from "Cache";
import messages from "../messages"; import messages from "../messages";
import { setBookmarked, setPinned } from "../../Login"; import { setBookmarked, setPinned } from "Login";
import Text from "../Text"; import Text from "../Text";
import Reveal from "./Reveal"; import Reveal from "./Reveal";
import Poll from "./Poll"; import Poll from "./Poll";
import ProfileImage from "../User/ProfileImage"; import ProfileImage from "../User/ProfileImage";
import Icon from "../../Icons/Icon"; import Icon from "Icons/Icon";
import NoteTime from "./NoteTime"; import NoteTime from "./NoteTime";
import NoteFooter from "./NoteFooter"; import NoteFooter from "./NoteFooter";
import Reactions from "./Reactions"; import Reactions from "./Reactions";
import HiddenNote from "./HiddenNote"; import HiddenNote from "./HiddenNote";
import { NoteProps } from "./Note"; import { NoteProps } from "./Note";
import { chainKey } from "Hooks/useThreadContext"; import { chainKey } from "Hooks/useThreadContext";
import { ProfileLink } from "Element/User/ProfileLink";
export function NoteInner(props: NoteProps) { export function NoteInner(props: NoteProps) {
const { data: ev, related, highlight, options: opt, ignoreModeration = false, className } = props; const { data: ev, related, highlight, options: opt, ignoreModeration = false, className } = props;
@ -182,7 +183,11 @@ export function NoteInner(props: NoteProps) {
mentions.push({ mentions.push({
pk, pk,
name: u?.name ?? shortNpub, name: u?.name ?? shortNpub,
link: <Link to={profileLink(pk)}>{u?.name ? `@${u.name}` : shortNpub}</Link>, link: (
<ProfileLink pubkey={pk} user={u}>
{u?.name ? `@${u.name}` : shortNpub}
</ProfileLink>
),
}); });
} }
mentions.sort(a => (a.name.startsWith(NostrPrefix.PublicKey) ? 1 : -1)); mentions.sort(a => (a.name.startsWith(NostrPrefix.PublicKey) ? 1 : -1));

View File

@ -7,7 +7,7 @@ import Note from "Element/Event/Note";
import { getDisplayName } from "Element/User/DisplayName"; import { getDisplayName } from "Element/User/DisplayName";
import { eventLink, hexToBech32 } from "SnortUtils"; import { eventLink, hexToBech32 } from "SnortUtils";
import useModeration from "Hooks/useModeration"; import useModeration from "Hooks/useModeration";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
import { useInView } from "react-intersection-observer"; import { useInView } from "react-intersection-observer";

View File

@ -108,7 +108,7 @@ export default function Poll(props: PollProps) {
return ( return (
<> <>
<div className="flex f-space p"> <div className="flex justify-between p">
<small> <small>
<FormattedMessage <FormattedMessage
defaultMessage="You are voting with {amount} sats" defaultMessage="You are voting with {amount} sats"
@ -147,7 +147,7 @@ export default function Poll(props: PollProps) {
const weight = totalVotes === 0 ? 0 : total / totalVotes; const weight = totalVotes === 0 ? 0 : total / totalVotes;
return ( return (
<div key={a[1]} className="flex" onClick={e => zapVote(e, opt)}> <div key={a[1]} className="flex" onClick={e => zapVote(e, opt)}>
<div className="f-grow">{opt === voting ? <Spinner /> : <>{desc}</>}</div> <div className="grow">{opt === voting ? <Spinner /> : <>{desc}</>}</div>
{showResults && ( {showResults && (
<> <>
<div className="flex"> <div className="flex">

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { FileExtensionRegex } from "Const"; import { FileExtensionRegex } from "Const";
import Reveal from "Element/Event/Reveal"; import Reveal from "Element/Event/Reveal";

View File

@ -17,7 +17,7 @@ const Zap = ({ zap, showZapped = true }: { zap: ParsedZap; showZapped?: boolean
return valid && sender ? ( return valid && sender ? (
<div className="card"> <div className="card">
<div className="flex f-space"> <div className="flex justify-between">
<ProfileImage pubkey={sender} /> <ProfileImage pubkey={sender} />
{receiver !== pubKey && showZapped && <ProfileImage pubkey={unwrap(receiver)} />} {receiver !== pubKey && showZapped && <ProfileImage pubkey={unwrap(receiver)} />}
<h3> <h3>

View File

@ -19,7 +19,7 @@ export function ZapGoal({ ev }: { ev: NostrEvent }) {
return ( return (
<div className="zap-goal card"> <div className="zap-goal card">
<div className="flex f-space"> <div className="flex justify-between">
<h2>{ev.content}</h2> <h2>{ev.content}</h2>
<div className="zap-button flex" onClick={() => setZap(true)}> <div className="zap-button flex" onClick={() => setZap(true)}>
<Icon name="zap" size={15} /> <Icon name="zap" size={15} />
@ -27,7 +27,7 @@ export function ZapGoal({ ev }: { ev: NostrEvent }) {
<SendSats targets={Zapper.fromEvent(ev)} show={zap} onClose={() => setZap(false)} /> <SendSats targets={Zapper.fromEvent(ev)} show={zap} onClose={() => setZap(false)} />
</div> </div>
<div className="flex f-space"> <div className="flex justify-between">
<div> <div>
<FormattedNumber value={progress} style="percent" /> <FormattedNumber value={progress} style="percent" />
</div> </div>

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useInView } from "react-intersection-observer"; import { useInView } from "react-intersection-observer";
import messages from "../messages"; import messages from "../messages";

View File

@ -1,5 +1,5 @@
import "./Timeline.css"; import "./Timeline.css";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { useInView } from "react-intersection-observer"; import { useInView } from "react-intersection-observer";
import { TaggedNostrEvent, EventKind, u256 } from "@snort/system"; import { TaggedNostrEvent, EventKind, u256 } from "@snort/system";
@ -84,7 +84,7 @@ const Timeline = (props: TimelineProps) => {
<> <>
<div className="card latest-notes" onClick={() => onShowLatest()} ref={ref}> <div className="card latest-notes" onClick={() => onShowLatest()} ref={ref}>
{latestAuthors.slice(0, 3).map(p => { {latestAuthors.slice(0, 3).map(p => {
return <ProfileImage pubkey={p} showUsername={false} link={""} />; return <ProfileImage pubkey={p} showUsername={false} link={""} showProfileCard={false} />;
})} })}
<FormattedMessage <FormattedMessage
defaultMessage="{n} new {n, plural, =1 {note} other {notes}}" defaultMessage="{n} new {n, plural, =1 {note} other {notes}}"
@ -117,7 +117,7 @@ const Timeline = (props: TimelineProps) => {
/> />
))} ))}
{(props.loadMore === undefined || props.loadMore === true) && ( {(props.loadMore === undefined || props.loadMore === true) && (
<div className="flex f-center"> <div className="flex items-center">
<button type="button" onClick={() => feed.loadMore()}> <button type="button" onClick={() => feed.loadMore()}>
<FormattedMessage defaultMessage="Load more" /> <FormattedMessage defaultMessage="Load more" />
</button> </button>

View File

@ -1,6 +1,6 @@
import "./Timeline.css"; import "./Timeline.css";
import { ReactNode, useCallback, useContext, useMemo, useState, useSyncExternalStore } from "react"; import { ReactNode, useCallback, useContext, useMemo, useState, useSyncExternalStore } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { TaggedNostrEvent, EventKind, u256, NostrEvent, NostrLink } from "@snort/system"; import { TaggedNostrEvent, EventKind, u256, NostrEvent, NostrLink } from "@snort/system";
import { unixNow } from "@snort/shared"; import { unixNow } from "@snort/shared";
import { SnortContext } from "@snort/system-react"; import { SnortContext } from "@snort/system-react";
@ -126,7 +126,7 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
/> />
), ),
)} )}
<div className="flex f-center p"> <div className="flex items-center p">
<AsyncButton <AsyncButton
onClick={async () => { onClick={async () => {
await FollowsFeed.loadMore(system, login, sortedFeed[sortedFeed.length - 1].created_at); await FollowsFeed.loadMore(system, login, sortedFeed[sortedFeed.length - 1].created_at);

View File

@ -1,22 +0,0 @@
import { useState, useEffect, FC, ComponentProps } from "react";
import { useIntl, FormattedMessage } from "react-intl";
type ExtendedProps = ComponentProps<typeof FormattedMessage>;
const ExtendedFormattedMessage: FC<ExtendedProps> = props => {
const { id, defaultMessage, values } = props;
const { formatMessage } = useIntl();
const [processedMessage, setProcessedMessage] = useState<string | null>(null);
useEffect(() => {
const translatedMessage = formatMessage({ id, defaultMessage }, values);
if (typeof translatedMessage === "string") {
setProcessedMessage(translatedMessage.replace("Snort", CONFIG.appNameCapitalized || "Snort"));
}
}, [id, defaultMessage, values, formatMessage]);
return <>{processedMessage}</>;
};
export default ExtendedFormattedMessage;

View File

@ -1,5 +1,5 @@
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
export default function AccountName({ name = "", link = true }) { export default function AccountName({ name = "", link = true }) {
const navigate = useNavigate(); const navigate = useNavigate();

View File

@ -2,10 +2,10 @@ import { mapEventToProfile } from "@snort/system";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
import AccountName from "./AccountName"; import AccountName from "./AccountName";
import useLogin from "../../Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import { UserCache } from "../../Cache"; import { UserCache } from "Cache";
import useEventPublisher from "../../Hooks/useEventPublisher"; import useEventPublisher from "Hooks/useEventPublisher";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
export default function ActiveAccount({ name = "", setAsPrimary = () => {} }) { export default function ActiveAccount({ name = "", setAsPrimary = () => {} }) {
const { publicKey, readonly } = useLogin(s => ({ const { publicKey, readonly } = useLogin(s => ({

View File

@ -5,8 +5,8 @@ import { LoginStore } from "Login";
import AccountName from "./AccountName"; import AccountName from "./AccountName";
import ActiveAccount from "./ActiveAccount"; import ActiveAccount from "./ActiveAccount";
import ReservedAccount from "./ReservedAccount"; import ReservedAccount from "./ReservedAccount";
import { ProfileLoader } from "../../index"; import { ProfileLoader } from "index";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { injectIntl } from "react-intl"; import { injectIntl } from "react-intl";
import messages from "Element/messages"; import messages from "Element/messages";

View File

@ -1,5 +1,5 @@
import AccountName from "./AccountName"; import AccountName from "./AccountName";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
export default function ReservedAccount({ name = "", enableReserved = () => {}, declineReserved = () => {} }) { export default function ReservedAccount({ name = "", enableReserved = () => {}, declineReserved = () => {} }) {
return ( return (

View File

@ -1,5 +1,5 @@
import { NostrEvent, NostrLink } from "@snort/system"; import { NostrEvent, NostrLink } from "@snort/system";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { findTag } from "SnortUtils"; import { findTag } from "SnortUtils";
@ -70,7 +70,7 @@ export function LiveEvent({ ev }: { ev: NostrEvent }) {
} }
return ( return (
<div className="flex f-space br p24 bg-primary"> <div className="flex justify-between br p24 bg-primary">
<div className="flex g12"> <div className="flex g12">
<ProfileImage pubkey={host} showUsername={false} size={56} /> <ProfileImage pubkey={host} showUsername={false} size={56} />
<div> <div>

View File

@ -43,7 +43,7 @@ function LiveStreamEvent({ ev }: { ev: NostrEvent }) {
"--img": `url(${imageProxy})`, "--img": `url(${imageProxy})`,
} as CSSProperties } as CSSProperties
}></div> }></div>
<div className="flex f-col details"> <div className="flex flex-col details">
<div className="flex g2"> <div className="flex g2">
<span className="live">{status}</span> <span className="live">{status}</span>
<div className="reaction-pill"> <div className="reaction-pill">

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { logout } from "Login"; import { logout } from "Login";

View File

@ -250,7 +250,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
)} )}
{error && <b className="error">{error.error}</b>} {error && <b className="error">{error.error}</b>}
{!registerStatus && ( {!registerStatus && (
<div className="flex mb10"> <div className="flex items-center mb10">
<input <input
type="text" type="text"
className="nip-handle" className="nip-handle"
@ -305,7 +305,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
title={formatMessage(messages.Buying, { item: `${handle}@${domain}` })} title={formatMessage(messages.Buying, { item: `${handle}@${domain}` })}
/> />
{registerStatus?.paid && ( {registerStatus?.paid && (
<div className="flex f-col"> <div className="flex flex-col">
<h4> <h4>
<FormattedMessage {...messages.OrderPaid} /> <FormattedMessage {...messages.OrderPaid} />
</h4> </h4>

View File

@ -2,7 +2,7 @@ import Spinner from "Icons/Spinner";
export default function PageSpinner() { export default function PageSpinner() {
return ( return (
<div className="flex f-center"> <div className="flex items-center">
<Spinner width={50} height={50} /> <Spinner width={50} height={50} />
</div> </div>
); );

View File

@ -63,7 +63,7 @@ export function PinPrompt({
submitButtonRef.current.click(); submitButtonRef.current.click();
} }
}}> }}>
<div className="flex-column g12"> <div className="flex flex-col g12">
<h2> <h2>
<FormattedMessage defaultMessage="Enter Pin" /> <FormattedMessage defaultMessage="Enter Pin" />
</h2> </h2>

View File

@ -1,6 +1,6 @@
import useImgProxy from "Hooks/useImgProxy"; import useImgProxy from "Hooks/useImgProxy";
import React, { useState } from "react"; import React, { useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { getUrlHostname } from "SnortUtils"; import { getUrlHostname } from "SnortUtils";
interface ProxyImgProps extends React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement> { interface ProxyImgProps extends React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement> {

View File

@ -1,5 +1,5 @@
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { TaggedNostrEvent } from "@snort/system"; import { TaggedNostrEvent } from "@snort/system";
import { SnortContext } from "@snort/system-react"; import { SnortContext } from "@snort/system-react";
@ -23,11 +23,11 @@ export function ReBroadcaster({ onClose, ev }: { onClose: () => void; ev: Tagged
function renderRelayCustomisation() { function renderRelayCustomisation() {
return ( return (
<div className="flex-column g8"> <div className="flex flex-col g8">
{Object.keys(relays.item || {}) {Object.keys(relays.item || {})
.filter(el => relays.item[el].write) .filter(el => relays.item[el].write)
.map((r, i, a) => ( .map((r, i, a) => (
<div className="card flex f-space"> <div className="card flex justify-between">
<div>{r}</div> <div>{r}</div>
<div> <div>
<input <input

View File

@ -1,6 +1,6 @@
import "./Relay.css"; import "./Relay.css";
import { useContext, useMemo } from "react"; import { useContext, useMemo } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { RelaySettings } from "@snort/system"; import { RelaySettings } from "@snort/system";
import { unixNowMs } from "@snort/shared"; import { unixNowMs } from "@snort/shared";
@ -46,7 +46,7 @@ export default function Relay(props: RelayProps) {
<div className={`flex ${state?.connected ? "bg-success" : "bg-error"}`}> <div className={`flex ${state?.connected ? "bg-success" : "bg-error"}`}>
<Icon name="wifi" /> <Icon name="wifi" />
</div> </div>
<div className="f-grow f-col"> <div className="grow flex-col">
<div className="flex mb10"> <div className="flex mb10">
<b className="f-2">{name}</b> <b className="f-2">{name}</b>
<div className="f-1"> <div className="f-1">
@ -77,7 +77,7 @@ export default function Relay(props: RelayProps) {
</div> </div>
</div> </div>
<div className="flex"> <div className="flex">
<div className="f-grow"></div> <div className="grow"></div>
<div> <div>
<span className="icon-btn" onClick={() => navigate(state?.id ?? "")}> <span className="icon-btn" onClick={() => navigate(state?.id ?? "")}>
<Icon name="gear" size={12} /> <Icon name="gear" size={12} />

View File

@ -2,7 +2,7 @@ import "./RootTabs.css";
import { useState, ReactNode, useEffect } from "react"; import { useState, ReactNode, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { Menu, MenuItem } from "@szhsin/react-menu"; import { Menu, MenuItem } from "@szhsin/react-menu";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";

View File

@ -101,7 +101,7 @@ export default function SendSats(props: SendSatsProps) {
function successAction() { function successAction() {
if (!success) return null; if (!success) return null;
return ( return (
<div className="flex f-center"> <div className="flex items-center">
<p className="flex g12"> <p className="flex g12">
<Icon name="check" className="success" /> <Icon name="check" className="success" />
{success?.description ?? <FormattedMessage defaultMessage="Paid" />} {success?.description ?? <FormattedMessage defaultMessage="Paid" />}
@ -153,7 +153,7 @@ export default function SendSats(props: SendSatsProps) {
const total = props.targets.reduce((acc, v) => (acc += v.weight), 0); const total = props.targets.reduce((acc, v) => (acc += v.weight), 0);
return ( return (
<div className="flex-column g12"> <div className="flex flex-col g12">
<h2> <h2>
{zapper?.canZap() ? ( {zapper?.canZap() ? (
<FormattedMessage defaultMessage="Send zap splits to" /> <FormattedMessage defaultMessage="Send zap splits to" />
@ -179,9 +179,9 @@ export default function SendSats(props: SendSatsProps) {
if (!(props.show ?? false)) return null; if (!(props.show ?? false)) return null;
return ( return (
<Modal id="send-sats" className="lnurl-modal" onClose={onClose}> <Modal id="send-sats" className="lnurl-modal" onClose={onClose}>
<div className="p flex-column g12"> <div className="p flex flex-col g12">
<div className="flex g12"> <div className="flex g12">
<div className="flex f-grow">{props.title || title()}</div> <div className="flex items-center grow">{props.title || title()}</div>
<div onClick={onClose}> <div onClick={onClose}>
<Icon name="close" /> <Icon name="close" />
</div> </div>
@ -310,7 +310,7 @@ function SendSatsInput(props: {
type="number" type="number"
min={min} min={min}
max={max} max={max}
className="f-grow" className="grow"
placeholder={formatMessage(messages.Custom)} placeholder={formatMessage(messages.Custom)}
value={customAmount} value={customAmount}
onChange={e => setCustomAmount(parseInt(e.target.value))} onChange={e => setCustomAmount(parseInt(e.target.value))}
@ -327,8 +327,8 @@ function SendSatsInput(props: {
} }
return ( return (
<div className="flex-column g24"> <div className="flex flex-col g24">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h3> <h3>
<FormattedMessage defaultMessage="Zap amount in sats" /> <FormattedMessage defaultMessage="Zap amount in sats" />
</h3> </h3>
@ -338,7 +338,7 @@ function SendSatsInput(props: {
<input <input
type="text" type="text"
placeholder={formatMessage(messages.Comment)} placeholder={formatMessage(messages.Comment)}
className="f-grow" className="grow"
maxLength={props.zapper.maxComment()} maxLength={props.zapper.maxComment()}
onChange={e => setComment(e.target.value)} onChange={e => setComment(e.target.value)}
/> />
@ -346,11 +346,9 @@ function SendSatsInput(props: {
</div> </div>
<SendSatsZapTypeSelector zapType={zapType} setZapType={setZapType} /> <SendSatsZapTypeSelector zapType={zapType} setZapType={setZapType} />
{(amount ?? 0) > 0 && ( {(amount ?? 0) > 0 && (
<AsyncButton className="zap-action" onClick={() => props.onNextStage(getValue())}> <AsyncButton onClick={() => props.onNextStage(getValue())}>
<div className="zap-action-container"> <Icon name="zap" />
<Icon name="zap" /> <FormattedMessage defaultMessage="Zap {n} sats" values={{ n: formatShort(amount) }} />
<FormattedMessage defaultMessage="Zap {n} sats" values={{ n: formatShort(amount) }} />
</div>
</AsyncButton> </AsyncButton>
)} )}
</div> </div>
@ -365,7 +363,7 @@ function SendSatsZapTypeSelector({ zapType, setZapType }: { zapType: ZapType; se
</button> </button>
); );
return ( return (
<div className="flex-column g8"> <div className="flex flex-col g8">
<h3> <h3>
<FormattedMessage defaultMessage="Zap Type" /> <FormattedMessage defaultMessage="Zap Type" />
</h3> </h3>
@ -389,13 +387,13 @@ function SendSatsInvoice(props: {
onInvoicePaid: () => void; onInvoicePaid: () => void;
}) { }) {
return ( return (
<div className="flex-column g12 txt-center"> <div className="flex flex-col items-center g12 txt-center">
{props.notice && <b className="error">{props.notice}</b>} {props.notice && <b className="error">{props.notice}</b>}
{props.invoice.map(v => ( {props.invoice.map(v => (
<> <>
<QrCode data={v.pr} link={`lightning:${v.pr}`} /> <QrCode data={v.pr} link={`lightning:${v.pr}`} />
<div className="flex-column g12"> <div className="flex flex-col g12">
<Copy text={v.pr} maxSize={26} className="f-center" /> <Copy text={v.pr} maxSize={26} className="items-center" />
<a href={`lightning:${v.pr}`}> <a href={`lightning:${v.pr}`}>
<button type="button"> <button type="button">
<FormattedMessage defaultMessage="Open Wallet" /> <FormattedMessage defaultMessage="Open Wallet" />

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { HexKey, NostrPrefix } from "@snort/system"; import { HexKey, NostrPrefix } from "@snort/system";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import FollowListBase from "Element/User/FollowListBase"; import FollowListBase from "Element/User/FollowListBase";
import PageSpinner from "Element/PageSpinner"; import PageSpinner from "Element/PageSpinner";
@ -55,7 +55,7 @@ export default function SuggestedProfiles() {
return ( return (
<> <>
<div className="card flex f-space"> <div className="card flex justify-between">
<FormattedMessage defaultMessage="Provider" /> <FormattedMessage defaultMessage="Provider" />
<select onChange={e => setProvider(Number(e.target.value))}> <select onChange={e => setProvider(Number(e.target.value))}>
<option value={Provider.NostrBand}>nostr.band</option> <option value={Provider.NostrBand}>nostr.band</option>

View File

@ -41,7 +41,7 @@ export default function AvatarEditor({ picture, onPictureChange }: AvatarEditorP
return ( return (
<> <>
<div className="flex f-center"> <div className="flex justify-center items-center">
<div style={{ backgroundImage: `url(${picture})` }} className="avatar"> <div style={{ backgroundImage: `url(${picture})` }} className="avatar">
<div className={`edit${picture ? "" : " new"}`} onClick={() => uploadFile().catch(console.error)}> <div className={`edit${picture ? "" : " new"}`} onClick={() => uploadFile().catch(console.error)}>
{loading ? <Spinner /> : <Icon name={picture ? "edit" : "camera-plus"} />} {loading ? <Spinner /> : <Icon name={picture ? "edit" : "camera-plus"} />}

View File

@ -1,7 +1,7 @@
import "./BadgeList.css"; import "./BadgeList.css";
import { useState } from "react"; import { useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { TaggedNostrEvent } from "@snort/system"; import { TaggedNostrEvent } from "@snort/system";

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { HexKey } from "@snort/system"; import { HexKey } from "@snort/system";
import useModeration from "Hooks/useModeration"; import useModeration from "Hooks/useModeration";

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { HexKey } from "@snort/system"; import { HexKey } from "@snort/system";
import useEventPublisher from "Hooks/useEventPublisher"; import useEventPublisher from "Hooks/useEventPublisher";

View File

@ -1,5 +1,5 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { HexKey } from "@snort/system"; import { HexKey } from "@snort/system";
import { dedupe } from "@snort/shared"; import { dedupe } from "@snort/shared";
@ -38,9 +38,9 @@ export default function FollowListBase({
if (publisher) { if (publisher) {
const newFollows = dedupe([...pubkeys, ...login.follows.item]); const newFollows = dedupe([...pubkeys, ...login.follows.item]);
const ev = await publisher.contactList(newFollows, login.relays.item); const ev = await publisher.contactList(newFollows, login.relays.item);
system.BroadcastEvent(ev);
await FollowsFeed.backFill(system, pubkeys);
setFollows(login, newFollows, ev.created_at); setFollows(login, newFollows, ev.created_at);
await system.BroadcastEvent(ev);
await FollowsFeed.backFill(system, pubkeys);
} }
} }
@ -48,7 +48,7 @@ export default function FollowListBase({
<div className={className}> <div className={className}>
{(showFollowAll ?? true) && ( {(showFollowAll ?? true) && (
<div className="flex mt10 mb10"> <div className="flex mt10 mb10">
<div className="f-grow bold">{title}</div> <div className="grow bold">{title}</div>
{actions} {actions}
<AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={login.readonly}> <AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={login.readonly}>
<FormattedMessage {...messages.FollowAll} /> <FormattedMessage {...messages.FollowAll} />

View File

@ -1,7 +1,8 @@
import "./Following.css"; import "./Following.css";
import { FormattedMessage } from "react-intl";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import FormattedMessage from "Element/FormattedMessage";
export function FollowingMark({ pubkey }: { pubkey: string }) { export function FollowingMark({ pubkey }: { pubkey: string }) {
const { follows } = useLogin(s => ({ follows: s.follows })); const { follows } = useLogin(s => ({ follows: s.follows }));

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { HexKey } from "@snort/system"; import { HexKey } from "@snort/system";
import useModeration from "Hooks/useModeration"; import useModeration from "Hooks/useModeration";

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { HexKey } from "@snort/system"; import { HexKey } from "@snort/system";
import MuteButton from "Element/User/MuteButton"; import MuteButton from "Element/User/MuteButton";
import ProfilePreview from "Element/User/ProfilePreview"; import ProfilePreview from "Element/User/ProfilePreview";
@ -16,7 +16,7 @@ export default function MutedList({ pubkeys }: MutedListProps) {
return ( return (
<div className="main-content p"> <div className="main-content p">
<div className="flex f-space"> <div className="flex justify-between">
<div className="bold"> <div className="bold">
<FormattedMessage {...messages.MuteCount} values={{ n: pubkeys?.length }} /> <FormattedMessage {...messages.MuteCount} values={{ n: pubkeys?.length }} />
</div> </div>

View File

@ -1,52 +1,31 @@
import "./NoteToSelf.css"; import "./NoteToSelf.css";
import { Link, useNavigate } from "react-router-dom";
import FormattedMessage from "Element/FormattedMessage";
import { profileLink } from "SnortUtils";
import classNames from "classnames"; import classNames from "classnames";
import { FormattedMessage } from "react-intl";
import messages from "../messages";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import messages from "../messages";
export interface NoteToSelfProps { export interface NoteToSelfProps {
pubkey: string;
clickable?: boolean;
className?: string; className?: string;
link?: string;
} }
function NoteLabel() { function NoteLabel() {
return ( return (
<div> <div className="bold flex items-center g4">
<FormattedMessage {...messages.NoteToSelf} /> <Icon name="badge" size={15} /> <FormattedMessage {...messages.NoteToSelf} /> <Icon name="badge" size={15} />
</div> </div>
); );
} }
export default function NoteToSelf({ pubkey, clickable, className, link }: NoteToSelfProps) { export default function NoteToSelf({ className }: NoteToSelfProps) {
const navigate = useNavigate();
const clickLink = () => {
if (clickable) {
navigate(link ?? profileLink(pubkey));
}
};
return ( return (
<div className={classNames("nts", className)}> <div className={classNames("nts", className)}>
<div className="avatar-wrapper"> <div className="avatar-wrapper">
<div className={classNames("avatar", { clickable: clickable })}> <div className="avatar">
<Icon onClick={clickLink} name="book-closed" size={20} /> <Icon name="book-closed" size={20} />
</div>
</div>
<div className="f-grow">
<div className="name">
{(clickable && (
<Link to={link ?? profileLink(pubkey)}>
<NoteLabel />
</Link>
)) || <NoteLabel />}
</div> </div>
</div> </div>
<NoteLabel />
</div> </div>
); );
} }

View File

@ -1,14 +1,12 @@
import "./ProfileImage.css"; import "./ProfileImage.css";
import React, { ReactNode, useEffect, useState } from "react"; import React, { ReactNode, useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { HexKey, UserMetadata } from "@snort/system"; import { HexKey, UserMetadata } from "@snort/system";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
import { useHover } from "@uidotdev/usehooks"; import { useHover } from "@uidotdev/usehooks";
import { ControlledMenu } from "@szhsin/react-menu"; import { ControlledMenu } from "@szhsin/react-menu";
import classNames from "classnames"; import classNames from "classnames";
import { profileLink } from "SnortUtils";
import Avatar from "Element/User/Avatar"; import Avatar from "Element/User/Avatar";
import Nip05 from "Element/User/Nip05"; import Nip05 from "Element/User/Nip05";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
@ -17,6 +15,7 @@ import DisplayName from "./DisplayName";
import Text from "Element/Text"; import Text from "Element/Text";
import FollowButton from "Element/User/FollowButton"; import FollowButton from "Element/User/FollowButton";
import { UserWebsiteLink } from "Element/User/UserWebsiteLink"; import { UserWebsiteLink } from "Element/User/UserWebsiteLink";
import { ProfileLink } from "./ProfileLink";
export interface ProfileImageProps { export interface ProfileImageProps {
pubkey: HexKey; pubkey: HexKey;
@ -126,8 +125,8 @@ export default function ProfileImage({
anchorRef={ref} anchorRef={ref}
menuClassName="profile-card" menuClassName="profile-card"
onClose={() => setShowProfileMenu(false)}> onClose={() => setShowProfileMenu(false)}>
<div className="flex-column g8"> <div className="flex flex-col g8">
<div className="flex f-space"> <div className="flex justify-between">
<ProfileImage pubkey={""} profile={user} showProfileCard={false} link="" /> <ProfileImage pubkey={""} profile={user} showProfileCard={false} link="" />
<div className="flex g8"> <div className="flex g8">
{/*<button type="button" onClick={() => { {/*<button type="button" onClick={() => {
@ -167,12 +166,14 @@ export default function ProfileImage({
} else { } else {
return ( return (
<> <>
<Link <ProfileLink
pubkey={pubkey}
className={classNames("pfp", className)} className={classNames("pfp", className)}
to={link === undefined ? profileLink(pubkey) : link} user={user}
explicitLink={link}
onClick={handleClick}> onClick={handleClick}>
{inner()} {inner()}
</Link> </ProfileLink>
{profileCard()} {profileCard()}
</> </>
); );

View File

@ -0,0 +1,62 @@
import { ReactNode, useContext } from "react";
import { Link, LinkProps } from "react-router-dom";
import { UserMetadata, NostrLink, NostrPrefix, MetadataCache } from "@snort/system";
import { SnortContext } from "@snort/system-react";
import { randomSample } from "SnortUtils";
export function ProfileLink({
pubkey,
user,
link,
explicitLink,
children,
...others
}: {
pubkey: string;
user?: UserMetadata | MetadataCache;
link?: NostrLink;
explicitLink?: string;
children?: ReactNode;
} & Omit<LinkProps, "to">) {
const system = useContext(SnortContext);
const relays = system.RelayCache.getFromCache(pubkey)
?.relays?.filter(a => a.settings.write)
?.map(a => a.url);
function profileLink() {
if (explicitLink) {
return explicitLink;
}
if (user) {
if (
user.nip05 &&
user.nip05.endsWith(`@${CONFIG.nip05Domain}`) &&
(!("isNostrAddressValid" in user) || user.isNostrAddressValid)
) {
const [username] = user.nip05.split("@");
return `/${username}`;
}
return `/p/${new NostrLink(
NostrPrefix.Profile,
pubkey,
undefined,
undefined,
relays ? randomSample(relays, 3) : undefined,
).encode()}`;
}
if (link && (link.type === NostrPrefix.Profile || link.type === NostrPrefix.PublicKey)) {
return `/p/${link.encode()}`;
}
return "#";
}
const oFiltered = others as Record<string, unknown>;
delete oFiltered["user"];
delete oFiltered["link"];
delete oFiltered["children"];
return (
<Link {...oFiltered} to={profileLink()} state={user}>
{children}
</Link>
);
}

View File

@ -1,24 +1,15 @@
import { MouseEvent } from "react";
import { useNavigate, Link } from "react-router-dom";
import { HexKey } from "@snort/system"; import { HexKey } from "@snort/system";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
import { profileLink } from "SnortUtils"; import { ProfileLink } from "./ProfileLink";
import DisplayName from "./DisplayName";
export default function Username({ pubkey, onLinkVisit }: { pubkey: HexKey; onLinkVisit(): void }) { export default function Username({ pubkey, onLinkVisit }: { pubkey: HexKey; onLinkVisit(): void }) {
const user = useUserProfile(pubkey); const user = useUserProfile(pubkey);
const navigate = useNavigate();
function onClick(ev: MouseEvent) {
ev.preventDefault();
onLinkVisit();
navigate(profileLink(pubkey));
}
return user ? ( return user ? (
<Link to={profileLink(pubkey)} onClick={onClick}> <ProfileLink pubkey={pubkey} onClick={onLinkVisit} user={user}>
{user.name || pubkey.slice(0, 12)} <DisplayName pubkey={pubkey} user={user} />
</Link> </ProfileLink>
) : null; ) : null;
} }

View File

@ -18,7 +18,7 @@ export interface TimelineSubject {
type: "pubkey" | "hashtag" | "global" | "ptag" | "post_keyword" | "profile_keyword"; type: "pubkey" | "hashtag" | "global" | "ptag" | "post_keyword" | "profile_keyword";
discriminator: string; discriminator: string;
items: string[]; items: string[];
relay?: string; relay?: Array<string>;
streams?: boolean; streams?: boolean;
} }

View File

@ -11,6 +11,8 @@ export default function useLoading<T>(fn: ((e: React.MouseEvent) => Promise<T> |
if (typeof fn === "function") { if (typeof fn === "function") {
await fn(e); await fn(e);
} }
} catch (e) {
console.error(e);
} finally { } finally {
setLoading(false); setLoading(false);
} }

View File

@ -1,7 +1,7 @@
import "./Deck.css"; import "./Deck.css";
import { CSSProperties, createContext, useContext, useEffect, useState } from "react"; import { CSSProperties, createContext, useContext, useEffect, useState } from "react";
import { Outlet, useNavigate } from "react-router-dom"; import { Outlet, useNavigate } from "react-router-dom";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { NostrLink, TaggedNostrEvent } from "@snort/system"; import { NostrLink, TaggedNostrEvent } from "@snort/system";
import { DeckNav } from "Element/Deck/Nav"; import { DeckNav } from "Element/Deck/Nav";

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { HexKey } from "@snort/system"; import { HexKey } from "@snort/system";
import { ApiHost, DeveloperAccounts, SnortPubKey } from "Const"; import { ApiHost, DeveloperAccounts, SnortPubKey } from "Const";
@ -114,9 +114,9 @@ const DonatePage = () => {
<p> <p>
<FormattedMessage defaultMessage="Each contributor will get paid a percentage of all donations and NIP-05 orders, you can see the split amounts below" /> <FormattedMessage defaultMessage="Each contributor will get paid a percentage of all donations and NIP-05 orders, you can see the split amounts below" />
</p> </p>
<div className="flex-column g12"> <div className="flex flex-col g12">
<div className="b br p"> <div className="b br p">
<div className="flex f-space"> <div className="flex justify-between">
<FormattedMessage defaultMessage="Lightning Donation" /> <FormattedMessage defaultMessage="Lightning Donation" />
<ZapButton pubkey={bech32ToHex(SnortPubKey)} lnurl={DonateLNURL}> <ZapButton pubkey={bech32ToHex(SnortPubKey)} lnurl={DonateLNURL}>
<FormattedMessage defaultMessage="Donate" /> <FormattedMessage defaultMessage="Donate" />
@ -132,7 +132,7 @@ const DonatePage = () => {
)} )}
</div> </div>
<div className="b br p"> <div className="b br p">
<div className="flex f-space"> <div className="flex justify-between">
<FormattedMessage defaultMessage="On-chain Donation" /> <FormattedMessage defaultMessage="On-chain Donation" />
<AsyncButton type="button" onClick={getOnChainAddress}> <AsyncButton type="button" onClick={getOnChainAddress}>
<FormattedMessage defaultMessage="Get Address" /> <FormattedMessage defaultMessage="Get Address" />
@ -142,7 +142,7 @@ const DonatePage = () => {
</div> </div>
{onChain && ( {onChain && (
<Modal onClose={() => setOnChain("")} id="donate-on-chain"> <Modal onClose={() => setOnChain("")} id="donate-on-chain">
<div className="flex-column f-center g12"> <div className="flex flex-col items-center g12">
<h2> <h2>
<FormattedMessage defaultMessage="On-chain Donation Address" /> <FormattedMessage defaultMessage="On-chain Donation Address" />
</h2> </h2>

View File

@ -1,6 +1,6 @@
import { db } from "Db"; import { db } from "Db";
import AsyncButton from "Element/AsyncButton"; import AsyncButton from "Element/AsyncButton";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useRouteError } from "react-router-dom"; import { useRouteError } from "react-router-dom";
const ErrorPage = () => { const ErrorPage = () => {

View File

@ -1,7 +1,7 @@
import FormattedMessage from "@snort/app/src/Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import IrisAccount from "Element/IrisAccount/IrisAccount";
import messages from "./messages"; import messages from "./messages";
import IrisAccount from "Element/IrisAccount/IrisAccount";
export default function FreeNostrAddressPage() { export default function FreeNostrAddressPage() {
return ( return (

View File

@ -1,6 +1,6 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import Timeline from "Element/Feed/Timeline"; import Timeline from "Element/Feed/Timeline";
import useEventPublisher from "Hooks/useEventPublisher"; import useEventPublisher from "Hooks/useEventPublisher";

View File

@ -1,6 +1,6 @@
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { KieranPubKey } from "Const"; import { KieranPubKey } from "Const";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { TLVEntryType, encodeTLVEntries, NostrPrefix } from "@snort/system"; import { TLVEntryType, encodeTLVEntries, NostrPrefix } from "@snort/system";
import { bech32ToHex } from "SnortUtils"; import { bech32ToHex } from "SnortUtils";

View File

@ -12,7 +12,7 @@ import useLoginFeed from "Feed/LoginFeed";
import { mapPlanName } from "./subscribe"; import { mapPlanName } from "./subscribe";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import Avatar from "Element/User/Avatar"; import Avatar from "Element/User/Avatar";
import { isFormElement, profileLink } from "SnortUtils"; import { isFormElement } from "SnortUtils";
import { getCurrentSubscription } from "Subscription"; import { getCurrentSubscription } from "Subscription";
import Toaster from "Toaster"; import Toaster from "Toaster";
import Spinner from "Icons/Spinner"; import Spinner from "Icons/Spinner";
@ -23,6 +23,7 @@ import { LoginUnlock } from "Element/PinPrompt";
import useKeyboardShortcut from "Hooks/useKeyboardShortcut"; import useKeyboardShortcut from "Hooks/useKeyboardShortcut";
import { LoginStore } from "Login"; import { LoginStore } from "Login";
import { NoteCreatorButton } from "Element/Event/NoteCreatorButton"; import { NoteCreatorButton } from "Element/Event/NoteCreatorButton";
import { ProfileLink } from "Element/User/ProfileLink";
export default function Layout() { export default function Layout() {
const location = useLocation(); const location = useLocation();
@ -78,7 +79,7 @@ export default function Layout() {
onClick={() => { onClick={() => {
LoginStore.removeSession(id); LoginStore.removeSession(id);
}}> }}>
<button type="button" className="circle flex f-center"> <button type="button" className="circle flex items-center">
<Icon name="close" /> <Icon name="close" />
</button> </button>
</div> </div>
@ -192,15 +193,9 @@ const AccountHeader = () => {
<Icon name="bell-02" size={24} /> <Icon name="bell-02" size={24} />
{hasNotifications && <span className="has-unread"></span>} {hasNotifications && <span className="has-unread"></span>}
</Link> </Link>
<Avatar <ProfileLink pubkey={publicKey} user={profile}>
pubkey={publicKey ?? ""} <Avatar pubkey={publicKey} user={profile} />
user={profile} </ProfileLink>
onClick={() => {
if (profile) {
navigate(profileLink(profile.pubkey));
}
}}
/>
</div> </div>
); );
}; };

View File

@ -196,7 +196,7 @@ export default function LoginPage() {
<p> <p>
<FormattedMessage defaultMessage="Scan this QR code with your signer app to get started" /> <FormattedMessage defaultMessage="Scan this QR code with your signer app to get started" />
</p> </p>
<div className="flex-column f-center g12"> <div className="flex flex-col items-center g12">
<QrCode data={nostrConnect} /> <QrCode data={nostrConnect} />
<Copy text={nostrConnect} /> <Copy text={nostrConnect} />
</div> </div>
@ -297,14 +297,14 @@ export default function LoginPage() {
<p dir="auto"> <p dir="auto">
<FormattedMessage defaultMessage="Your key" description="Label for key input" /> <FormattedMessage defaultMessage="Your key" description="Label for key input" />
</p> </p>
<div className="flex f-center g8"> <div className="flex items-center g8">
<input <input
dir="auto" dir="auto"
type={isMasking ? "password" : "text"} type={isMasking ? "password" : "text"}
placeholder={formatMessage({ placeholder={formatMessage({
defaultMessage: "nsec, npub, nip-05, hex, mnemonic", defaultMessage: "nsec, npub, nip-05, hex, mnemonic",
})} })}
className="f-grow" className="grow"
onChange={e => setKey(e.target.value)} onChange={e => setKey(e.target.value)}
/> />
<Icon <Icon

View File

@ -26,6 +26,7 @@ import { LoginSession, LoginStore } from "Login";
import { Nip28ChatSystem } from "chat/nip28"; import { Nip28ChatSystem } from "chat/nip28";
import { ChatParticipantProfile } from "Element/Chat/ChatParticipant"; import { ChatParticipantProfile } from "Element/Chat/ChatParticipant";
import { getDisplayName } from "Element/User/DisplayName"; import { getDisplayName } from "Element/User/DisplayName";
import classNames from "classnames";
const TwoCol = 768; const TwoCol = 768;
const ThreeCol = 1500; const ThreeCol = 1500;
@ -55,7 +56,7 @@ export default function MessagesPage() {
function noteToSelf(chat: Chat) { function noteToSelf(chat: Chat) {
return ( return (
<div className="flex p" key={chat.id} onClick={e => openChat(e, chat.type, chat.id)}> <div className="flex p" key={chat.id} onClick={e => openChat(e, chat.type, chat.id)}>
<NoteToSelf clickable={true} className="f-grow" link="" pubkey={chat.id} /> <NoteToSelf className="grow" />
</div> </div>
); );
} }
@ -65,7 +66,7 @@ export default function MessagesPage() {
return <ChatParticipantProfile participant={cx.participants[0]} />; return <ChatParticipantProfile participant={cx.participants[0]} />;
} else { } else {
return ( return (
<div className="flex f-grow pfp-overlap"> <div className="flex items-center grow pfp-overlap">
{cx.participants.map(v => ( {cx.participants.map(v => (
<ProfileImage pubkey={v.id} link="" showUsername={false} profile={v.profile} /> <ProfileImage pubkey={v.id} link="" showUsername={false} profile={v.profile} />
))} ))}
@ -82,7 +83,10 @@ export default function MessagesPage() {
const isActive = cx.id === chat; const isActive = cx.id === chat;
return ( return (
<div className={`flex p${isActive ? " active" : ""}`} key={cx.id} onClick={e => openChat(e, cx.type, cx.id)}> <div
className={classNames("flex items-center p", { active: isActive })}
key={cx.id}
onClick={e => openChat(e, cx.type, cx.id)}>
{conversationIdent(cx)} {conversationIdent(cx)}
<div className="nowrap"> <div className="nowrap">
<small> <small>
@ -98,7 +102,7 @@ export default function MessagesPage() {
<div className="dm-page"> <div className="dm-page">
{(pageWidth >= TwoCol || !chat) && ( {(pageWidth >= TwoCol || !chat) && (
<div className="chat-list"> <div className="chat-list">
<div className="flex p f-space"> <div className="flex items-center p justify-between">
<button disabled={unreadCount <= 0} type="button"> <button disabled={unreadCount <= 0} type="button">
<FormattedMessage defaultMessage="Mark all read" /> <FormattedMessage defaultMessage="Mark all read" />
</button> </button>
@ -205,13 +209,13 @@ function NewChatWindow() {
return ( return (
<> <>
<button type="button" className="new-chat" onClick={() => setShow(true)}> <button type="button" className="flex justify-center new-chat" onClick={() => setShow(true)}>
<Icon name="plus" size={16} /> <Icon name="plus" size={16} />
</button> </button>
{show && ( {show && (
<Modal id="new-chat" onClose={() => setShow(false)} className="new-chat-modal"> <Modal id="new-chat" onClose={() => setShow(false)} className="new-chat-modal">
<div className="flex-column g16"> <div className="flex flex-col g16">
<div className="flex f-space"> <div className="flex justify-between">
<h2> <h2>
<FormattedMessage defaultMessage="New Chat" /> <FormattedMessage defaultMessage="New Chat" />
</h2> </h2>
@ -219,7 +223,7 @@ function NewChatWindow() {
<FormattedMessage defaultMessage="Start chat" /> <FormattedMessage defaultMessage="Start chat" />
</button> </button>
</div> </div>
<div className="flex-column g8"> <div className="flex flex-col g8">
<h3> <h3>
<FormattedMessage defaultMessage="Search users" /> <FormattedMessage defaultMessage="Search users" />
</h3> </h3>
@ -245,7 +249,7 @@ function NewChatWindow() {
<p> <p>
<FormattedMessage defaultMessage="People you follow" /> <FormattedMessage defaultMessage="People you follow" />
</p> </p>
<div className="user-list flex-column g2"> <div className="user-list flex flex-col g2">
{results.map(a => { {results.map(a => {
return ( return (
<ProfilePreview <ProfilePreview

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { ApiHost } from "Const"; import { ApiHost } from "Const";
import Nip5Service from "Element/Nip5Service"; import Nip5Service from "Element/Nip5Service";

View File

@ -1,7 +1,7 @@
import { NostrPrefix, tryParseNostrLink } from "@snort/system"; import { NostrPrefix, tryParseNostrLink } from "@snort/system";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useParams } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import Spinner from "Icons/Spinner"; import Spinner from "Icons/Spinner";
import { getNip05PubKey } from "Pages/LoginPage"; import { getNip05PubKey } from "Pages/LoginPage";
@ -10,8 +10,9 @@ import { ThreadRoute } from "Element/Event/Thread";
export default function NostrLinkHandler() { export default function NostrLinkHandler() {
const params = useParams(); const params = useParams();
const { state } = useLocation();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [renderComponent, setRenderComponent] = useState<React.ReactNode | null>(null); const [renderComponent, setRenderComponent] = useState<React.ReactNode>(null);
const link = decodeURIComponent(params["*"] ?? "").toLowerCase(); const link = decodeURIComponent(params["*"] ?? "").toLowerCase();
@ -21,16 +22,20 @@ export default function NostrLinkHandler() {
if (nav.type === NostrPrefix.Event || nav.type === NostrPrefix.Note || nav.type === NostrPrefix.Address) { if (nav.type === NostrPrefix.Event || nav.type === NostrPrefix.Note || nav.type === NostrPrefix.Address) {
setRenderComponent(<ThreadRoute id={nav.encode()} />); // Directly render ThreadRoute setRenderComponent(<ThreadRoute id={nav.encode()} />); // Directly render ThreadRoute
} else if (nav.type === NostrPrefix.PublicKey || nav.type === NostrPrefix.Profile) { } else if (nav.type === NostrPrefix.PublicKey || nav.type === NostrPrefix.Profile) {
setRenderComponent(<ProfilePage id={nav.encode()} />); // Directly render ProfilePage setRenderComponent(<ProfilePage id={nav.encode()} state={state} />); // Directly render ProfilePage
} }
} else { } else {
try { if (state) {
const pubkey = await getNip05PubKey(`${link}@${CONFIG.nip05Domain}`); setRenderComponent(<ProfilePage state={state} />); // Directly render ProfilePage from route state
if (pubkey) { } else {
setRenderComponent(<ProfilePage id={pubkey} />); // Directly render ProfilePage try {
const pubkey = await getNip05PubKey(`${link}@${CONFIG.nip05Domain}`);
if (pubkey) {
setRenderComponent(<ProfilePage id={pubkey} state={state} />); // Directly render ProfilePage
}
} catch {
//ignored
} }
} catch {
//ignored
} }
} }
setLoading(false); setLoading(false);
@ -47,7 +52,7 @@ export default function NostrLinkHandler() {
} }
return ( return (
<div className="flex f-center"> <div className="flex items-center">
{loading ? ( {loading ? (
<Spinner width={50} height={50} /> <Spinner width={50} height={50} />
) : ( ) : (

View File

@ -208,12 +208,12 @@ function NotificationSummary({ evs }: { evs: Array<TaggedNostrEvent> }) {
}; };
return ( return (
<div className="flex-column g12 p bb"> <div className="flex flex-col g12 p bb">
<div className="flex f-space"> <div className="flex justify-between">
<h2> <h2>
<FormattedMessage defaultMessage="Summary" description="Notifications summary" /> <FormattedMessage defaultMessage="Summary" description="Notifications summary" />
</h2> </h2>
<div className="flex g8"> <div className="flex items-center g8">
{filterIcon(NotificationSummaryFilter.Reactions, "heart-solid", "text-heart")} {filterIcon(NotificationSummaryFilter.Reactions, "heart-solid", "text-heart")}
{filterIcon(NotificationSummaryFilter.Zaps, "zap-solid", "text-zap")} {filterIcon(NotificationSummaryFilter.Zaps, "zap-solid", "text-zap")}
{filterIcon(NotificationSummaryFilter.Reposts, "reverse-left", "text-repost")} {filterIcon(NotificationSummaryFilter.Reposts, "reverse-left", "text-repost")}
@ -246,19 +246,19 @@ function NotificationSummary({ evs }: { evs: Array<TaggedNostrEvent> }) {
if (active && payload && payload.length) { if (active && payload && payload.length) {
return ( return (
<div className="summary-tooltip"> <div className="summary-tooltip">
<div className="flex-column g12"> <div className="flex flex-col g12">
<Icon name="heart-solid" className="text-heart" /> <Icon name="heart-solid" className="text-heart" />
{formatShort(payload.find(a => a.name === "reactions")?.value as number)} {formatShort(payload.find(a => a.name === "reactions")?.value as number)}
</div> </div>
<div className="flex-column g12"> <div className="flex flex-col g12">
<Icon name="zap-solid" className="text-zap" /> <Icon name="zap-solid" className="text-zap" />
{formatShort(payload.find(a => a.name === "zaps")?.value as number)} {formatShort(payload.find(a => a.name === "zaps")?.value as number)}
</div> </div>
<div className="flex-column g12"> <div className="flex flex-col g12">
<Icon name="reverse-left" className="text-repost" /> <Icon name="reverse-left" className="text-repost" />
{formatShort(payload.find(a => a.name === "reposts")?.value as number)} {formatShort(payload.find(a => a.name === "reposts")?.value as number)}
</div> </div>
<div className="flex-column g12"> <div className="flex flex-col g12">
<Icon name="at-sign" className="text-mention" /> <Icon name="at-sign" className="text-mention" />
{formatShort(payload.find(a => a.name === "mentions")?.value as number)} {formatShort(payload.find(a => a.name === "mentions")?.value as number)}
</div> </div>
@ -357,13 +357,13 @@ function NotificationGroup({ evs, onClick }: { evs: Array<TaggedNostrEvent>; onC
<div className="card notification-group" ref={ref}> <div className="card notification-group" ref={ref}>
{inView && ( {inView && (
<> <>
<div className="flex-column g12"> <div className="flex flex-col g12">
<div> <div>
<Icon name={iconName()} size={24} className={iconName()} /> <Icon name={iconName()} size={24} className={iconName()} />
</div> </div>
<div>{kind === EventKind.ZapReceipt && formatShort(totalZaps)}</div> <div>{kind === EventKind.ZapReceipt && formatShort(totalZaps)}</div>
</div> </div>
<div className="flex-column w-max g12"> <div className="flex flex-col w-max g12">
<div className="flex"> <div className="flex">
{pubkeys {pubkeys
.filter(a => a !== "anon") .filter(a => a !== "anon")

View File

@ -1,11 +1,12 @@
import "./ProfilePage.css"; import "./ProfilePage.css";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useNavigate, useParams } from "react-router-dom"; import { useLocation, useNavigate, useParams } from "react-router-dom";
import { import {
encodeTLV, encodeTLV,
encodeTLVEntries, encodeTLVEntries,
EventKind, EventKind,
MetadataCache,
NostrLink, NostrLink,
NostrPrefix, NostrPrefix,
TLVEntryType, TLVEntryType,
@ -58,18 +59,22 @@ import ProfileTab, {
RelaysTab, RelaysTab,
ZapsProfileTab, ZapsProfileTab,
} from "Pages/Profile/ProfileTab"; } from "Pages/Profile/ProfileTab";
import DisplayName from "../../Element/User/DisplayName"; import DisplayName from "Element/User/DisplayName";
import { UserWebsiteLink } from "Element/User/UserWebsiteLink"; import { UserWebsiteLink } from "Element/User/UserWebsiteLink";
interface ProfilePageProps { interface ProfilePageProps {
id?: string; id?: string;
state?: MetadataCache;
} }
export default function ProfilePage({ id: propId }: ProfilePageProps) { export default function ProfilePage({ id: propId, state }: ProfilePageProps) {
const params = useParams(); const params = useParams();
const location = useLocation();
const profileState = (location.state as MetadataCache | undefined) || state;
const navigate = useNavigate(); const navigate = useNavigate();
const [id, setId] = useState<string>(); const [id, setId] = useState<string | undefined>(profileState?.pubkey);
const user = useUserProfile(id); const [relays, setRelays] = useState<Array<string>>();
const user = useUserProfile(profileState ? undefined : id) || profileState;
const login = useLogin(); const login = useLogin();
const loginPubKey = login.publicKey; const loginPubKey = login.publicKey;
const isMe = loginPubKey === id; const isMe = loginPubKey === id;
@ -105,22 +110,24 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) {
const horizontalScroll = useHorizontalScroll(); const horizontalScroll = useHorizontalScroll();
useEffect(() => { useEffect(() => {
const resolvedId = propId || params.id; if (!id) {
if (resolvedId?.match(EmailRegex)) { const resolvedId = propId || params.id;
getNip05PubKey(resolvedId).then(a => { if (resolvedId?.match(EmailRegex)) {
setId(a); getNip05PubKey(resolvedId).then(a => {
}); setId(a);
} else { });
const nav = tryParseNostrLink(resolvedId ?? "");
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
// todo: use relays if any for nprofile
setId(nav.id);
} else { } else {
setId(parseId(resolvedId ?? "")); const nav = tryParseNostrLink(resolvedId ?? "");
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
setId(nav.id);
setRelays(nav.relays);
} else {
setId(parseId(resolvedId ?? ""));
}
} }
} }
setTab(ProfileTab.Notes); setTab(ProfileTab.Notes);
}, [propId, params]); }, [id, propId, params]);
function musicStatus() { function musicStatus() {
if (!status.music) return; if (!status.music) return;
@ -145,20 +152,11 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) {
return inner(); return inner();
} }
useEffect(() => {
if (user?.nip05 && user?.isNostrAddressValid) {
if (user.nip05.endsWith(`@${CONFIG.nip05Domain}`)) {
const username = user.nip05?.replace(`@${CONFIG.nip05Domain}`, "");
navigate(`/${username}`, { replace: true });
}
}
}, [user?.isNostrAddressValid, user?.nip05]);
function username() { function username() {
return ( return (
<> <>
<div className="flex-column g4"> <div className="flex flex-col g4">
<h2 className="flex g4"> <h2 className="flex items-center g4">
<DisplayName user={user} pubkey={user?.pubkey ?? ""} /> <DisplayName user={user} pubkey={user?.pubkey ?? ""} />
<FollowsYou followsMe={follows.includes(loginPubKey ?? "")} /> <FollowsYou followsMe={follows.includes(loginPubKey ?? "")} />
</h2> </h2>
@ -251,6 +249,7 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) {
type: "pubkey", type: "pubkey",
items: [id], items: [id],
discriminator: id.slice(0, 12), discriminator: id.slice(0, 12),
relay: relays,
}} }}
postsOnly={false} postsOnly={false}
method={"LIMIT_UNTIL"} method={"LIMIT_UNTIL"}

View File

@ -1,19 +1,21 @@
import useZapsFeed from "../../Feed/ZapsFeed"; import { FormattedMessage } from "react-intl";
import FormattedMessage from "../../Element/FormattedMessage";
import messages from "../messages";
import { formatShort } from "../../Number";
import useFollowersFeed from "../../Feed/FollowersFeed";
import FollowsList from "../../Element/User/FollowListBase";
import useFollowsFeed from "../../Feed/FollowsFeed";
import useRelaysFeed from "../../Feed/RelaysFeed";
import RelaysMetadata from "../../Element/Relay/RelaysMetadata";
import useBookmarkFeed from "../../Feed/BookmarkFeed";
import Bookmarks from "../../Element/Bookmarks";
import Icon from "../../Icons/Icon";
import { Tab } from "../../Element/Tabs";
import { EventKind, HexKey, NostrLink, NostrPrefix } from "@snort/system"; import { EventKind, HexKey, NostrLink, NostrPrefix } from "@snort/system";
import useZapsFeed from "Feed/ZapsFeed";
import { formatShort } from "Number";
import useFollowersFeed from "Feed/FollowersFeed";
import FollowsList from "Element/User/FollowListBase";
import useFollowsFeed from "Feed/FollowsFeed";
import useRelaysFeed from "Feed/RelaysFeed";
import RelaysMetadata from "Element/Relay/RelaysMetadata";
import useBookmarkFeed from "Feed/BookmarkFeed";
import Bookmarks from "Element/Bookmarks";
import Icon from "Icons/Icon";
import { Tab } from "Element/Tabs";
import { default as ZapElement } from "Element/Event/Zap"; import { default as ZapElement } from "Element/Event/Zap";
import messages from "../messages";
export enum ProfileTabType { export enum ProfileTabType {
NOTES = 0, NOTES = 0,
REACTIONS = 1, REACTIONS = 1,

View File

@ -1,6 +1,6 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { Link, Outlet, RouteObject, useParams } from "react-router-dom"; import { Link, Outlet, RouteObject, useParams } from "react-router-dom";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { unixNow } from "@snort/shared"; import { unixNow } from "@snort/shared";
import { NostrLink } from "@snort/system"; import { NostrLink } from "@snort/system";
import { SnortContext } from "@snort/system-react"; import { SnortContext } from "@snort/system-react";
@ -68,7 +68,7 @@ export const GlobalTab = () => {
const subject: TimelineSubject = { const subject: TimelineSubject = {
type: "global", type: "global",
items: [], items: [],
relay: relay?.url, relay: relay?.url ? [relay.url] : undefined,
discriminator: `all-${sha256(relay?.url ?? "").slice(0, 12)}`, discriminator: `all-${sha256(relay?.url ?? "").slice(0, 12)}`,
}; };
@ -78,7 +78,7 @@ export const GlobalTab = () => {
const paidRelays = allRelays.filter(a => a.paid); const paidRelays = allRelays.filter(a => a.paid);
const publicRelays = allRelays.filter(a => !a.paid); const publicRelays = allRelays.filter(a => !a.paid);
return ( return (
<div className="flex mb10 f-end nowrap"> <div className="flex items-center mb10 justify-end nowrap">
<FormattedMessage <FormattedMessage
defaultMessage="Read global from" defaultMessage="Read global from"
description="Label for reading global feed from specific relays" description="Label for reading global feed from specific relays"

View File

@ -72,7 +72,7 @@ const SearchPage = () => {
function sortOptions() { function sortOptions() {
if (tab.value != PROFILES) return null; if (tab.value != PROFILES) return null;
return ( return (
<div className="flex mb10 f-end"> <div className="flex mb10 justify-end">
<FormattedMessage defaultMessage="Sort" description="Label for sorting options for people search" /> <FormattedMessage defaultMessage="Sort" description="Label for sorting options for people search" />
&nbsp; &nbsp;
<select onChange={e => setSortPopular(e.target.value == "true")} value={sortPopular ? "true" : "false"}> <select onChange={e => setSortPopular(e.target.value == "true")} value={sortPopular ? "true" : "false"}>
@ -99,7 +99,7 @@ const SearchPage = () => {
<div className="flex mb10"> <div className="flex mb10">
<input <input
type="text" type="text"
className="f-grow mr10" className="grow mr10"
placeholder={formatMessage({ defaultMessage: "Search..." })} placeholder={formatMessage({ defaultMessage: "Search..." })}
value={search} value={search}
onChange={e => setSearch(e.target.value)} onChange={e => setSearch(e.target.value)}

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Outlet, RouteObject, useNavigate } from "react-router-dom"; import { Outlet, RouteObject, useNavigate } from "react-router-dom";
import SettingsIndex from "Pages/settings/Root"; import SettingsIndex from "Pages/settings/Root";
import Profile from "Pages/settings/Profile"; import Profile from "Pages/settings/Profile";

View File

@ -86,7 +86,7 @@ export default function WalletPage() {
<FormattedMessage defaultMessage="Enter wallet password" /> <FormattedMessage defaultMessage="Enter wallet password" />
</h3> </h3>
<div className="flex w-max"> <div className="flex w-max">
<div className="f-grow mr10"> <div className="grow mr10">
<input <input
type="password" type="password"
placeholder={formatMessage({ placeholder={formatMessage({
@ -140,7 +140,7 @@ export default function WalletPage() {
</h3> </h3>
{history?.map(a => ( {history?.map(a => (
<div className="card flex wallet-history-item" key={a.timestamp}> <div className="card flex wallet-history-item" key={a.timestamp}>
<div className="f-grow f-col"> <div className="grow flex-col">
<NoteTime from={a.timestamp * 1000} fallback={formatMessage({ defaultMessage: "now" })} /> <NoteTime from={a.timestamp * 1000} fallback={formatMessage({ defaultMessage: "now" })} />
<div>{(a.memo ?? "").length === 0 ? <>&nbsp;</> : a.memo}</div> <div>{(a.memo ?? "").length === 0 ? <>&nbsp;</> : a.memo}</div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";

View File

@ -80,7 +80,7 @@ export default function ImportFollows() {
<input <input
type="text" type="text"
placeholder={formatMessage(messages.TwitterPlaceholder)} placeholder={formatMessage(messages.TwitterPlaceholder)}
className="f-grow mr10" className="grow mr10"
value={twitterUsername} value={twitterUsername}
onChange={e => setTwitterUsername(e.target.value)} onChange={e => setTwitterUsername(e.target.value)}
/> />

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Logo from "Element/Logo"; import Logo from "Element/Logo";
@ -102,7 +102,7 @@ export default function NewUserFlow() {
<h1> <h1>
<FormattedMessage {...messages.SaveKeys} /> <FormattedMessage {...messages.SaveKeys} />
</h1> </h1>
<div className="flex f-space"> <div className="flex justify-between">
<FormattedMessage defaultMessage="Language" /> <FormattedMessage defaultMessage="Language" />
<select <select
value={login.preferences.language || DefaultPreferences.language} value={login.preferences.language || DefaultPreferences.language}

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import ProfilePreview from "Element/User/ProfilePreview"; import ProfilePreview from "Element/User/ProfilePreview";
@ -10,7 +10,7 @@ export default function AccountsPage() {
const sub = getActiveSubscriptions(LoginStore.allSubscriptions()); const sub = getActiveSubscriptions(LoginStore.allSubscriptions());
return ( return (
<div className="flex-column g12"> <div className="flex flex-col g12">
<h3> <h3>
<FormattedMessage defaultMessage="Logins" /> <FormattedMessage defaultMessage="Logins" />
</h3> </h3>

View File

@ -21,7 +21,7 @@
.mnemonic-grid .word > div:nth-of-type(1) { .mnemonic-grid .word > div:nth-of-type(1) {
background-color: var(--gray); background-color: var(--gray);
padding: 4px 8px; padding: 4px 8px;
min-width: 1.5em; min-width: 2em;
font-variant-numeric: ordinal; font-variant-numeric: ordinal;
} }

View File

@ -1,5 +1,5 @@
import "./Keys.css"; import "./Keys.css";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { encodeTLV, KeyStorage, NostrPrefix } from "@snort/system"; import { encodeTLV, KeyStorage, NostrPrefix } from "@snort/system";
import Copy from "Element/Copy"; import Copy from "Element/Copy";
@ -10,7 +10,7 @@ import { hexToBech32 } from "SnortUtils";
export default function ExportKeys() { export default function ExportKeys() {
const { publicKey, privateKeyData, generatedEntropy } = useLogin(); const { publicKey, privateKeyData, generatedEntropy } = useLogin();
return ( return (
<div className="flex-column g12"> <div className="flex flex-col g12">
<h2> <h2>
<FormattedMessage defaultMessage="Public Key" /> <FormattedMessage defaultMessage="Public Key" />
</h2> </h2>
@ -33,7 +33,7 @@ export default function ExportKeys() {
{hexToMnemonic(generatedEntropy ?? "") {hexToMnemonic(generatedEntropy ?? "")
.split(" ") .split(" ")
.map((a, i) => ( .map((a, i) => (
<div className="flex word"> <div className="flex items-center word">
<div>{i + 1}</div> <div>{i + 1}</div>
<div>{a}</div> <div>{a}</div>
</div> </div>

View File

@ -3,7 +3,7 @@ import useLogin from "Hooks/useLogin";
import { setAppData } from "Login"; import { setAppData } from "Login";
import { appendDedupe } from "SnortUtils"; import { appendDedupe } from "SnortUtils";
import { useState } from "react"; import { useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
export function ModerationSettings() { export function ModerationSettings() {
const login = useLogin(); const login = useLogin();
@ -26,28 +26,41 @@ export function ModerationSettings() {
); );
setMuteWord(""); setMuteWord("");
} }
function removeMutedWord(word: string) {
setAppData(
login,
{
...login.appData.item,
mutedWords: login.appData.item.mutedWords.filter(a => a !== word),
},
unixNowMs(),
);
setMuteWord("");
}
return ( return (
<> <>
<h2> <h2>
<FormattedMessage defaultMessage="Muted Words" /> <FormattedMessage defaultMessage="Muted Words" />
</h2> </h2>
<div className="flex-column g12"> <div className="flex flex-col g12">
<div className="flex g8"> <div className="flex g8">
<input <input
type="text" type="text"
placeholder="eg. crypto" placeholder="eg. crypto"
className="w-max" className="w-max"
value={muteWord} value={muteWord}
onChange={e => setMuteWord(e.target.value)} onChange={e => setMuteWord(e.target.value.toLowerCase())}
/> />
<button type="button" onClick={addMutedWord}> <button type="button" onClick={addMutedWord}>
<FormattedMessage defaultMessage="Add" /> <FormattedMessage defaultMessage="Add" />
</button> </button>
</div> </div>
{login.appData.item.mutedWords.map(v => ( {login.appData.item.mutedWords.map(v => (
<div className="p br b flex f-space"> <div className="p br b flex items-center justify-between">
<div>{v}</div> <div>{v}</div>
<button type="button"> <button type="button" onClick={() => removeMutedWord(v)}>
<FormattedMessage defaultMessage="Delete" /> <FormattedMessage defaultMessage="Delete" />
</button> </button>
</div> </div>

View File

@ -1,12 +1,10 @@
import "./Preferences.css"; import "./Preferences.css";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { useEffect, useState } from "react";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import { DefaultPreferences, updatePreferences, UserPreferences } from "Login"; import { DefaultPreferences, updatePreferences, UserPreferences } from "Login";
import { DefaultImgProxy } from "Const"; import { DefaultImgProxy } from "Const";
import { unwrap } from "SnortUtils"; import { unwrap } from "SnortUtils";
import searchEmoji from "emoji-search";
import messages from "./messages"; import messages from "./messages";
@ -37,24 +35,15 @@ export const AllLanguageCodes = [
const PreferencesPage = () => { const PreferencesPage = () => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const login = useLogin(); const login = useLogin();
console.debug(login);
const perf = login.preferences; const perf = login.preferences;
const [emoji, setEmoji] = useState<Array<{ name: string; char: string }>>([]);
useEffect(() => {
(async () => {
const allEmoji = await searchEmoji("");
setEmoji(allEmoji.map(a => ({ name: a.name, char: a.char })));
})();
}, []);
return ( return (
<div className="preferences flex-column g24"> <div className="preferences flex flex-col g24">
<h3> <h3>
<FormattedMessage {...messages.Preferences} /> <FormattedMessage {...messages.Preferences} />
</h3> </h3>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<h4> <h4>
<FormattedMessage defaultMessage="Language" /> <FormattedMessage defaultMessage="Language" />
</h4> </h4>
@ -78,7 +67,7 @@ const PreferencesPage = () => {
</select> </select>
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<h4> <h4>
<FormattedMessage {...messages.Theme} /> <FormattedMessage {...messages.Theme} />
</h4> </h4>
@ -103,7 +92,7 @@ const PreferencesPage = () => {
</select> </select>
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<h4> <h4>
<FormattedMessage {...messages.DefaultRootTab} /> <FormattedMessage {...messages.DefaultRootTab} />
</h4> </h4>
@ -128,8 +117,8 @@ const PreferencesPage = () => {
</select> </select>
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage defaultMessage="Send usage metrics" /> <FormattedMessage defaultMessage="Send usage metrics" />
</h4> </h4>
@ -146,7 +135,7 @@ const PreferencesPage = () => {
</div> </div>
</div> </div>
<div className="flex w-max"> <div className="flex w-max">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage {...messages.AutoloadMedia} /> <FormattedMessage {...messages.AutoloadMedia} />
</h4> </h4>
@ -176,8 +165,8 @@ const PreferencesPage = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage defaultMessage="Check Signatures" /> <FormattedMessage defaultMessage="Check Signatures" />
</h4> </h4>
@ -193,8 +182,8 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage defaultMessage="Proof of Work" /> <FormattedMessage defaultMessage="Proof of Work" />
</h4> </h4>
@ -211,7 +200,7 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<h4> <h4>
<FormattedMessage defaultMessage="Default Zap amount" /> <FormattedMessage defaultMessage="Default Zap amount" />
</h4> </h4>
@ -224,8 +213,8 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage defaultMessage="Show Badges" /> <FormattedMessage defaultMessage="Show Badges" />
</h4> </h4>
@ -241,8 +230,8 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage defaultMessage="Show Status" /> <FormattedMessage defaultMessage="Show Status" />
</h4> </h4>
@ -258,8 +247,8 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage defaultMessage="Auto Zap" /> <FormattedMessage defaultMessage="Auto Zap" />
</h4> </h4>
@ -275,9 +264,9 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex-column"> <div className="flex flex-col">
<div className="flex f-space"> <div className="flex justify-between">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage {...messages.ImgProxy} /> <FormattedMessage {...messages.ImgProxy} />
</h4> </h4>
@ -375,8 +364,8 @@ const PreferencesPage = () => {
</div> </div>
)} )}
</div> </div>
<div className="flex f-space w-max"> <div className="flex justify-between w-max">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage {...messages.EnableReactions} /> <FormattedMessage {...messages.EnableReactions} />
</h4> </h4>
@ -392,36 +381,28 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage {...messages.ReactionEmoji} /> <FormattedMessage {...messages.ReactionEmoji} />
</h4> </h4>
<small> <small>
<FormattedMessage {...messages.ReactionEmojiHelp} /> <FormattedMessage {...messages.ReactionEmojiHelp} />
</small> </small>
<select <input
className="emoji-selector" type="text"
value={perf.reactionEmoji} value={perf.reactionEmoji}
onChange={e => onChange={e => {
const split = e.target.value.match(/[\p{L}\S]{1}/u);
console.debug(e.target.value, split);
updatePreferences(login, { updatePreferences(login, {
...perf, ...perf,
reactionEmoji: e.target.value, reactionEmoji: split?.[0] ?? "",
}) });
}> }}
<option value="+"> />
+ <FormattedMessage {...messages.Default} />
</option>
{emoji.map(({ name, char }) => {
return (
<option value={char}>
{name} {char}
</option>
);
})}
</select>
</div> </div>
<div className="flex f-space"> <div className="flex justify-between">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage {...messages.ConfirmReposts} /> <FormattedMessage {...messages.ConfirmReposts} />
</h4> </h4>
@ -437,8 +418,8 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex f-space"> <div className="flex justify-between">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage {...messages.ShowLatest} /> <FormattedMessage {...messages.ShowLatest} />
</h4> </h4>
@ -454,7 +435,7 @@ const PreferencesPage = () => {
/> />
</div> </div>
</div> </div>
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage {...messages.FileUpload} /> <FormattedMessage {...messages.FileUpload} />
</h4> </h4>
@ -476,8 +457,8 @@ const PreferencesPage = () => {
<option value="nostrimg.com">nostrimg.com</option> <option value="nostrimg.com">nostrimg.com</option>
</select> </select>
</div> </div>
<div className="flex f-space"> <div className="flex justify-between">
<div className="flex-column g8"> <div className="flex flex-col g8">
<h4> <h4>
<FormattedMessage {...messages.DebugMenus} /> <FormattedMessage {...messages.DebugMenus} />
</h4> </h4>

View File

@ -1,6 +1,5 @@
import "./Profile.css"; import "./Profile.css";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FormattedMessage from "Element/FormattedMessage";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { mapEventToProfile } from "@snort/system"; import { mapEventToProfile } from "@snort/system";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
@ -13,6 +12,7 @@ import { UserCache } from "Cache";
import useLogin from "Hooks/useLogin"; import useLogin from "Hooks/useLogin";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import Avatar from "Element/User/Avatar"; import Avatar from "Element/User/Avatar";
import { FormattedMessage } from "react-intl";
export interface ProfileSettingsProps { export interface ProfileSettingsProps {
avatar?: boolean; avatar?: boolean;
@ -107,8 +107,8 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
function editor() { function editor() {
return ( return (
<div className="flex-column g24"> <div className="flex flex-col g24">
<div className="flex-column w-max g8"> <div className="flex flex-col w-max g8">
<h4> <h4>
<FormattedMessage defaultMessage="Name" /> <FormattedMessage defaultMessage="Name" />
</h4> </h4>
@ -120,7 +120,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
disabled={readonly} disabled={readonly}
/> />
</div> </div>
<div className="flex-column w-max g8"> <div className="flex flex-col w-max g8">
<h4> <h4>
<FormattedMessage defaultMessage="About" /> <FormattedMessage defaultMessage="About" />
</h4> </h4>
@ -130,7 +130,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
value={about} value={about}
disabled={readonly}></textarea> disabled={readonly}></textarea>
</div> </div>
<div className="flex-column w-max g8"> <div className="flex flex-col w-max g8">
<h4> <h4>
<FormattedMessage defaultMessage="Website" /> <FormattedMessage defaultMessage="Website" />
</h4> </h4>
@ -142,11 +142,11 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
disabled={readonly} disabled={readonly}
/> />
</div> </div>
<div className="flex-column w-max g8"> <div className="flex flex-col w-max g8">
<h4> <h4>
<FormattedMessage defaultMessage="Nostr Address" /> <FormattedMessage defaultMessage="Nostr Address" />
</h4> </h4>
<div className="flex-column g8 w-max"> <div className="flex flex-col g8 w-max">
<input <input
type="text" type="text"
className="w-max" className="w-max"
@ -158,16 +158,16 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<FormattedMessage defaultMessage="Usernames are not unique on Nostr. The nostr address is your unique human-readable address that is unique to you upon registration." /> <FormattedMessage defaultMessage="Usernames are not unique on Nostr. The nostr address is your unique human-readable address that is unique to you upon registration." />
</small> </small>
<div className="flex g12"> <div className="flex g12">
<button className="flex f-center" type="button" onClick={() => navigate("/nostr-address")}> <button className="flex items-center" type="button" onClick={() => navigate("/nostr-address")}>
<FormattedMessage defaultMessage="Buy nostr address" /> <FormattedMessage defaultMessage="Buy nostr address" />
</button> </button>
<button className="flex f-center secondary" type="button" onClick={() => navigate("/free-nostr-address")}> <button className="flex items-center secondary" type="button" onClick={() => navigate("/free-nostr-address")}>
<FormattedMessage defaultMessage="Get a free one" /> <FormattedMessage defaultMessage="Get a free one" />
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div className="flex-column w-max g8"> <div className="flex flex-col w-max g8">
<h4> <h4>
<FormattedMessage defaultMessage="Lightning Address" /> <FormattedMessage defaultMessage="Lightning Address" />
</h4> </h4>
@ -207,7 +207,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<Avatar pubkey={id} user={user} image={picture} /> <Avatar pubkey={id} user={user} image={picture} />
<AsyncButton <AsyncButton
type="button" type="button"
className="circle flex f-center" className="circle flex align-centerjustify-between"
onClick={() => setNewAvatar()} onClick={() => setNewAvatar()}
disabled={readonly}> disabled={readonly}>
<Icon name="upload-01" /> <Icon name="upload-01" />

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import ProfilePreview from "Element/User/ProfilePreview"; import ProfilePreview from "Element/User/ProfilePreview";
import useRelayState from "Feed/RelayState"; import useRelayState from "Feed/RelayState";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
@ -37,10 +37,10 @@ const RelayInfo = () => {
)} )}
{stats?.info?.software && ( {stats?.info?.software && (
<div className="flex"> <div className="flex">
<h4 className="f-grow"> <h4 className="grow">
<FormattedMessage {...messages.Software} /> <FormattedMessage {...messages.Software} />
</h4> </h4>
<div className="flex f-col"> <div className="flex flex-col">
{stats.info.software.startsWith("http") ? ( {stats.info.software.startsWith("http") ? (
<a href={stats.info.software} target="_blank" rel="noreferrer"> <a href={stats.info.software} target="_blank" rel="noreferrer">
{stats.info.software} {stats.info.software}
@ -57,7 +57,7 @@ const RelayInfo = () => {
)} )}
{stats?.info?.contact && ( {stats?.info?.contact && (
<div className="flex"> <div className="flex">
<h4 className="f-grow"> <h4 className="grow">
<FormattedMessage {...messages.Contact} /> <FormattedMessage {...messages.Contact} />
</h4> </h4>
<a <a
@ -73,7 +73,7 @@ const RelayInfo = () => {
<h4> <h4>
<FormattedMessage {...messages.Supports} /> <FormattedMessage {...messages.Supports} />
</h4> </h4>
<div className="f-grow"> <div className="grow">
{stats.info.supported_nips.map(a => ( {stats.info.supported_nips.map(a => (
<a target="_blank" rel="noreferrer" href={`https://nips.be/${a}`} className="pill"> <a target="_blank" rel="noreferrer" href={`https://nips.be/${a}`} className="pill">
NIP-{a.toString().padStart(2, "0")} NIP-{a.toString().padStart(2, "0")}
@ -85,7 +85,7 @@ const RelayInfo = () => {
<h4> <h4>
<FormattedMessage defaultMessage="Active Subscriptions" /> <FormattedMessage defaultMessage="Active Subscriptions" />
</h4> </h4>
<div className="f-grow"> <div className="grow">
{stats?.activeRequests.map(a => ( {stats?.activeRequests.map(a => (
<span className="pill" key={a}> <span className="pill" key={a}>
{a} {a}
@ -95,14 +95,14 @@ const RelayInfo = () => {
<h4> <h4>
<FormattedMessage defaultMessage="Pending Subscriptions" /> <FormattedMessage defaultMessage="Pending Subscriptions" />
</h4> </h4>
<div className="f-grow"> <div className="grow">
{stats?.pendingRequests.map(a => ( {stats?.pendingRequests.map(a => (
<span className="pill" key={a}> <span className="pill" key={a}>
{a} {a}
</span> </span>
))} ))}
</div> </div>
<div className="flex mt10 f-end"> <div className="flex mt10 justify-end">
<div <div
className="btn error" className="btn error"
onClick={() => { onClick={() => {

View File

@ -1,5 +1,5 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { unixNowMs } from "@snort/shared"; import { unixNowMs } from "@snort/shared";
import { randomSample } from "SnortUtils"; import { randomSample } from "SnortUtils";

View File

@ -1,6 +1,6 @@
import "./Root.css"; import "./Root.css";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Outlet, useLocation, useNavigate } from "react-router-dom"; import { Outlet, useLocation, useNavigate } from "react-router-dom";
import Icon from "Icons/Icon"; import Icon from "Icons/Icon";
import { LoginStore, logout } from "Login"; import { LoginStore, logout } from "Login";

View File

@ -1,6 +1,6 @@
import "./WalletSettings.css"; import "./WalletSettings.css";
import LndLogo from "lnd-logo.png"; import LndLogo from "lnd-logo.png";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Link, RouteObject, useNavigate } from "react-router-dom"; import { Link, RouteObject, useNavigate } from "react-router-dom";
import BlueWallet from "Icons/BlueWallet"; import BlueWallet from "Icons/BlueWallet";

View File

@ -50,7 +50,7 @@ export default function LNForwardAddress({ handle }: { handle: ManageHandle }) {
<FormattedMessage defaultMessage="Your handle will act like a lightning address and will redirect to your chosen LNURL or Lightning address" /> <FormattedMessage defaultMessage="Your handle will act like a lightning address and will redirect to your chosen LNURL or Lightning address" />
</p> </p>
<div className="flex"> <div className="flex">
<div className="f-grow"> <div className="grow">
<input <input
type="text" type="text"
className="w-max mr10" className="w-max mr10"

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { ApiHost } from "Const"; import { ApiHost } from "Const";
@ -37,14 +37,14 @@ export default function ListHandles() {
/> />
)} )}
{handles.map(a => ( {handles.map(a => (
<div className="flex f-space" key={a.id}> <div className="flex items-center justify-between" key={a.id}>
<h4 className="nip05"> <h4 className="nip05">
{a.handle}@ {a.handle}@
<span className="domain" data-domain={a.domain?.toLowerCase()}> <span className="domain" data-domain={a.domain?.toLowerCase()}>
{a.domain} {a.domain}
</span> </span>
</h4> </h4>
<button <button type="button"
onClick={() => onClick={() =>
navigate("manage", { navigate("manage", {
state: a, state: a,
@ -55,7 +55,7 @@ export default function ListHandles() {
</div> </div>
))} ))}
{handles.length > 0 && ( {handles.length > 0 && (
<button onClick={() => navigate("/nostr-address")}> <button type="button" onClick={() => navigate("/nostr-address")}>
<FormattedMessage defaultMessage="Buy Handle" /> <FormattedMessage defaultMessage="Buy Handle" />
</button> </button>
)} )}

View File

@ -34,7 +34,7 @@ export default function TransferHandle({ handle }: { handle: ManageHandle }) {
<FormattedMessage defaultMessage="Transfer to Pubkey" /> <FormattedMessage defaultMessage="Transfer to Pubkey" />
</h4> </h4>
<div className="flex"> <div className="flex">
<div className="f-grow"> <div className="grow">
<input <input
type="text" type="text"
className="w-max mr10" className="w-max mr10"

View File

@ -1,4 +1,4 @@
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Outlet, RouteObject, useNavigate } from "react-router-dom"; import { Outlet, RouteObject, useNavigate } from "react-router-dom";
import ListHandles from "./ListHandles"; import ListHandles from "./ListHandles";

View File

@ -51,7 +51,7 @@ const ConnectCashu = () => {
<FormattedMessage defaultMessage="Enter mint URL" /> <FormattedMessage defaultMessage="Enter mint URL" />
</h4> </h4>
<div className="flex"> <div className="flex">
<div className="f-grow mr10"> <div className="grow mr10">
<input <input
type="text" type="text"
placeholder="Mint URL" placeholder="Mint URL"

View File

@ -57,7 +57,7 @@ const ConnectLNC = () => {
<FormattedMessage defaultMessage="Enter pairing phrase" /> <FormattedMessage defaultMessage="Enter pairing phrase" />
</h4> </h4>
<div className="flex"> <div className="flex">
<div className="f-grow mr10"> <div className="grow mr10">
<input <input
type="text" type="text"
placeholder={formatMessage({ defaultMessage: "Pairing phrase" })} placeholder={formatMessage({ defaultMessage: "Pairing phrase" })}
@ -78,7 +78,7 @@ const ConnectLNC = () => {
function flowSetPassword() { function flowSetPassword() {
if (!connectedLNC) return null; if (!connectedLNC) return null;
return ( return (
<div className="flex f-col"> <div className="flex flex-col">
<h3> <h3>
<FormattedMessage <FormattedMessage
defaultMessage="Connected to: {node} 🎉" defaultMessage="Connected to: {node} 🎉"
@ -91,7 +91,7 @@ const ConnectLNC = () => {
<FormattedMessage defaultMessage="Enter password" /> <FormattedMessage defaultMessage="Enter password" />
</h4> </h4>
<div className="flex w-max"> <div className="flex w-max">
<div className="f-grow mr10"> <div className="grow mr10">
<input <input
type="password" type="password"
placeholder={formatMessage({ defaultMessage: "Wallet password" })} placeholder={formatMessage({ defaultMessage: "Wallet password" })}

View File

@ -49,7 +49,7 @@ const ConnectLNDHub = () => {
<FormattedMessage defaultMessage="Enter LNDHub config" /> <FormattedMessage defaultMessage="Enter LNDHub config" />
</h4> </h4>
<div className="flex"> <div className="flex">
<div className="f-grow mr10"> <div className="grow mr10">
<input <input
type="text" type="text"
placeholder="lndhub://username:password@lndhub.io" placeholder="lndhub://username:password@lndhub.io"

View File

@ -49,7 +49,7 @@ const ConnectNostrWallet = () => {
<FormattedMessage defaultMessage="Enter Nostr Wallet Connect config" /> <FormattedMessage defaultMessage="Enter Nostr Wallet Connect config" />
</h4> </h4>
<div className="flex"> <div className="flex">
<div className="f-grow mr10"> <div className="grow mr10">
<input <input
type="text" type="text"
placeholder="nostr+walletconnect:<pubkey>?relay=<relay>&secret=<secret>" placeholder="nostr+walletconnect:<pubkey>?relay=<relay>&secret=<secret>"

View File

@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import FormattedMessage from "Element/FormattedMessage"; import { FormattedMessage } from "react-intl";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import PageSpinner from "Element/PageSpinner"; import PageSpinner from "Element/PageSpinner";
@ -33,7 +33,7 @@ export default function ManageSubscriptionPage() {
return <PageSpinner />; return <PageSpinner />;
} }
return ( return (
<div className="main-content p flex-column g16"> <div className="main-content p flex flex-col g16">
<h2> <h2>
<FormattedMessage defaultMessage="Subscriptions" /> <FormattedMessage defaultMessage="Subscriptions" />
</h2> </h2>

View File

@ -120,7 +120,11 @@ export default function SubscriptionCard({ sub }: { sub: Subscription }) {
<div className="flex flex-col g4"> <div className="flex flex-col g4">
<span>&nbsp;</span> <span>&nbsp;</span>
<AsyncButton onClick={() => renew(sub.id, months)}> <AsyncButton onClick={() => renew(sub.id, months)}>
{isExpired ? <FormattedMessage defaultMessage="Renew" /> : <FormattedMessage defaultMessage="Pay Now" />} {isExpired ? (
<FormattedMessage defaultMessage="Renew" />
) : (
<FormattedMessage defaultMessage="Pay Now" />
)}
</AsyncButton> </AsyncButton>
</div> </div>
<div className="flex flex-col g4"> <div className="flex flex-col g4">

Some files were not shown because too many files have changed in this diff Show More