follow button
This commit is contained in:
@ -2,7 +2,8 @@ import "./async-button.css";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Spinner from "element/spinner";
|
import Spinner from "element/spinner";
|
||||||
|
|
||||||
interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface AsyncButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onClick(e: React.MouseEvent): Promise<void> | void;
|
onClick(e: React.MouseEvent): Promise<void> | void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -28,8 +29,15 @@ export default function AsyncButton(props: AsyncButtonProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button" disabled={loading || props.disabled} {...props} onClick={handle}>
|
<button
|
||||||
<span style={{ visibility: loading ? "hidden" : "visible" }}>{props.children}</span>
|
type="button"
|
||||||
|
disabled={loading || props.disabled}
|
||||||
|
{...props}
|
||||||
|
onClick={handle}
|
||||||
|
>
|
||||||
|
<span style={{ visibility: loading ? "hidden" : "visible" }}>
|
||||||
|
{props.children}
|
||||||
|
</span>
|
||||||
{loading && (
|
{loading && (
|
||||||
<span className="spinner-wrapper">
|
<span className="spinner-wrapper">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
@ -1,4 +1,66 @@
|
|||||||
// todo
|
import { EventKind, EventPublisher } from "@snort/system";
|
||||||
export function FollowButton({ pubkey }: { pubkey: string }) {
|
import { useLogin } from "hooks/login";
|
||||||
return <button className="btn btn-primary">Follow</button>;
|
import useFollows from "hooks/follows";
|
||||||
|
import AsyncButton from "element/async-button";
|
||||||
|
import { System } from "index";
|
||||||
|
|
||||||
|
export function LoggedInFollowButton({
|
||||||
|
loggedIn,
|
||||||
|
pubkey,
|
||||||
|
}: {
|
||||||
|
loggedIn: string;
|
||||||
|
pubkey: string;
|
||||||
|
}) {
|
||||||
|
const { contacts, relays } = useFollows(loggedIn, true);
|
||||||
|
const isFollowing = contacts.find((t) => t.at(1) === pubkey);
|
||||||
|
|
||||||
|
async function unfollow() {
|
||||||
|
const pub = await EventPublisher.nip7();
|
||||||
|
if (pub) {
|
||||||
|
const ev = await pub.generic((eb) => {
|
||||||
|
eb.kind(EventKind.ContactList).content(JSON.stringify(relays));
|
||||||
|
for (const c of contacts) {
|
||||||
|
if (c.at(1) !== pubkey) {
|
||||||
|
eb.tag(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eb;
|
||||||
|
});
|
||||||
|
console.debug(ev);
|
||||||
|
System.BroadcastEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function follow() {
|
||||||
|
const pub = await EventPublisher.nip7();
|
||||||
|
if (pub) {
|
||||||
|
const ev = await pub.generic((eb) => {
|
||||||
|
eb.kind(EventKind.ContactList).content(JSON.stringify(relays));
|
||||||
|
for (const tag of contacts) {
|
||||||
|
eb.tag(tag);
|
||||||
|
}
|
||||||
|
eb.tag(["p", pubkey]);
|
||||||
|
return eb;
|
||||||
|
});
|
||||||
|
console.debug(ev);
|
||||||
|
System.BroadcastEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsyncButton
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={isFollowing ? unfollow : follow}
|
||||||
|
>
|
||||||
|
{isFollowing ? "Unfollow" : "Follow"}
|
||||||
|
</AsyncButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FollowButton({ pubkey }: { pubkey: string }) {
|
||||||
|
const login = useLogin();
|
||||||
|
return login?.pubkey ? (
|
||||||
|
<LoggedInFollowButton loggedIn={login.pubkey} pubkey={pubkey} />
|
||||||
|
) : null;
|
||||||
}
|
}
|
||||||
|
28
src/hooks/follows.ts
Normal file
28
src/hooks/follows.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { EventKind, ReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
import { System } from "index";
|
||||||
|
|
||||||
|
export default function useFollows(pubkey: string, leaveOpen = false) {
|
||||||
|
const sub = useMemo(() => {
|
||||||
|
const b = new RequestBuilder(`follows:${pubkey.slice(0, 12)}`);
|
||||||
|
b.withOptions({
|
||||||
|
leaveOpen,
|
||||||
|
})
|
||||||
|
.withFilter()
|
||||||
|
.authors([pubkey])
|
||||||
|
.kinds([EventKind.ContactList]);
|
||||||
|
return b;
|
||||||
|
}, [pubkey, leaveOpen]);
|
||||||
|
|
||||||
|
const { data } = useRequestBuilder<ReplaceableNoteStore>(
|
||||||
|
System,
|
||||||
|
ReplaceableNoteStore,
|
||||||
|
sub
|
||||||
|
);
|
||||||
|
|
||||||
|
const contacts = (data?.tags ?? []).filter((t) => t.at(0) === "p");
|
||||||
|
const relays = JSON.parse(data?.content ?? "{}");
|
||||||
|
|
||||||
|
return { contacts, relays };
|
||||||
|
}
|
@ -13,6 +13,7 @@ import { Profile } from "element/profile";
|
|||||||
import { Icon } from "element/icon";
|
import { Icon } from "element/icon";
|
||||||
import { SendZapsDialog } from "element/send-zap";
|
import { SendZapsDialog } from "element/send-zap";
|
||||||
import { VideoTile } from "element/video-tile";
|
import { VideoTile } from "element/video-tile";
|
||||||
|
import { FollowButton } from "element/follow-button";
|
||||||
import { useProfile } from "hooks/profile";
|
import { useProfile } from "hooks/profile";
|
||||||
import useTopZappers from "hooks/top-zappers";
|
import useTopZappers from "hooks/top-zappers";
|
||||||
import { Text } from "element/text";
|
import { Text } from "element/text";
|
||||||
@ -81,8 +82,6 @@ export function ProfilePage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: follow
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="profile-page">
|
<div className="profile-page">
|
||||||
<div className="profile-container">
|
<div className="profile-container">
|
||||||
@ -125,6 +124,7 @@ export function ProfilePage() {
|
|||||||
targetName={profile?.name || link.id}
|
targetName={profile?.name || link.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<FollowButton pubkey={link.id} />
|
||||||
</div>
|
</div>
|
||||||
<div className="profile-information">
|
<div className="profile-information">
|
||||||
{profile?.name && <h1 className="name">{profile.name}</h1>}
|
{profile?.name && <h1 className="name">{profile.name}</h1>}
|
||||||
|
Reference in New Issue
Block a user