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;
justify-content: center;
align-items: center;
gap: 8px;
}
.light .spinner-button {

View File

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

View File

@ -1,3 +1,5 @@
import { MetadataCache } from "@snort/system";
import { ChatParticipant } from "chat";
import NoteToSelf from "../User/NoteToSelf";
import ProfileImage from "../User/ProfileImage";
@ -6,7 +8,7 @@ import useLogin from "Hooks/useLogin";
export function ChatParticipantProfile({ participant }: { participant: ChatParticipant }) {
const { publicKey } = useLogin(s => ({ publicKey: s.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 WriteMessage from "Element/Chat/WriteMessage";
import { Chat, createEmptyChatObject, useChatSystem } from "chat";
import FormattedMessage from "Element/FormattedMessage";
import { FormattedMessage } from "react-intl";
import { ChatParticipantProfile } from "./ChatParticipant";
export default function DmWindow({ id }: { id: string }) {
@ -32,7 +32,7 @@ export default function DmWindow({ id }: { id: string }) {
<div className="dm-window">
<div>{sender()}</div>
<div>
<div className="flex f-col">{chat && <DmChatSelected chat={chat} />}</div>
<div className="flex flex-col">{chat && <DmChatSelected chat={chat} />}</div>
</div>
<div>
<WriteMessage chat={chat} />

View File

@ -80,7 +80,7 @@ export default function WriteMessage({ chat }: { chat: Chat }) {
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} />}
</button>
<div className="w-max">
@ -97,7 +97,7 @@ export default function WriteMessage({ chat }: { chat: Chat }) {
/>
{error && <b className="error">{error}</b>}
</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} />}
</button>
</>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,17 @@
import { Link } from "react-router-dom";
import { HexKey } from "@snort/system";
import { NostrLink, NostrPrefix } from "@snort/system";
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 }) {
const user = useUserProfile(pubkey);
import DisplayName from "Element/User/DisplayName";
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 (
<Link to={profileLink(pubkey, relays)} onClick={e => e.stopPropagation()}>
@<DisplayName user={user} pubkey={pubkey} />
</Link>
<ProfileLink pubkey={link.id} user={profile} onClick={e => e.stopPropagation()}>
@<DisplayName user={profile} pubkey={link.id} />
</ProfileLink>
);
}

View File

@ -8,7 +8,7 @@ export default function NostrLink({ link, depth }: { link: string; depth?: numbe
const nav = tryParseNostrLink(link);
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) {
if ((depth ?? 0) > 0) {
const evLink = nav.encode();

View File

@ -4,7 +4,7 @@ import { NostrEvent, NostrLink } from "@snort/system";
import { ProxyImg } from "Element/ProxyImg";
import ProfileImage from "Element/User/ProfileImage";
import FormattedMessage from "Element/FormattedMessage";
import { FormattedMessage } from "react-intl";
export default function ZapstrEmbed({ ev }: { ev: NostrEvent }) {
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">
<ProxyImg src={cover?.[1] ?? ""} size={100} />
<div className="flex f-col">
<div className="flex flex-col">
<div>
<h3>{subject?.[1] ?? ""}</h3>
</div>

View File

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

View File

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

View File

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

View File

@ -71,7 +71,7 @@ export function NoteBroadcaster({
}
return (
<div className="flex-column g16">
<div className="flex flex-col g16">
<h3>
<FormattedMessage defaultMessage="Sending notes and other stuff" />
</h3>
@ -81,7 +81,7 @@ export function NoteBroadcaster({
.map(r => (
<div className="flex-row g16">
<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>
{r.message && <small>{r.message}</small>}
</div>
@ -89,7 +89,7 @@ export function NoteBroadcaster({
<div className="flex g8">
<AsyncButton
onClick={() => retryPublish(r)}
className="p4 br-compact flex f-center secondary"
className="p4 br-compact flex items-center secondary"
title={formatMessage({
defaultMessage: "Retry publishing",
})}>
@ -97,7 +97,7 @@ export function NoteBroadcaster({
</AsyncButton>
<AsyncButton
onClick={() => removeRelayFromResult(r)}
className="p4 br-compact flex f-center secondary"
className="p4 br-compact flex items-center secondary"
title={formatMessage({
defaultMessage: "Remove from my relays",
})}>

View File

@ -291,11 +291,11 @@ export function NoteCreator() {
function renderRelayCustomisation() {
return (
<div className="flex-column g8">
<div className="flex flex-col g8">
{Object.keys(relays.item || {})
.filter(el => relays.item[el].write)
.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>
<input
@ -353,15 +353,15 @@ export function NoteCreator() {
</p>
{renderRelayCustomisation()}
</div>
<div className="flex-column g8">
<div className="flex flex-col g8">
<h4>
<FormattedMessage defaultMessage="Zap Splits" />
</h4>
<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) => (
<div className="flex f-center g8">
<div className="flex-column f-4 g4">
<div className="flex items-center g8">
<div className="flex flex-col f-4 g4">
<h4>
<FormattedMessage defaultMessage="Recipient" />
</h4>
@ -376,7 +376,7 @@ export function NoteCreator() {
placeholder={formatMessage({ defaultMessage: "npub / nprofile / nostr address" })}
/>
</div>
<div className="flex-column f-1 g4">
<div className="flex flex-col f-1 g4">
<h4>
<FormattedMessage defaultMessage="Weight" />
</h4>
@ -394,7 +394,7 @@ export function NoteCreator() {
}
/>
</div>
<div className="flex-column f-shrink g4">
<div className="flex flex-col s g4">
<div>&nbsp;</div>
<Icon
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" />
</span>
</div>
<div className="flex-column g8">
<div className="flex flex-col g8">
<h4>
<FormattedMessage defaultMessage="Sensitive Content" />
</h4>
@ -441,7 +441,7 @@ export function NoteCreator() {
function noteCreatorFooter() {
return (
<div className="flex f-space">
<div className="flex justify-between">
<div className="flex g8">
<ProfileImage
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 { useEventReactions } from "@snort/system-react";
import { findTag, hexToBech32, profileLink } from "SnortUtils";
import { findTag, hexToBech32 } from "SnortUtils";
import useModeration from "Hooks/useModeration";
import useLogin from "Hooks/useLogin";
import useEventPublisher from "Hooks/useEventPublisher";
import { NoteContextMenu, NoteTranslation } from "./NoteContextMenu";
import { UserCache } from "../../Cache";
import { UserCache } from "Cache";
import messages from "../messages";
import { setBookmarked, setPinned } from "../../Login";
import { setBookmarked, setPinned } from "Login";
import Text from "../Text";
import Reveal from "./Reveal";
import Poll from "./Poll";
import ProfileImage from "../User/ProfileImage";
import Icon from "../../Icons/Icon";
import Icon from "Icons/Icon";
import NoteTime from "./NoteTime";
import NoteFooter from "./NoteFooter";
import Reactions from "./Reactions";
import HiddenNote from "./HiddenNote";
import { NoteProps } from "./Note";
import { chainKey } from "Hooks/useThreadContext";
import { ProfileLink } from "Element/User/ProfileLink";
export function NoteInner(props: NoteProps) {
const { data: ev, related, highlight, options: opt, ignoreModeration = false, className } = props;
@ -182,7 +183,11 @@ export function NoteInner(props: NoteProps) {
mentions.push({
pk,
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));

View File

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

View File

@ -108,7 +108,7 @@ export default function Poll(props: PollProps) {
return (
<>
<div className="flex f-space p">
<div className="flex justify-between p">
<small>
<FormattedMessage
defaultMessage="You are voting with {amount} sats"
@ -147,7 +147,7 @@ export default function Poll(props: PollProps) {
const weight = totalVotes === 0 ? 0 : total / totalVotes;
return (
<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 && (
<>
<div className="flex">

View File

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

View File

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

View File

@ -19,7 +19,7 @@ export function ZapGoal({ ev }: { ev: NostrEvent }) {
return (
<div className="zap-goal card">
<div className="flex f-space">
<div className="flex justify-between">
<h2>{ev.content}</h2>
<div className="zap-button flex" onClick={() => setZap(true)}>
<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)} />
</div>
<div className="flex f-space">
<div className="flex justify-between">
<div>
<FormattedNumber value={progress} style="percent" />
</div>

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import "./Timeline.css";
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 { unixNow } from "@snort/shared";
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
onClick={async () => {
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 FormattedMessage from "Element/FormattedMessage";
import { FormattedMessage } from "react-intl";
export default function AccountName({ name = "", link = true }) {
const navigate = useNavigate();

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,7 @@ function LiveStreamEvent({ ev }: { ev: NostrEvent }) {
"--img": `url(${imageProxy})`,
} as CSSProperties
}></div>
<div className="flex f-col details">
<div className="flex flex-col details">
<div className="flex g2">
<span className="live">{status}</span>
<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 { logout } from "Login";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import "./BadgeList.css";
import { useState } from "react";
import FormattedMessage from "Element/FormattedMessage";
import { FormattedMessage } from "react-intl";
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 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 useEventPublisher from "Hooks/useEventPublisher";

View File

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

View File

@ -1,7 +1,8 @@
import "./Following.css";
import { FormattedMessage } from "react-intl";
import useLogin from "Hooks/useLogin";
import Icon from "Icons/Icon";
import FormattedMessage from "Element/FormattedMessage";
export function FollowingMark({ pubkey }: { pubkey: string }) {
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 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 MuteButton from "Element/User/MuteButton";
import ProfilePreview from "Element/User/ProfilePreview";
@ -16,7 +16,7 @@ export default function MutedList({ pubkeys }: MutedListProps) {
return (
<div className="main-content p">
<div className="flex f-space">
<div className="flex justify-between">
<div className="bold">
<FormattedMessage {...messages.MuteCount} values={{ n: pubkeys?.length }} />
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { db } from "Db";
import AsyncButton from "Element/AsyncButton";
import FormattedMessage from "Element/FormattedMessage";
import { FormattedMessage } from "react-intl";
import { useRouteError } from "react-router-dom";
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 IrisAccount from "Element/IrisAccount/IrisAccount";
export default function FreeNostrAddressPage() {
return (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,21 @@
import useZapsFeed from "../../Feed/ZapsFeed";
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 { FormattedMessage } from "react-intl";
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 messages from "../messages";
export enum ProfileTabType {
NOTES = 0,
REACTIONS = 1,

View File

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

View File

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

View File

@ -86,7 +86,7 @@ export default function WalletPage() {
<FormattedMessage defaultMessage="Enter wallet password" />
</h3>
<div className="flex w-max">
<div className="f-grow mr10">
<div className="grow mr10">
<input
type="password"
placeholder={formatMessage({
@ -140,7 +140,7 @@ export default function WalletPage() {
</h3>
{history?.map(a => (
<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" })} />
<div>{(a.memo ?? "").length === 0 ? <>&nbsp;</> : a.memo}</div>
</div>

View File

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

View File

@ -80,7 +80,7 @@ export default function ImportFollows() {
<input
type="text"
placeholder={formatMessage(messages.TwitterPlaceholder)}
className="f-grow mr10"
className="grow mr10"
value={twitterUsername}
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 Logo from "Element/Logo";
@ -102,7 +102,7 @@ export default function NewUserFlow() {
<h1>
<FormattedMessage {...messages.SaveKeys} />
</h1>
<div className="flex f-space">
<div className="flex justify-between">
<FormattedMessage defaultMessage="Language" />
<select
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 ProfilePreview from "Element/User/ProfilePreview";
@ -10,7 +10,7 @@ export default function AccountsPage() {
const sub = getActiveSubscriptions(LoginStore.allSubscriptions());
return (
<div className="flex-column g12">
<div className="flex flex-col g12">
<h3>
<FormattedMessage defaultMessage="Logins" />
</h3>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import "./Profile.css";
import { useEffect, useState } from "react";
import FormattedMessage from "Element/FormattedMessage";
import { useNavigate } from "react-router-dom";
import { mapEventToProfile } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
@ -13,6 +12,7 @@ import { UserCache } from "Cache";
import useLogin from "Hooks/useLogin";
import Icon from "Icons/Icon";
import Avatar from "Element/User/Avatar";
import { FormattedMessage } from "react-intl";
export interface ProfileSettingsProps {
avatar?: boolean;
@ -107,8 +107,8 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
function editor() {
return (
<div className="flex-column g24">
<div className="flex-column w-max g8">
<div className="flex flex-col g24">
<div className="flex flex-col w-max g8">
<h4>
<FormattedMessage defaultMessage="Name" />
</h4>
@ -120,7 +120,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
disabled={readonly}
/>
</div>
<div className="flex-column w-max g8">
<div className="flex flex-col w-max g8">
<h4>
<FormattedMessage defaultMessage="About" />
</h4>
@ -130,7 +130,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
value={about}
disabled={readonly}></textarea>
</div>
<div className="flex-column w-max g8">
<div className="flex flex-col w-max g8">
<h4>
<FormattedMessage defaultMessage="Website" />
</h4>
@ -142,11 +142,11 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
disabled={readonly}
/>
</div>
<div className="flex-column w-max g8">
<div className="flex flex-col w-max g8">
<h4>
<FormattedMessage defaultMessage="Nostr Address" />
</h4>
<div className="flex-column g8 w-max">
<div className="flex flex-col g8 w-max">
<input
type="text"
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." />
</small>
<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" />
</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" />
</button>
</div>
</div>
</div>
<div className="flex-column w-max g8">
<div className="flex flex-col w-max g8">
<h4>
<FormattedMessage defaultMessage="Lightning Address" />
</h4>
@ -207,7 +207,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
<Avatar pubkey={id} user={user} image={picture} />
<AsyncButton
type="button"
className="circle flex f-center"
className="circle flex align-centerjustify-between"
onClick={() => setNewAvatar()}
disabled={readonly}>
<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 useRelayState from "Feed/RelayState";
import { useNavigate, useParams } from "react-router-dom";
@ -37,10 +37,10 @@ const RelayInfo = () => {
)}
{stats?.info?.software && (
<div className="flex">
<h4 className="f-grow">
<h4 className="grow">
<FormattedMessage {...messages.Software} />
</h4>
<div className="flex f-col">
<div className="flex flex-col">
{stats.info.software.startsWith("http") ? (
<a href={stats.info.software} target="_blank" rel="noreferrer">
{stats.info.software}
@ -57,7 +57,7 @@ const RelayInfo = () => {
)}
{stats?.info?.contact && (
<div className="flex">
<h4 className="f-grow">
<h4 className="grow">
<FormattedMessage {...messages.Contact} />
</h4>
<a
@ -73,7 +73,7 @@ const RelayInfo = () => {
<h4>
<FormattedMessage {...messages.Supports} />
</h4>
<div className="f-grow">
<div className="grow">
{stats.info.supported_nips.map(a => (
<a target="_blank" rel="noreferrer" href={`https://nips.be/${a}`} className="pill">
NIP-{a.toString().padStart(2, "0")}
@ -85,7 +85,7 @@ const RelayInfo = () => {
<h4>
<FormattedMessage defaultMessage="Active Subscriptions" />
</h4>
<div className="f-grow">
<div className="grow">
{stats?.activeRequests.map(a => (
<span className="pill" key={a}>
{a}
@ -95,14 +95,14 @@ const RelayInfo = () => {
<h4>
<FormattedMessage defaultMessage="Pending Subscriptions" />
</h4>
<div className="f-grow">
<div className="grow">
{stats?.pendingRequests.map(a => (
<span className="pill" key={a}>
{a}
</span>
))}
</div>
<div className="flex mt10 f-end">
<div className="flex mt10 justify-end">
<div
className="btn error"
onClick={() => {

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import "./WalletSettings.css";
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 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" />
</p>
<div className="flex">
<div className="f-grow">
<div className="grow">
<input
type="text"
className="w-max mr10"

View File

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

View File

@ -34,7 +34,7 @@ export default function TransferHandle({ handle }: { handle: ManageHandle }) {
<FormattedMessage defaultMessage="Transfer to Pubkey" />
</h4>
<div className="flex">
<div className="f-grow">
<div className="grow">
<input
type="text"
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 ListHandles from "./ListHandles";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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