feat: modular right bar
This commit is contained in:
@ -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}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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"
|
||||||
|
31
packages/app/src/Components/RightWidgets/base.tsx
Normal file
31
packages/app/src/Components/RightWidgets/base.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
9
packages/app/src/Components/RightWidgets/index.tsx
Normal file
9
packages/app/src/Components/RightWidgets/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export enum RightColumnWidget {
|
||||||
|
TaskList,
|
||||||
|
TrendingNotes,
|
||||||
|
TrendingPeople,
|
||||||
|
TrendingHashtags,
|
||||||
|
TrendingArticls,
|
||||||
|
LiveStreams,
|
||||||
|
InviteFriends,
|
||||||
|
}
|
43
packages/app/src/Components/RightWidgets/invite-friends.tsx
Normal file
43
packages/app/src/Components/RightWidgets/invite-friends.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
51
packages/app/src/Components/RightWidgets/mini-stream.tsx
Normal file
51
packages/app/src/Components/RightWidgets/mini-stream.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
packages/app/src/Hooks/useLiveStreams.ts
Normal file
27
packages/app/src/Hooks/useLiveStreams.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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={[]}
|
||||||
|
@ -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} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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",
|
||||||
|
Reference in New Issue
Block a user