adding actions on list page

This commit is contained in:
zmeyer44 2023-10-17 15:58:04 -04:00
parent 7cb2080c74
commit ab377a78d4
4 changed files with 291 additions and 65 deletions

View File

@ -0,0 +1,185 @@
"use client";
import { useEffect, useState } from "react";
import Image from "next/image";
import dynamic from "next/dynamic";
import { Button } from "@/components/ui/button";
import useProfile from "@/lib/hooks/useProfile";
import { nip19 } from "nostr-tools";
import useEvents from "@/lib/hooks/useEvents";
import Spinner from "@/components/spinner";
import { getTagValues, getTagsValues } from "@/lib/nostr/utils";
import ProfileInfo from "./ProfileInfo";
import useCurrentUser from "@/lib/hooks/useCurrentUser";
import { useNDK } from "@/app/_providers/ndk";
import { toast } from "sonner";
import {
sendZap,
checkPayment,
updateListUsersFromZaps,
} from "@/lib/actions/zap";
import { useModal } from "@/app/_providers/modal/provider";
const EditListModal = dynamic(() => import("@/components/Modals/EditList"), {
ssr: false,
});
const CreateEventModal = dynamic(() => import("@/components/Modals/NewEvent"), {
ssr: false,
});
export default function Header({ naddr }: { naddr: string }) {
const { currentUser } = useCurrentUser();
const modal = useModal();
const { ndk } = useNDK();
const [sendingZap, setSendingZap] = useState(false);
const [checkingPayment, setCheckingPayment] = useState(false);
const [hasValidPayment, setHasValidPayment] = useState(false);
const [syncingUsers, setSyncingUsers] = useState(false);
const { type, data } = nip19.decode(naddr);
console.log("PASSED", naddr, data);
if (type !== "naddr") {
throw new Error("Invalid list");
}
const { identifier, kind, pubkey } = data;
const { profile } = useProfile(pubkey);
const { events } = useEvents({
filter: {
authors: [pubkey],
kinds: [kind],
["#d"]: [identifier],
limit: 1,
},
});
const event = events[0];
if (!event) {
return (
<div className="center pt-20 text-primary">
<Spinner />
</div>
);
}
const noteIds = getTagsValues("e", event.tags).filter(Boolean);
console.log("notes", event.tags);
const title =
getTagValues("title", event.tags) ??
getTagValues("name", event.tags) ??
"Untitled";
const image =
getTagValues("image", event.tags) ??
getTagValues("picture", event.tags) ??
getTagValues("banner", event.tags) ??
profile?.banner;
const description = getTagValues("description", event.tags);
const rawEvent = event.rawEvent();
const subscriptionsEnabled = !!getTagValues("subscriptions", rawEvent.tags);
const priceInBTC = getTagValues("price", rawEvent.tags);
const isMember =
currentUser &&
getTagsValues("p", rawEvent.tags).includes(currentUser.pubkey);
useEffect(() => {
if (!currentUser || !subscriptionsEnabled) return;
if (!isMember && !checkingPayment && !hasValidPayment) {
void handleCheckPayment();
}
}, [isMember, currentUser]);
async function handleCheckPayment() {
if (!event) return;
setCheckingPayment(true);
console.log("Checking payment");
try {
const result = await checkPayment(
ndk!,
event.tagId(),
currentUser!.hexpubkey,
rawEvent,
);
console.log("Payment result", result);
if (result) {
setHasValidPayment(true);
}
} catch (err) {
console.log("error sending zap", err);
} finally {
setCheckingPayment(false);
}
}
async function handleSyncUsers() {
if (!event) return;
setSyncingUsers(true);
try {
console.log("handleSyncUsers");
await updateListUsersFromZaps(ndk!, event.tagId(), rawEvent);
toast.success("Users Synced!");
} catch (err) {
console.log("error syncing users", err);
} finally {
setSyncingUsers(false);
}
}
return (
<div className="relative overflow-hidden rounded-[1rem] border bg-muted p-[0.5rem] @container">
<div className="overflow-hidden rounded-[0.5rem] p-0">
<div className="relative w-full overflow-hidden bg-gradient-to-b from-primary pb-[50%] @5xl:rounded-[20px] md:pb-[40%]">
{!!image && (
<Image
className="absolute inset-0 h-full w-full object-cover align-middle"
src={image}
width={400}
height={100}
alt="banner"
unoptimized
/>
)}
</div>
</div>
<div className="space-y-1 p-3 @sm:px-3.5 @sm:pb-2 @sm:pt-5">
<div className="flex items-start justify-between gap-x-1.5 @lg:gap-x-2.5">
<div className="space-y-1 @sm:space-y-2">
<h2 className="font-condensed text-2xl font-semibold sm:text-3xl lg:text-4xl">
{title}
</h2>
<div className="flex items-center">
<ProfileInfo pubkey={pubkey} />
</div>
</div>
<div className="flex items-center gap-3">
{!!currentUser && currentUser.pubkey === pubkey && (
<>
<Button onClick={() => modal?.show(<CreateEventModal />)}>
Add Event
</Button>
<Button
variant={"outline"}
loading={syncingUsers}
onClick={() => void handleSyncUsers()}
>
Sync users
</Button>
<Button
variant="ghost"
onClick={() =>
modal?.show(<EditListModal listEvent={rawEvent} />)
}
>
Edit
</Button>
</>
)}
{subscriptionsEnabled && !isMember && <Button>Subscribe</Button>}
</div>
</div>
<div className="pt-1 @md:pt-2">
{!!description && (
<p className="line-clamp-3 text-sm text-muted-foreground md:text-sm">
{description}
</p>
)}
</div>
</div>
</div>
);
}

View File

@ -13,18 +13,8 @@ import Spinner from "@/components/spinner";
import { getTagValues, getTagsValues } from "@/lib/nostr/utils";
import ProfileInfo from "./_components/ProfileInfo";
import Feed from "@/containers/Feed";
const demo = [
{
id: "1",
title: "BTC Radio",
description:
"BTC Radio is the best fuking show ever. you should sub to it. now",
picture:
"https://assets.whop.com/cdn-cgi/image/width=1080/https://assets.whop.com/images/images/51602.original.png?1693358530",
tags: ["music", "crypto", "art"],
},
];
import useCurrentUser from "@/lib/hooks/useCurrentUser";
import Header from "./_components/Header";
export default function ListPage({
params: { naddr },
@ -33,14 +23,11 @@ export default function ListPage({
naddr: string;
};
}) {
const [activeTab, setActiveTab] = useState("feed");
const { type, data } = nip19.decode(naddr);
console.log("PASSED", naddr, data);
if (type !== "naddr") {
throw new Error("Invalid list");
}
const { identifier, kind, pubkey } = data;
const { profile } = useProfile(pubkey);
const { events } = useEvents({
filter: {
authors: [pubkey],
@ -60,56 +47,10 @@ export default function ListPage({
}
const noteIds = getTagsValues("e", event.tags).filter(Boolean);
console.log("notes", event.tags);
const title =
getTagValues("title", event.tags) ??
getTagValues("name", event.tags) ??
"Untitled";
const image =
getTagValues("image", event.tags) ??
getTagValues("picture", event.tags) ??
getTagValues("banner", event.tags) ??
profile?.banner;
const description = getTagValues("description", event.tags);
return (
<div className="relative mx-auto max-w-5xl space-y-4 p-2 sm:p-4">
<div className="relative overflow-hidden rounded-[1rem] border bg-muted p-[0.5rem] @container">
<div className="overflow-hidden rounded-[0.5rem] p-0">
<div className="relative w-full overflow-hidden bg-gradient-to-b from-primary pb-[50%] @5xl:rounded-[20px] md:pb-[40%]">
{!!image && (
<Image
className="absolute inset-0 h-full w-full object-cover align-middle"
src={image}
width={400}
height={100}
alt="banner"
unoptimized
/>
)}
</div>
</div>
<div className="space-y-1 p-3 @sm:px-3.5 @sm:pb-2 @sm:pt-5">
<div className="flex justify-between gap-x-1.5 @lg:gap-x-2.5">
<div className="space-y-1 @sm:space-y-2">
<h2 className="font-condensed text-2xl font-semibold sm:text-3xl lg:text-4xl">
{title}
</h2>
<div className="flex items-center">
<ProfileInfo pubkey={pubkey} />
</div>
</div>
<Button>Subscribe</Button>
</div>
<div className="pt-1 @md:pt-2">
{!!description && (
<p className="line-clamp-3 text-sm text-muted-foreground md:text-sm">
{description}
</p>
)}
</div>
</div>
</div>
<Header naddr={naddr} />
<div className="relative overflow-hidden rounded-[1rem] border bg-muted p-[0.5rem] @container">
<div className="space-y-3 overflow-hidden rounded-[0.5rem] p-0">
<Feed

View File

@ -12,7 +12,7 @@ import { createEvent } from "@/lib/actions/create";
import { getTagValues } from "@/lib/nostr/utils";
import { NDKList } from "@nostr-dev-kit/ndk";
import { saveEphemeralSigner } from "@/lib/actions/ephemeral";
import { useRouter } from "next/navigation";
const CreateListSchema = z.object({
title: z.string(),
image: z.string().optional(),
@ -26,6 +26,7 @@ type CreateListType = z.infer<typeof CreateListSchema>;
export default function CreateList() {
const [isLoading, setIsLoading] = useState(false);
const modal = useModal();
const router = useRouter();
const { currentUser, updateUser } = useCurrentUser();
const { ndk } = useNDK();
@ -80,8 +81,13 @@ export default function CreateList() {
}
// getLists(currentUser!.hexpubkey);
setIsLoading(false);
if (event) {
toast.success("List Created!");
modal?.hide();
router.push(`/list/${event.encode()}`);
} else {
toast.error("An error occured");
}
}
return (
<FormModal

View File

@ -0,0 +1,94 @@
import { useEffect, useState } from "react";
import FormModal from "./FormModal";
import { z } from "zod";
import useEvents from "@/lib/hooks/useEvents";
import { updateList } from "@/lib/actions/create";
import { unixTimeNowInSeconds } from "@/lib/nostr/dates";
import { useModal } from "@/app/_providers/modal/provider";
import { toast } from "sonner";
import { useNDK } from "@/app/_providers/ndk";
import { NostrEvent } from "@nostr-dev-kit/ndk";
import { getTagValues } from "@/lib/nostr/utils";
const EditListSchema = z.object({
title: z.string(),
image: z.string().optional(),
description: z.string().optional(),
});
type EditListType = z.infer<typeof EditListSchema>;
type EditListModalProps = {
listEvent: NostrEvent;
};
export default function EditListModal({ listEvent }: EditListModalProps) {
const modal = useModal();
const [isLoading, setIsLoading] = useState(false);
const [sent, setSent] = useState(false);
const { ndk } = useNDK();
const { events } = useEvents({
filter: {
kinds: [listEvent.kind as number],
authors: [listEvent.pubkey],
since: unixTimeNowInSeconds() - 10,
limit: 1,
},
enabled: sent,
});
useEffect(() => {
if (events.length) {
console.log("Done!");
setIsLoading(false);
toast.success("List Updated!");
modal?.hide();
}
}, [events]);
async function handleSubmit(listData: EditListType) {
setIsLoading(true);
const newTags = Object.entries(listData);
setSent(true);
const result = await updateList(ndk!, listEvent, newTags);
}
const defaultValues: Partial<EditListType> = {
title:
getTagValues("title", listEvent.tags) ??
getTagValues("name", listEvent.tags),
image:
getTagValues("image", listEvent.tags) ??
getTagValues("picture", listEvent.tags),
description:
getTagValues("description", listEvent.tags) ??
getTagValues("summary", listEvent.tags),
};
return (
<FormModal
title="Edit List"
fields={[
{
label: "Title",
type: "input",
slug: "title",
},
{
label: "Image",
type: "input",
slug: "image",
},
{
label: "Description",
type: "text-area",
slug: "description",
},
]}
defaultValues={defaultValues ?? {}}
formSchema={EditListSchema}
onSubmit={handleSubmit}
isSubmitting={isLoading}
cta={{
text: "Save Changes",
}}
/>
);
}