feat: modular right bar

This commit is contained in:
2024-09-18 15:37:59 +01:00
parent b38b6b27ef
commit 290dedb333
14 changed files with 262 additions and 92 deletions

View File

@ -1,6 +1,6 @@
import "./ZapButton.css"; import "./ZapButton.css";
import { HexKey } from "@snort/system"; import { HexKey, NostrLink } from "@snort/system";
import { useUserProfile } from "@snort/system-react"; import { useUserProfile } from "@snort/system-react";
import { useState } from "react"; import { useState } from "react";
@ -17,7 +17,7 @@ const ZapButton = ({
pubkey: HexKey; pubkey: HexKey;
lnurl?: string; lnurl?: string;
children?: React.ReactNode; children?: React.ReactNode;
event?: string; event?: NostrLink;
}) => { }) => {
const profile = useUserProfile(pubkey); const profile = useUserProfile(pubkey);
const [zap, setZap] = useState(false); const [zap, setZap] = useState(false);
@ -37,12 +37,11 @@ const ZapButton = ({
value: service, value: service,
weight: 1, weight: 1,
name: profile?.display_name || profile?.name, name: profile?.display_name || profile?.name,
zap: { pubkey: pubkey }, zap: { pubkey: pubkey, event },
} as ZapTarget, } as ZapTarget,
]} ]}
show={zap} show={zap}
onClose={() => setZap(false)} onClose={() => setZap(false)}
note={event}
/> />
</> </>
); );

View File

