feat: follow sets page

This commit is contained in:
2025-05-06 15:09:43 +01:00
parent 91c912a886
commit d22ce56ebc
13 changed files with 195 additions and 80 deletions

View File

@ -12,6 +12,8 @@ import usePreferences from "@/Hooks/usePreferences";
import { dedupe, findTag, getDisplayName, hexToBech32 } from "@/Utils";
import { useWallet } from "@/Wallet";
import { ProxyImg } from "../ProxyImg";
export default function PubkeyList({ ev, className }: { ev: NostrEvent; className?: string }) {
const wallet = useWallet();
const defaultZapAmount = usePreferences(s => s.defaultZapAmount);
@ -62,29 +64,33 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam
}
}
const picture = findTag(ev, "image");
return (
<FollowListBase
pubkeys={ids}
className={className}
title={findTag(ev, "title") ?? findTag(ev, "d")}
actions={
<>
<AsyncButton className="mr5 secondary" onClick={() => zapAll()}>
<FormattedMessage
defaultMessage="Zap all {n} sats"
id="IVbtTS"
values={{
n: <FormattedNumber value={defaultZapAmount * ids.length} />,
}}
/>
</AsyncButton>
</>
}
profilePreviewProps={{
options: {
about: true,
},
}}
/>
<>
{picture && <ProxyImg src={picture} className="br max-h-44 w-full object-cover mb-4" />}
<FollowListBase
pubkeys={ids}
className={className}
title={findTag(ev, "title") ?? findTag(ev, "d")}
actions={
<>
<AsyncButton className="mr5 secondary" onClick={() => zapAll()}>
<FormattedMessage
defaultMessage="Zap all {n} sats"
id="IVbtTS"
values={{
n: <FormattedNumber value={defaultZapAmount * ids.length} />,
}}
/>
</AsyncButton>
</>
}
profilePreviewProps={{
options: {
about: true,
},
}}
/>
</>
);
}

View File

@ -62,6 +62,7 @@ export default memo(function EventComponent(props: NoteProps) {
case EventKind.ZapstrTrack:
content = <ZapstrEmbed ev={ev} />;
break;
case EventKind.StarterPackSet:
case EventKind.FollowSet:
case EventKind.ContactList:
content = <PubkeyList ev={ev} className={className} />;

View File

@ -61,17 +61,6 @@ export function rootTabItems(base: string, pubKey: string | undefined, tags: Arr
</>
),
},
{
tab: "suggested",
path: `${base}/suggested`,
show: Boolean(pubKey),
element: (
<>
<Icon name="thumbs-up" />
<FormattedMessage defaultMessage="Suggested Follows" />
</>
),
},
{
tab: "trending/hashtags",
path: `${base}/trending/hashtags`,
@ -105,6 +94,17 @@ export function rootTabItems(base: string, pubKey: string | undefined, tags: Arr
</>
),
},
{
tab: "follow-sets",
path: `${base}/follow-sets`,
show: true,
element: (
<>
<Icon name="thumbs-up" />
<FormattedMessage defaultMessage="Follow Sets" />
</>
),
},
] as Array<{
tab: RootTabRoutePath;
path: string;

View File

@ -1,4 +1,4 @@
import { HexKey, NostrPrefix } from "@snort/system";
import { NostrPrefix } from "@snort/system";
import { useState } from "react";
import { FormattedMessage } from "react-intl";
@ -61,7 +61,7 @@ export default function SuggestedProfiles() {
</select>
</div>
<FollowListBase
pubkeys={userList as HexKey[]}
pubkeys={userList}
profilePreviewProps={{
options: {
about: true,

View File

@ -1,16 +1,16 @@
import { HexKey } from "@snort/system";
import classNames from "classnames";
import { ReactNode } from "react";
import { FormattedMessage } from "react-intl";
import ProfilePreview, { ProfilePreviewProps } from "@/Components/User/ProfilePreview";
import useFollowsControls from "@/Hooks/useFollowControls";
import useLogin from "@/Hooks/useLogin";
import useWoT from "@/Hooks/useWoT";
import AsyncButton from "../Button/AsyncButton";
import messages from "../messages";
export interface FollowListBaseProps {
pubkeys: HexKey[];
pubkeys: string[];
title?: ReactNode;
showFollowAll?: boolean;
className?: string;
@ -27,7 +27,8 @@ export default function FollowListBase({
profilePreviewProps,
}: FollowListBaseProps) {
const control = useFollowsControls();
const login = useLogin();
const readonly = useLogin(s => s.readonly);
const wot = useWoT();
async function followAll() {
await control.addFollow(pubkeys);
@ -37,15 +38,17 @@ export default function FollowListBase({
<div className="flex flex-col gap-2">
{(showFollowAll ?? true) && (
<div className="flex items-center">
<div className="grow font-bold">{title}</div>
<div className="grow font-bold text-xl">{title}</div>
{actions}
<AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={login.readonly}>
<FormattedMessage {...messages.FollowAll} />
<AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={readonly}>
<FormattedMessage defaultMessage="Follow All" />
</AsyncButton>
</div>
)}
<div className={className}>
{pubkeys?.slice(0, 20).map(a => <ProfilePreview pubkey={a} key={a} {...profilePreviewProps} />)}
<div className={classNames("flex flex-col gap-2", className)}>
{wot.sortPubkeys(pubkeys).map(a => (
<ProfilePreview pubkey={a} key={a} {...profilePreviewProps} />
))}
</div>
</div>
);

View File

@ -277,6 +277,8 @@ export default function KindName({ kind }: { kind: number }) {
return <FormattedMessage defaultMessage="Community Definition" />;
case 38383:
return <FormattedMessage defaultMessage="Peer-to-peer Order events" />;
case 39089:
return <FormattedMessage defaultMessage="Starter Pack" />;
case 39701:
return <FormattedMessage defaultMessage="Web bookmarks" />;
default: