adding events
This commit is contained in:
parent
f952f4d456
commit
6b13cfee0a
84
app/(app)/app/_sections/UpcomingEvents.tsx
Normal file
84
app/(app)/app/_sections/UpcomingEvents.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
"use client";
|
||||||
|
import {
|
||||||
|
Section,
|
||||||
|
SectionHeader,
|
||||||
|
SectionTitle,
|
||||||
|
SectionContent,
|
||||||
|
} from "@/containers/PageSection";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { RiArrowRightLine } from "react-icons/ri";
|
||||||
|
import CalendarEventCard, {
|
||||||
|
CardLoading,
|
||||||
|
} from "@/components/Cards/CalendarEvent";
|
||||||
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
import Link from "next/link";
|
||||||
|
import useEvents from "@/lib/hooks/useEvents";
|
||||||
|
import { Event } from "nostr-tools";
|
||||||
|
import KindLoading from "@/components/KindCard/loading";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
|
import { getTagValues, getTagsValues } from "@/lib/nostr/utils";
|
||||||
|
import { type NDKKind } from "@nostr-dev-kit/ndk";
|
||||||
|
import { uniqBy } from "ramda";
|
||||||
|
|
||||||
|
export default function UpcomingEventsSection() {
|
||||||
|
const { events } = useEvents({
|
||||||
|
filter: {
|
||||||
|
kinds: [31923 as NDKKind],
|
||||||
|
limit: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("UpcomingEventsSection", events);
|
||||||
|
|
||||||
|
const processedEvents = uniqBy(
|
||||||
|
(e) => getTagValues("name", e.tags),
|
||||||
|
events,
|
||||||
|
).sort((a, b) => {
|
||||||
|
const aImage = getTagValues("image", a.tags);
|
||||||
|
const bImage = getTagValues("image", b.tags);
|
||||||
|
if (aImage && bImage) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (bImage) return 1;
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section className="max-sm:-mx-5">
|
||||||
|
<SectionHeader>
|
||||||
|
<div className="center gap-x-2 max-sm:px-5">
|
||||||
|
<SectionTitle>Upcoming Events</SectionTitle>
|
||||||
|
</div>
|
||||||
|
<Button variant={"ghost"}>
|
||||||
|
View all <RiArrowRightLine className="ml-1 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</SectionHeader>
|
||||||
|
<SectionContent className="relative">
|
||||||
|
<ScrollArea>
|
||||||
|
<div className="flex space-x-2 pb-4 max-sm:px-5">
|
||||||
|
{processedEvents?.length > 3 ? (
|
||||||
|
processedEvents.slice(0, 6).map((e, idx) => {
|
||||||
|
return (
|
||||||
|
<Link key={e.id} href={`/event/${e.encode()}`}>
|
||||||
|
<CalendarEventCard
|
||||||
|
event={e.rawEvent()}
|
||||||
|
className="min-w-[250px] max-w-[350px]"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CardLoading className="min-w-[250px] max-w-[350px]" />
|
||||||
|
<CardLoading className="min-w-[250px] max-w-[350px]" />
|
||||||
|
<CardLoading className="min-w-[250px] max-w-[350px]" />
|
||||||
|
<CardLoading className="min-w-[250px] max-w-[350px]" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ScrollBar orientation="horizontal" />
|
||||||
|
</ScrollArea>
|
||||||
|
</SectionContent>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import ExploreCreators from "./_sections/ExploreCreators";
|
import ExploreCreators from "./_sections/ExploreCreators";
|
||||||
|
import UpcomingEvents from "./_sections/UpcomingEvents";
|
||||||
import LongFormContentSection from "./_sections/LongFormContent";
|
import LongFormContentSection from "./_sections/LongFormContent";
|
||||||
import BecomeACreator from "./_sections/BecomeACreator";
|
import BecomeACreator from "./_sections/BecomeACreator";
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ export default function Page() {
|
|||||||
return (
|
return (
|
||||||
<div className="relative space-y-6 px-5 pt-5 sm:pt-7">
|
<div className="relative space-y-6 px-5 pt-5 sm:pt-7">
|
||||||
<ExploreCreators />
|
<ExploreCreators />
|
||||||
|
<UpcomingEvents />
|
||||||
<LongFormContentSection />
|
<LongFormContentSection />
|
||||||
<BecomeACreator />
|
<BecomeACreator />
|
||||||
<LiveStreamingSection />
|
<LiveStreamingSection />
|
||||||
|
@ -46,14 +46,10 @@ export default function Header({ event }: { event: NDKEvent }) {
|
|||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const [checkingPayment, setCheckingPayment] = useState(false);
|
const [checkingPayment, setCheckingPayment] = useState(false);
|
||||||
const [hasValidPayment, setHasValidPayment] = useState(false);
|
const [hasValidPayment, setHasValidPayment] = useState(false);
|
||||||
const [syncingUsers, setSyncingUsers] = useState(false);
|
|
||||||
const { pubkey, tags } = event;
|
const { pubkey, tags } = event;
|
||||||
const { profile } = useProfile(pubkey);
|
const { profile } = useProfile(pubkey);
|
||||||
console.log("EVENT", tags);
|
|
||||||
|
|
||||||
const noteIds = getTagsValues("e", tags).filter(Boolean);
|
|
||||||
const title = getTagValues("name", tags) ?? "Untitled";
|
const title = getTagValues("name", tags) ?? "Untitled";
|
||||||
console.log("tite", tags);
|
|
||||||
const image =
|
const image =
|
||||||
getTagValues("image", tags) ??
|
getTagValues("image", tags) ??
|
||||||
getTagValues("picture", tags) ??
|
getTagValues("picture", tags) ??
|
||||||
@ -110,19 +106,7 @@ export default function Header({ event }: { event: NDKEvent }) {
|
|||||||
setCheckingPayment(false);
|
setCheckingPayment(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleSyncUsers() {
|
|
||||||
if (!event || !ndk) 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function handleSendZap() {
|
async function handleSendZap() {
|
||||||
try {
|
try {
|
||||||
const result = await sendZap(
|
const result = await sendZap(
|
||||||
@ -163,7 +147,7 @@ export default function Header({ event }: { event: NDKEvent }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 p-3 @sm:px-3.5 @sm:pb-2 @sm:pt-5">
|
<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="flex items-start justify-between gap-x-1.5 @lg:gap-x-2.5">
|
||||||
<div className="space-y-1 @sm:space-y-2">
|
<div className="shrink-0 space-y-1 @sm:space-y-2">
|
||||||
<h2 className="font-condensed text-2xl font-semibold sm:text-3xl lg:text-4xl">
|
<h2 className="font-condensed text-2xl font-semibold sm:text-3xl lg:text-4xl">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
@ -175,14 +159,7 @@ export default function Header({ event }: { event: NDKEvent }) {
|
|||||||
{!!currentUser && currentUser.pubkey === pubkey && (
|
{!!currentUser && currentUser.pubkey === pubkey && (
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => modal?.show(<CreateListEvent />)}>
|
<Button onClick={() => modal?.show(<CreateListEvent />)}>
|
||||||
Add Event
|
Invite Users
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={"outline"}
|
|
||||||
loading={syncingUsers}
|
|
||||||
onClick={() => void handleSyncUsers()}
|
|
||||||
>
|
|
||||||
Sync users
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -233,30 +210,30 @@ export default function Header({ event }: { event: NDKEvent }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex pt-1 @md:pt-2">
|
<div className="flex flex-col gap-x-6 gap-y-3 pt-1 @md:pt-2 @xl:flex-row">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{!!description && (
|
{!!description && (
|
||||||
<p className="line-clamp-3 text-sm text-muted-foreground md:text-sm">
|
<p className="line-clamp-3 text-sm text-muted-foreground @md:text-sm">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 justify-end">
|
<div className="flex flex-1 @xl:justify-end">
|
||||||
<div className="flex flex-col gap-3 pr-3">
|
<div className="flex flex-col gap-3 pr-3">
|
||||||
{!!startDate && (
|
{!!startDate && (
|
||||||
<div className="flex flex-1 items-center gap-3">
|
<div className="flex flex-1 items-center gap-3">
|
||||||
<SmallCalendarIcon date={startDate} />
|
<SmallCalendarIcon date={startDate} />
|
||||||
<div className="">
|
<div className="">
|
||||||
<p className="text-bold text-base">
|
<p className="text-bold text-sm @xl:text-base">
|
||||||
{formatDate(startDate, "dddd, MMMM Do")}
|
{formatDate(startDate, "dddd, MMMM Do")}
|
||||||
</p>
|
</p>
|
||||||
{!!endDate ? (
|
{!!endDate ? (
|
||||||
<p className="text-sm text-muted-foreground">{`${formatDate(
|
<p className="text-xs text-muted-foreground @xl:text-sm">{`${formatDate(
|
||||||
startDate,
|
startDate,
|
||||||
"h:mm a",
|
"h:mm a",
|
||||||
)} to ${formatDate(endDate, "h:mm a")}`}</p>
|
)} to ${formatDate(endDate, "h:mm a")}`}</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-muted-foreground">{`${formatDate(
|
<p className="text-xs text-muted-foreground @xl:text-sm">{`${formatDate(
|
||||||
startDate,
|
startDate,
|
||||||
"h:mm a",
|
"h:mm a",
|
||||||
)}`}</p>
|
)}`}</p>
|
||||||
@ -270,8 +247,10 @@ export default function Header({ event }: { event: NDKEvent }) {
|
|||||||
<div className="">
|
<div className="">
|
||||||
{location.length > 2 ? (
|
{location.length > 2 ? (
|
||||||
<>
|
<>
|
||||||
<p className="text-bold text-base">{location[1]}</p>
|
<p className="text-bold text-sm @xl:text-base">
|
||||||
<p className="text-xs text-muted-foreground">
|
{location[1]}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground @xl:text-sm">
|
||||||
{location[2]}
|
{location[2]}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
|
159
components/Cards/CalendarEvent/index.tsx
Normal file
159
components/Cards/CalendarEvent/index.tsx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
"use client";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { cn, formatNumber } from "@/lib/utils";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { RxClock, RxCalendar } from "react-icons/rx";
|
||||||
|
import { HiOutlineUsers } from "react-icons/hi";
|
||||||
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { formatDate } from "@/lib/utils/dates";
|
||||||
|
import { NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import {
|
||||||
|
getTagAllValues,
|
||||||
|
getTagValues,
|
||||||
|
getTagsValues,
|
||||||
|
} from "@/lib/nostr/utils";
|
||||||
|
import useProfile from "@/lib/hooks/useProfile";
|
||||||
|
import SmallProfileLine from "@/components/ProfileContainers/SmallProfileLine";
|
||||||
|
|
||||||
|
type CalendarEventCardProps = {
|
||||||
|
event: NostrEvent;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CalendarEventCard({
|
||||||
|
className,
|
||||||
|
event,
|
||||||
|
}: CalendarEventCardProps) {
|
||||||
|
const { pubkey, tags } = event;
|
||||||
|
const { profile } = useProfile(pubkey);
|
||||||
|
|
||||||
|
const title = getTagValues("name", tags) || "Untitled";
|
||||||
|
console.log("tite", tags);
|
||||||
|
const image =
|
||||||
|
getTagValues("image", tags) ??
|
||||||
|
getTagValues("picture", tags) ??
|
||||||
|
getTagValues("banner", tags) ??
|
||||||
|
profile?.banner;
|
||||||
|
|
||||||
|
const description = event.content;
|
||||||
|
const startDate = getTagValues("start", tags)
|
||||||
|
? new Date(parseInt(getTagValues("start", tags) as string) * 1000)
|
||||||
|
: null;
|
||||||
|
const endDate = getTagValues("end", tags)
|
||||||
|
? new Date(parseInt(getTagValues("end", tags) as string) * 1000)
|
||||||
|
: null;
|
||||||
|
const getLocation = () => {
|
||||||
|
let temp = getTagAllValues("location", tags);
|
||||||
|
if (temp[0]) {
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
return getTagAllValues("address", tags);
|
||||||
|
};
|
||||||
|
const location = getLocation();
|
||||||
|
|
||||||
|
const users = getTagsValues("p", tags);
|
||||||
|
const hashtags = getTagsValues("t", tags);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"group flex h-full flex-col rounded-[16px] p-2 hover:bg-muted",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="relative overflow-hidden rounded-md">
|
||||||
|
<AspectRatio ratio={16 / 9} className="bg-muted">
|
||||||
|
<Image
|
||||||
|
src={image ?? ""}
|
||||||
|
alt={title}
|
||||||
|
width={250}
|
||||||
|
height={150}
|
||||||
|
unoptimized
|
||||||
|
className={cn(
|
||||||
|
"h-auto w-auto object-cover transition-all group-hover:scale-105",
|
||||||
|
"aspect-video",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</AspectRatio>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 flex-1 space-y-2 text-base">
|
||||||
|
<h3 className="line-clamp-2 font-semibold leading-5">{title}</h3>
|
||||||
|
<div className="flex flex-col items-start gap-y-1">
|
||||||
|
<div className="flex flex-col items-start gap-x-3 gap-y-1">
|
||||||
|
{!!startDate && (
|
||||||
|
<>
|
||||||
|
{startDate.getDay() === endDate?.getDay() ? (
|
||||||
|
<div className="center shrink-0 gap-x-1 text-xs text-muted-foreground">
|
||||||
|
<RxCalendar className="h-4 w-4 text-primary" />
|
||||||
|
<span>{formatDate(startDate, "ddd, MMM D")}</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="center shrink-0 gap-x-1 text-xs text-muted-foreground">
|
||||||
|
<RxCalendar className="h-4 w-4 text-primary" />
|
||||||
|
<span>{formatDate(startDate, "ddd, MMM D")}</span>
|
||||||
|
{!!endDate && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
<span>-</span>{" "}
|
||||||
|
<span>{formatDate(endDate, "MMM D")}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="center shrink-0 gap-x-1 text-xs text-muted-foreground">
|
||||||
|
<RxClock className="h-4 w-4 text-primary" />
|
||||||
|
<span>{formatDate(startDate, "h:mm a")}</span>
|
||||||
|
{!!endDate && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
<span>-</span>{" "}
|
||||||
|
<span>{formatDate(endDate, "h:mm a")}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!!users.length && (
|
||||||
|
<div className="center gap-x-1 text-xs text-muted-foreground">
|
||||||
|
<HiOutlineUsers className="h-4 w-4 text-primary" />
|
||||||
|
<span>{formatNumber(users.length)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 flex">
|
||||||
|
<SmallProfileLine pubkey={pubkey} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function CardLoading({ className }: { className: string }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"group pointer-events-none flex flex-col space-y-3 rounded-[16px] p-2",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden rounded-md">
|
||||||
|
<AspectRatio ratio={16 / 9} className="bg-muted"></AspectRatio>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 space-y-2 text-base">
|
||||||
|
<Skeleton className="mb-2 h-4 w-1/3 bg-muted" />
|
||||||
|
<div className="flex flex-col items-start">
|
||||||
|
<div className="center gap-x-1 text-xs text-muted-foreground">
|
||||||
|
<Skeleton className="h-3 w-3 bg-muted" />
|
||||||
|
<Skeleton className="h-3 w-[50px] bg-muted" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="-mt-1 flex flex-wrap gap-2 overflow-x-hidden">
|
||||||
|
<Skeleton className="h-2 w-[50px] bg-muted" />
|
||||||
|
<Skeleton className="h-2 w-[40px] bg-muted" />
|
||||||
|
<Skeleton className="h-2 w-[30px] bg-muted" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
43
components/ProfileContainers/SmallProfileLine.tsx
Normal file
43
components/ProfileContainers/SmallProfileLine.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||||
|
import useProfile from "@/lib/hooks/useProfile";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
|
import { getTwoLetters, getNameToShow } from "@/lib/utils";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { HiMiniChevronRight, HiCheckBadge } from "react-icons/hi2";
|
||||||
|
|
||||||
|
type ProfileInfoProps = {
|
||||||
|
pubkey: string;
|
||||||
|
};
|
||||||
|
export default function ProfileInfo({ pubkey }: ProfileInfoProps) {
|
||||||
|
const { profile } = useProfile(pubkey);
|
||||||
|
const npub = nip19.npubEncode(pubkey);
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/${npub}`}
|
||||||
|
className="center group gap-x-2 rounded-sm rounded-r-full bg-background/50 pr-1 text-muted-foreground hover:shadow"
|
||||||
|
>
|
||||||
|
<Avatar className="center h-[16px] w-[16px] overflow-hidden rounded-[.25rem] bg-muted">
|
||||||
|
<AvatarImage src={profile?.image} alt={profile?.displayName} />
|
||||||
|
<AvatarFallback className="text-[8px]">
|
||||||
|
{getTwoLetters({ npub, profile })}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="text-[12px]">{getNameToShow({ npub, profile })}</span>
|
||||||
|
{!!profile?.nip05 && <HiCheckBadge className="h-3 w-3 text-primary" />}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoadingProfileInfo() {
|
||||||
|
return (
|
||||||
|
<div className="center group gap-x-1">
|
||||||
|
<Avatar className="center h-[16px] w-[16px] overflow-hidden rounded-[.25rem] bg-muted @sm:h-[18px] @sm:w-[18px]"></Avatar>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Skeleton className="h-2 w-[70px] bg-muted" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user