@ -1,50 +1,30 @@
import { unixNow } from "@snort/shared"; import { NostrEvent, NostrLink } from "@snort/system";
import { EventKind, NostrEvent, NostrLink, RequestBuilder } from "@snort/system"; import { useUserProfile } from "@snort/system-react";
import { useRequestBuilder, useUserProfile } from "@snort/system-react"; import classNames from "classnames";
import { CSSProperties, useMemo } from "react"; import { CSSProperties } from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import useImgProxy from "@/Hooks/useImgProxy"; import useImgProxy from "@/Hooks/useImgProxy";
import useLiveStreams from "@/Hooks/useLiveStreams";
import { findTag } from "@/Utils"; import { findTag } from "@/Utils";
import { Hour } from "@/Utils/Const";
import Avatar from "../User/Avatar"; import Avatar from "../User/Avatar";
export function LiveStreams() { export function LiveStreams() {
const sub = useMemo(() => { const streams = useLiveStreams();
const rb = new RequestBuilder("streams");
rb.withFilter()
.kinds([EventKind.LiveEvent])
.since(unixNow() - Hour);
rb.withFilter()
.kinds([EventKind.LiveEvent])
.since(unixNow() - Hour);
return rb;
}, []);
const streams = useRequestBuilder(sub);
if (streams.length === 0) return null; if (streams.length === 0) return null;
return ( return (
<div className="flex mx-2 gap-4 overflow-x-auto sm-hide-scrollbar"> <div className="flex mx-2 gap-4 overflow-x-auto sm-hide-scrollbar">
{streams {streams.map(v => (
.filter(a => { <LiveStreamEvent ev={v} key={`${v.kind}:${v.pubkey}:${findTag(v, "d")}`} className="h-[80px]" />
return findTag(a, "status") === "live"; ))}
})
.sort((a, b) => {
const sA = Number(findTag(a, "starts"));
const sB = Number(findTag(b, "starts"));
return sA > sB ? -1 : 1;
})
.map(v => (
<LiveStreamEvent ev={v} key={`${v.kind}:${v.pubkey}:${findTag(v, "d")}`} />
))}
</div> </div>
); );
} }
function LiveStreamEvent({ ev }: { ev: NostrEvent }) { export function LiveStreamEvent({ ev, className }: { ev: NostrEvent; className?: string }) {
const { proxy } = useImgProxy(); const { proxy } = useImgProxy();
const title = findTag(ev, "title"); const title = findTag(ev, "title");
const image = findTag(ev, "image"); const image = findTag(ev, "image");
@ -57,7 +37,7 @@ function LiveStreamEvent({ ev }: { ev: NostrEvent }) {
const imageProxy = proxy(image ?? ""); const imageProxy = proxy(image ?? "");
return ( return (
<Link className="flex gap-2 h-[80px]" to={`https://zap.stream/${link}`} target="_blank"> <Link className={classNames("flex gap-2", className)} to={`https://zap.stream/${link}`} target="_blank">
<div className="relative aspect-video"> <div className="relative aspect-video">
<div <div
className="absolute h-full w-full bg-center bg-cover bg-gray-ultradark rounded-lg" className="absolute h-full w-full bg-center bg-cover bg-gray-ultradark rounded-lg"

View File

@ -0,0 +1,31 @@
import { ReactNode } from "react";
import Icon from "../Icons/Icon";
export interface BaseWidgetProps {
title?: ReactNode;
icon?: string;
iconClassName?: string;
children?: ReactNode;
contextMenu?: ReactNode;
}
export function BaseWidget({ children, title, icon, iconClassName, contextMenu }: BaseWidgetProps) {
return (
<div className="br p bg-gray-ultradark">
{title && (
<div className="flex justify-between items-center">
<div className="flex gap-2 items-center text-xl text-white font-semibold mb-1">
{icon && (
<div className="p-2 bg-gray-dark rounded-full">
<Icon name={icon} className={iconClassName} />
</div>
)}
<div>{title}</div>
</div>
{contextMenu}
</div>
)}
{children}
</div>
);
}

View File

@ -0,0 +1,9 @@
export enum RightColumnWidget {
TaskList,
TrendingNotes,
TrendingPeople,
TrendingHashtags,
TrendingArticls,
LiveStreams,
InviteFriends,
}

View File

@ -0,0 +1,43 @@
import { useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";
import SnortApi, { RefCodeResponse } from "@/External/SnortApi";
import { useCopy } from "@/Hooks/useCopy";
import useEventPublisher from "@/Hooks/useEventPublisher";
import AsyncButton from "../Button/AsyncButton";
import Icon from "../Icons/Icon";
import { BaseWidget } from "./base";
export default function InviteFriendsWidget() {
const [refCode, setRefCode] = useState<RefCodeResponse>();
const { publisher } = useEventPublisher();
const api = new SnortApi(undefined, publisher);
const copy = useCopy();
async function loadRefCode() {
const c = await api.getRefCode();
setRefCode(c);
}
useEffect(() => {
loadRefCode();
}, [publisher]);
return (
<BaseWidget
title={<FormattedMessage defaultMessage="Invite Friends" />}
icon="heart-solid"
iconClassName="text-heart">
<div className="flex flex-col gap-2">
<FormattedMessage defaultMessage="Share a personalized invitation with friends!" />
<div>
<AsyncButton onClick={() => copy.copy(`https://${window.location.host}?ref=${refCode?.code}`)}>
<Icon name="copy" />
<FormattedMessage defaultMessage="Copy link" />
</AsyncButton>
</div>
</div>
</BaseWidget>
);
}

View File

@ -0,0 +1,51 @@
import { NostrLink } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import useLiveStreams from "@/Hooks/useLiveStreams";
import { findTag, getDisplayName } from "@/Utils";
import IconButton from "../Button/IconButton";
import ZapButton from "../Event/ZapButton";
import { ProxyImg } from "../ProxyImg";
import Avatar from "../User/Avatar";
import { BaseWidget } from "./base";
export default function MiniStreamWidget() {
const streams = useLiveStreams();
const ev = streams.at(0);
const host = ev?.tags.find(a => a[0] === "p" && a.at(3) === "host")?.at(1) ?? ev?.pubkey;
const hostProfile = useUserProfile(host);
if (!ev) return;
const link = NostrLink.fromEvent(ev);
const image = findTag(ev, "image");
const title = findTag(ev, "title");
return (
<BaseWidget>
<div className="flex flex-col gap-4">
<div className="rounded-xl relative aspect-video w-full overflow-hidden">
<ProxyImg src={image} className="absolute w-full h-full" />
<div className="absolute flex items-center justify-center w-full h-full">
<IconButton
icon={{
name: "play-square-outline",
}}
onClick={() => window.open(`https://zap.stream/${link.encode()}`, "_blank")}
/>
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex gap-2">
<Avatar pubkey={host ?? ""} user={hostProfile} size={48} />
<div className="flex flex-col">
<div className="text-lg text-white f-ellipsis font-semibold">{title}</div>
<div>{getDisplayName(hostProfile, host!)}</div>
</div>
</div>
<div>{host && <ZapButton pubkey={host} event={link} />}</div>
</div>
</div>
</BaseWidget>
);
}

View File

@ -2,13 +2,25 @@ import { HexKey } from "@snort/system";
import { ReactNode } from "react"; import { ReactNode } from "react";
import PageSpinner from "@/Components/PageSpinner"; import PageSpinner from "@/Components/PageSpinner";
import FollowListBase from "@/Components/User/FollowListBase"; import FollowListBase, { FollowListBaseProps } from "@/Components/User/FollowListBase";
import NostrBandApi from "@/External/NostrBand"; import NostrBandApi from "@/External/NostrBand";
import useCachedFetch from "@/Hooks/useCachedFetch"; import useCachedFetch from "@/Hooks/useCachedFetch";
import { ErrorOrOffline } from "../ErrorOrOffline"; import { ErrorOrOffline } from "../ErrorOrOffline";
export default function TrendingUsers({ title, count = Infinity }: { title?: ReactNode; count?: number }) { export default function TrendingUsers({
title,
count = Infinity,
followAll = true,
actions,
profileActions,
}: {
title?: ReactNode;
count?: number;
followAll?: boolean;
actions?: FollowListBaseProps["actions"];
profileActions?: FollowListBaseProps["profileActions"];
}) {
const api = new NostrBandApi(); const api = new NostrBandApi();
const trendingProfilesUrl = api.trendingProfilesUrl(); const trendingProfilesUrl = api.trendingProfilesUrl();
const storageKey = `nostr-band-${trendingProfilesUrl}`; const storageKey = `nostr-band-${trendingProfilesUrl}`;
@ -27,5 +39,14 @@ export default function TrendingUsers({ title, count = Infinity }: { title?: Rea
return <PageSpinner />; return <PageSpinner />;
} }
return <FollowListBase pubkeys={trendingUsersData.slice(0, count) as HexKey[]} showAbout={true} title={title} />; return (
<FollowListBase
pubkeys={trendingUsersData.slice(0, count) as HexKey[]}
showAbout={true}
title={title}
showFollowAll={followAll}
actions={actions}
profileActions={profileActions}
/>
);
} }

View File

@ -1,8 +1,7 @@
import "./ZapModal.css"; import "./ZapModal.css";
import { LNURLSuccessAction } from "@snort/shared"; import { LNURLSuccessAction } from "@snort/shared";
import { HexKey } from "@snort/system"; import { ReactNode, useEffect, useState } from "react";
import React, { ReactNode, useEffect, useState } from "react";
import CloseButton from "@/Components/Button/CloseButton"; import CloseButton from "@/Components/Button/CloseButton";
import Modal from "@/Components/Modal/Modal"; import Modal from "@/Components/Modal/Modal";
@ -23,7 +22,6 @@ export interface SendSatsProps {
invoice?: string; // shortcut to invoice qr tab invoice?: string; // shortcut to invoice qr tab
title?: ReactNode; title?: ReactNode;
notice?: string; notice?: string;
note?: HexKey;
allocatePool?: boolean; allocatePool?: boolean;
} }

View File

@ -0,0 +1,27 @@
import { unixNow } from "@snort/shared";
import { EventKind, RequestBuilder } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { useMemo } from "react";
import { findTag } from "@/Utils";
import { Hour } from "@/Utils/Const";
export default function useLiveStreams() {
const sub = useMemo(() => {
const rb = new RequestBuilder("streams");
rb.withFilter()
.kinds([EventKind.LiveEvent])
.since(unixNow() - Hour);
return rb;
}, []);
return useRequestBuilder(sub)
.filter(a => {
return findTag(a, "status") === "live";
})
.sort((a, b) => {
const sA = Number(findTag(a, "starts"));
const sB = Number(findTag(b, "starts"));
return sA > sB ? -1 : 1;
});
}

View File

@ -1,9 +1,15 @@
import classNames from "classnames"; import classNames from "classnames";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { RightColumnWidget } from "@/Components/RightWidgets";
import { BaseWidget } from "@/Components/RightWidgets/base";
import InviteFriendsWidget from "@/Components/RightWidgets/invite-friends";
import MiniStreamWidget from "@/Components/RightWidgets/mini-stream";
import SearchBox from "@/Components/SearchBox/SearchBox"; import SearchBox from "@/Components/SearchBox/SearchBox";
import { TaskList } from "@/Components/Tasks/TaskList";
import TrendingHashtags from "@/Components/Trending/TrendingHashtags"; import TrendingHashtags from "@/Components/Trending/TrendingHashtags";
import TrendingNotes from "@/Components/Trending/TrendingPosts"; import TrendingNotes from "@/Components/Trending/TrendingPosts";
import TrendingUsers from "@/Components/Trending/TrendingUsers";
import useLogin from "@/Hooks/useLogin"; import useLogin from "@/Hooks/useLogin";
export default function RightColumn() { export default function RightColumn() {
@ -11,16 +17,44 @@ export default function RightColumn() {
const hideRightColumnPaths = ["/login", "/new", "/messages"]; const hideRightColumnPaths = ["/login", "/new", "/messages"];
const show = !hideRightColumnPaths.some(path => globalThis.location.pathname.startsWith(path)); const show = !hideRightColumnPaths.some(path => globalThis.location.pathname.startsWith(path));
const getTitleMessage = () => { const widgets = pubkey
return pubkey ? ( ? [
<FormattedMessage defaultMessage="Trending notes" /> RightColumnWidget.TaskList,
) : ( RightColumnWidget.InviteFriends,
<FormattedMessage defaultMessage="Trending hashtags" /> //RightColumnWidget.LiveStreams,
); RightColumnWidget.TrendingNotes,
}; RightColumnWidget.TrendingPeople,
RightColumnWidget.TrendingHashtags,
]
: [RightColumnWidget.TrendingPeople, RightColumnWidget.TrendingHashtags];
const getContent = () => { const getWidget = (t: RightColumnWidget) => {
return pubkey ? <TrendingNotes small={true} count={100} /> : <TrendingHashtags short={true} />; switch (t) {
case RightColumnWidget.TaskList:
return <TaskList />;
case RightColumnWidget.TrendingNotes:
return (
<BaseWidget title={<FormattedMessage defaultMessage="Trending Notes" />}>
<TrendingNotes small={true} count={6} />
</BaseWidget>
);
case RightColumnWidget.TrendingPeople:
return (
<BaseWidget title={<FormattedMessage defaultMessage="Trending People" />}>
<TrendingUsers count={6} followAll={false} profileActions={pubkey ? () => undefined : () => <></>} />
</BaseWidget>
);
case RightColumnWidget.TrendingHashtags:
return (
<BaseWidget title={<FormattedMessage defaultMessage="Popular Hashtags" />}>
<TrendingHashtags short={true} count={6} />
</BaseWidget>
);
case RightColumnWidget.InviteFriends:
return <InviteFriendsWidget />;
case RightColumnWidget.LiveStreams:
return <MiniStreamWidget />;
}
}; };
return ( return (
@ -34,8 +68,7 @@ export default function RightColumn() {
<div> <div>
<SearchBox /> <SearchBox />
</div> </div>
<div className="font-bold text-xs mt-4 mb-2 uppercase tracking-wide">{getTitleMessage()}</div> <div className="flex flex-col gap-4 overflow-y-auto">{widgets.map(getWidget)}</div>
<div className="overflow-y-auto hide-scrollbar flex-grow rounded-lg">{getContent()}</div>
</div> </div>
); );
} }

View File

@ -1,39 +1,17 @@
import { EventKind, NostrEvent, RequestBuilder, TaggedNostrEvent } from "@snort/system"; import { EventKind, NostrEvent, RequestBuilder, TaggedNostrEvent } from "@snort/system";
import { WorkerRelayInterface } from "@snort/worker-relay"; import { WorkerRelayInterface } from "@snort/worker-relay";
import { memo, useEffect, useMemo, useState } from "react"; import { memo, useEffect, useMemo, useState } from "react";
import { FormattedMessage } from "react-intl"; import { useNavigationType } from "react-router-dom";
import { Link, useNavigationType } from "react-router-dom";
import { Relay } from "@/Cache"; import { Relay } from "@/Cache";
import { DisplayAs, DisplayAsSelector } from "@/Components/Feed/DisplayAsSelector"; import { DisplayAs, DisplayAsSelector } from "@/Components/Feed/DisplayAsSelector";
import { TimelineRenderer } from "@/Components/Feed/TimelineRenderer"; import { TimelineRenderer } from "@/Components/Feed/TimelineRenderer";
import { TaskList } from "@/Components/Tasks/TaskList";
import useTimelineFeed, { TimelineFeedOptions, TimelineSubject } from "@/Feed/TimelineFeed"; import useTimelineFeed, { TimelineFeedOptions, TimelineSubject } from "@/Feed/TimelineFeed";
import useFollowsControls from "@/Hooks/useFollowControls"; import useFollowsControls from "@/Hooks/useFollowControls";
import useHistoryState from "@/Hooks/useHistoryState"; import useHistoryState from "@/Hooks/useHistoryState";
import useLogin from "@/Hooks/useLogin"; import useLogin from "@/Hooks/useLogin";
import messages from "@/Pages/messages";
import { System } from "@/system"; import { System } from "@/system";
const FollowsHint = () => {
const publicKey = useLogin(s => s.publicKey);
const { followList } = useFollowsControls();
if (followList.length === 0 && publicKey) {
return (
<FormattedMessage
{...messages.NoFollows}
values={{
newUsersPage: (
<Link to={"/discover"}>
<FormattedMessage {...messages.NewUsers} />
</Link>
),
}}
/>
);
}
};
let forYouFeed = { let forYouFeed = {
events: [] as NostrEvent[], events: [] as NostrEvent[],
created_at: 0, created_at: 0,
@ -180,8 +158,6 @@ export const ForYouTab = memo(function ForYouTab() {
return ( return (
<> <>
<DisplayAsSelector activeSelection={displayAs} onSelect={a => setDisplayAs(a)} /> <DisplayAsSelector activeSelection={displayAs} onSelect={a => setDisplayAs(a)} />
<FollowsHint />
<TaskList />
<TimelineRenderer <TimelineRenderer
frags={frags} frags={frags}
latest={[]} latest={[]}

View File

@ -2,7 +2,6 @@ import { NostrEvent, NostrLink } from "@snort/system";
import { useContext, useMemo } from "react"; import { useContext, useMemo } from "react";
import TimelineFollows from "@/Components/Feed/TimelineFollows"; import TimelineFollows from "@/Components/Feed/TimelineFollows";
import { TaskList } from "@/Components/Tasks/TaskList";
import { DeckContext } from "@/Pages/Deck/DeckLayout"; import { DeckContext } from "@/Pages/Deck/DeckLayout";
export const NotesTab = () => { export const NotesTab = () => {
@ -18,10 +17,5 @@ export const NotesTab = () => {
return undefined; return undefined;
}, [deckContext]); }, [deckContext]);
return ( return <TimelineFollows postsOnly={true} noteOnClick={noteOnClick} />;
<>
<TaskList />
<TimelineFollows postsOnly={true} noteOnClick={noteOnClick} />
</>
);
}; };

View File

@ -306,9 +306,6 @@
"6ewQqw": { "6ewQqw": {
"defaultMessage": "Likes ({n})" "defaultMessage": "Likes ({n})"
}, },
"6k7xfM": {
"defaultMessage": "Trending notes"
},
"6mr8WU": { "6mr8WU": {
"defaultMessage": "Followed by" "defaultMessage": "Followed by"
}, },
@ -506,9 +503,6 @@
"CYkOCI": { "CYkOCI": {
"defaultMessage": "and {count} others you follow" "defaultMessage": "and {count} others you follow"
}, },
"CbM2hK": {
"defaultMessage": "Trending hashtags"
},
"CmZ9ls": { "CmZ9ls": {
"defaultMessage": "{n} Muted" "defaultMessage": "{n} Muted"
}, },
@ -1151,6 +1145,9 @@
"UrKTqQ": { "UrKTqQ": {
"defaultMessage": "You have an active iris.to account" "defaultMessage": "You have an active iris.to account"
}, },
"UsCzPc": {
"defaultMessage": "Share a personalized invitation with friends!"
},
"UxgyeY": { "UxgyeY": {
"defaultMessage": "Your referral code is {code}" "defaultMessage": "Your referral code is {code}"
}, },
@ -1307,6 +1304,9 @@
"abbGKq": { "abbGKq": {
"defaultMessage": "{n} km" "defaultMessage": "{n} km"
}, },
"ak3MTf": {
"defaultMessage": "Invite Friends"
},
"b12Goz": { "b12Goz": {
"defaultMessage": "Mnemonic" "defaultMessage": "Mnemonic"
}, },
@ -1407,6 +1407,9 @@
"dOQCL8": { "dOQCL8": {
"defaultMessage": "Display name" "defaultMessage": "Display name"
}, },
"ddd3JX": {
"defaultMessage": "Popular Hashtags"
},
"deEeEI": { "deEeEI": {
"defaultMessage": "Register" "defaultMessage": "Register"
}, },
@ -1701,6 +1704,9 @@
"lTbT3s": { "lTbT3s": {
"defaultMessage": "Wallet password" "defaultMessage": "Wallet password"
}, },
"lbr3Lq": {
"defaultMessage": "Copy link"
},
"lfOesV": { "lfOesV": {
"defaultMessage": "Non-Zap" "defaultMessage": "Non-Zap"
}, },

View File

@ -101,7 +101,6 @@
"6WWD34": "Looking for: {noteId}", "6WWD34": "Looking for: {noteId}",
"6bgpn+": "Not all clients support this, you may still receive some zaps as if zap splits was not configured", "6bgpn+": "Not all clients support this, you may still receive some zaps as if zap splits was not configured",
"6ewQqw": "Likes ({n})", "6ewQqw": "Likes ({n})",
"6k7xfM": "Trending notes",
"6mr8WU": "Followed by", "6mr8WU": "Followed by",
"6pdxsi": "Extra metadata fields and tags", "6pdxsi": "Extra metadata fields and tags",
"6uMqL1": "Unpaid", "6uMqL1": "Unpaid",
@ -167,7 +166,6 @@
"CM0k0d": "Prune follow list", "CM0k0d": "Prune follow list",
"CVWeJ6": "Trending People", "CVWeJ6": "Trending People",
"CYkOCI": "and {count} others you follow", "CYkOCI": "and {count} others you follow",
"CbM2hK": "Trending hashtags",
"CmZ9ls": "{n} Muted", "CmZ9ls": "{n} Muted",
"CsCUYo": "{n} sats", "CsCUYo": "{n} sats",
"Cu/K85": "Translated from {lang}", "Cu/K85": "Translated from {lang}",
@ -381,6 +379,7 @@
"Up5U7K": "Block", "Up5U7K": "Block",
"Ups2/p": "Your application is pending", "Ups2/p": "Your application is pending",
"UrKTqQ": "You have an active iris.to account", "UrKTqQ": "You have an active iris.to account",
"UsCzPc": "Share a personalized invitation with friends!",
"UxgyeY": "Your referral code is {code}", "UxgyeY": "Your referral code is {code}",
"V20Og0": "Labeling", "V20Og0": "Labeling",
"VOjC1i": "Pick which upload service you want to upload attachments to", "VOjC1i": "Pick which upload service you want to upload attachments to",
@ -433,6 +432,7 @@
"aSGz4J": "Connect to your own LND node with Lightning Node Connect", "aSGz4J": "Connect to your own LND node with Lightning Node Connect",
"aWpBzj": "Show more", "aWpBzj": "Show more",
"abbGKq": "{n} km", "abbGKq": "{n} km",
"ak3MTf": "Invite Friends",
"b12Goz": "Mnemonic", "b12Goz": "Mnemonic",
"b5vAk0": "Your handle will act like a lightning address and will redirect to your chosen LNURL or Lightning address", "b5vAk0": "Your handle will act like a lightning address and will redirect to your chosen LNURL or Lightning address",
"bF1MYT": "You are a community leader and are earning <b>{percent}</b> of referred users subscriptions!", "bF1MYT": "You are a community leader and are earning <b>{percent}</b> of referred users subscriptions!",
@ -466,6 +466,7 @@
"d7d0/x": "LN Address", "d7d0/x": "LN Address",
"dK2CcV": "The public key is like your username, you can share it with anyone.", "dK2CcV": "The public key is like your username, you can share it with anyone.",
"dOQCL8": "Display name", "dOQCL8": "Display name",
"ddd3JX": "Popular Hashtags",
"deEeEI": "Register", "deEeEI": "Register",
"djLctd": "Amount in sats", "djLctd": "Amount in sats",
"djNL6D": "Read-only", "djNL6D": "Read-only",
@ -564,6 +565,7 @@
"lEnclp": "My events: {n}", "lEnclp": "My events: {n}",
"lPWASz": "Snort nostr address", "lPWASz": "Snort nostr address",
"lTbT3s": "Wallet password", "lTbT3s": "Wallet password",
"lbr3Lq": "Copy link",
"lfOesV": "Non-Zap", "lfOesV": "Non-Zap",
"lgg1KN": "account page", "lgg1KN": "account page",
"ll3xBp": "Image proxy service", "ll3xBp": "Image proxy service",