updated event feed
This commit is contained in:
parent
b84845eade
commit
c583eb9a6d
@ -22,6 +22,7 @@ import {
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useModal } from "@/app/_providers/modal/provider";
|
import { useModal } from "@/app/_providers/modal/provider";
|
||||||
import { IconType } from "react-icons";
|
import { IconType } from "react-icons";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
const ZapPickerModal = dynamic(() => import("@/components/Modals/ZapPicker"), {
|
const ZapPickerModal = dynamic(() => import("@/components/Modals/ZapPicker"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@ -42,7 +43,6 @@ type NavigationElement = {
|
|||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: IconType;
|
icon: IconType;
|
||||||
current: boolean;
|
|
||||||
active: boolean;
|
active: boolean;
|
||||||
} & (NavigationLink | NavigationButton);
|
} & (NavigationLink | NavigationButton);
|
||||||
const flockstrEvent = {
|
const flockstrEvent = {
|
||||||
@ -61,6 +61,7 @@ const flockstrEvent = {
|
|||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
const navigation: NavigationElement[] = [
|
const navigation: NavigationElement[] = [
|
||||||
{
|
{
|
||||||
@ -69,16 +70,14 @@ export default function Sidebar() {
|
|||||||
label: "Home",
|
label: "Home",
|
||||||
icon: RiHome6Fill,
|
icon: RiHome6Fill,
|
||||||
type: "link",
|
type: "link",
|
||||||
current: true,
|
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "",
|
href: "/explore",
|
||||||
name: "explore",
|
name: "explore",
|
||||||
label: "Explore",
|
label: "Explore",
|
||||||
icon: RiCompassLine,
|
icon: RiCompassLine,
|
||||||
type: "link",
|
type: "link",
|
||||||
current: false,
|
|
||||||
active: false,
|
active: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -87,7 +86,6 @@ export default function Sidebar() {
|
|||||||
label: "Messages",
|
label: "Messages",
|
||||||
icon: RiQuestionAnswerLine,
|
icon: RiQuestionAnswerLine,
|
||||||
type: "link",
|
type: "link",
|
||||||
current: false,
|
|
||||||
active: false,
|
active: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -96,7 +94,6 @@ export default function Sidebar() {
|
|||||||
label: "Zap Flockstr",
|
label: "Zap Flockstr",
|
||||||
icon: HiOutlineLightningBolt,
|
icon: HiOutlineLightningBolt,
|
||||||
type: "button",
|
type: "button",
|
||||||
current: false,
|
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -114,7 +111,7 @@ export default function Sidebar() {
|
|||||||
href={item.href}
|
href={item.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"center group relative min-h-[48px] min-w-[48px] rounded-lg hover:bg-muted xl:justify-start xl:gap-x-4 xl:p-2.5",
|
"center group relative min-h-[48px] min-w-[48px] rounded-lg hover:bg-muted xl:justify-start xl:gap-x-4 xl:p-2.5",
|
||||||
item.current
|
pathname === item.href
|
||||||
? "text-foreground"
|
? "text-foreground"
|
||||||
: "text-muted-foreground hover:text-foreground",
|
: "text-muted-foreground hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
@ -136,7 +133,7 @@ export default function Sidebar() {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"center group relative min-h-[48px] min-w-[48px] rounded-lg hover:bg-muted xl:justify-start xl:gap-x-4 xl:p-2.5",
|
"center group relative min-h-[48px] min-w-[48px] rounded-lg hover:bg-muted xl:justify-start xl:gap-x-4 xl:p-2.5",
|
||||||
item.current
|
false
|
||||||
? "text-foreground"
|
? "text-foreground"
|
||||||
: "text-muted-foreground hover:text-foreground",
|
: "text-muted-foreground hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
@ -165,7 +162,7 @@ export default function Sidebar() {
|
|||||||
onClick={item.onClick}
|
onClick={item.onClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
"center group relative min-h-[48px] min-w-[48px] rounded-lg hover:bg-muted xl:justify-start xl:gap-x-4 xl:p-2.5",
|
"center group relative min-h-[48px] min-w-[48px] rounded-lg hover:bg-muted xl:justify-start xl:gap-x-4 xl:p-2.5",
|
||||||
item.current
|
false
|
||||||
? "text-foreground"
|
? "text-foreground"
|
||||||
: "text-muted-foreground hover:text-foreground",
|
: "text-muted-foreground hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
@ -187,7 +184,7 @@ export default function Sidebar() {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"center group relative min-h-[48px] min-w-[48px] rounded-lg hover:bg-muted xl:justify-start xl:gap-x-4 xl:p-2.5",
|
"center group relative min-h-[48px] min-w-[48px] rounded-lg hover:bg-muted xl:justify-start xl:gap-x-4 xl:p-2.5",
|
||||||
item.current
|
false
|
||||||
? "text-foreground"
|
? "text-foreground"
|
||||||
: "text-muted-foreground hover:text-foreground",
|
: "text-muted-foreground hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
|
@ -20,7 +20,10 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
|
|||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="relative flex flex-1 shrink-0 grow justify-center sm:w-[calc(100vw_-_var(--sidebar-closed-width))] xl:w-[calc(100vw_-_var(--sidebar-open-width))]">
|
<div className="relative flex flex-1 shrink-0 grow justify-center sm:w-[calc(100vw_-_var(--sidebar-closed-width))] xl:w-[calc(100vw_-_var(--sidebar-open-width))]">
|
||||||
<div className="flex-1 overflow-x-hidden pb-5">{children}</div>
|
<div className="w-[100vw] flex-1 pb-5 sm:w-[calc(100vw_-_var(--sidebar-closed-width))] xl:w-[calc(100vw_-_var(--sidebar-open-width))]">
|
||||||
|
{/* <div className="flex-1 overflow-y-auto overflow-x-hidden pb-5"> */}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Mobile Banner */}
|
{/* Mobile Banner */}
|
||||||
<MobileBanner />
|
<MobileBanner />
|
||||||
|
99
app/(app)/explore/_components/CalendarSection.tsx
Normal file
99
app/(app)/explore/_components/CalendarSection.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
"use client";
|
||||||
|
import { useRef, useEffect, useState } from "react";
|
||||||
|
import { CardLoading } from "@/components/Cards/CalendarEvent";
|
||||||
|
import { formatDate, fromUnix, relativeTime } from "@/lib/utils/dates";
|
||||||
|
|
||||||
|
import useCurrentUser from "@/lib/hooks/useCurrentUser";
|
||||||
|
import { DUMMY_1 } from "@/constants";
|
||||||
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import { getTagValues } from "@/lib/nostr/utils";
|
||||||
|
import LargeFeedCard from "@/components/Cards/CalendarEvent/LargeFeedCard";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
type CalendarSectionProps = {
|
||||||
|
events: NDKEvent[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CalendarSection({ events }: CalendarSectionProps) {
|
||||||
|
const { currentUser } = useCurrentUser();
|
||||||
|
const firstEvent = events.at(0);
|
||||||
|
if (!firstEvent) return null;
|
||||||
|
const startDateUnix = getTagValues("start", firstEvent?.tags);
|
||||||
|
if (!startDateUnix) return null;
|
||||||
|
const startDate = fromUnix(parseInt(startDateUnix));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex w-full items-start gap-x-3 @container">
|
||||||
|
{/* Date Indicator */}
|
||||||
|
<div className="sticky top-[calc(var(--header-height)_+_28px)] hidden w-[230px] shrink-0 md:block">
|
||||||
|
<CalendarIcon date={startDate} />
|
||||||
|
</div>
|
||||||
|
{/* Date Indicator Mobile */}
|
||||||
|
<div className="absolute inset-y-0 right-0 z-50 md:hidden">
|
||||||
|
<div className="sticky top-[calc(var(--header-height)_+_14px)] shrink-0">
|
||||||
|
<CalendarIconOpacity date={startDate} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Events */}
|
||||||
|
<div className="flex-1 space-y-4 max-md:pt-[60px]">
|
||||||
|
{events.map((e) => (
|
||||||
|
<LargeFeedCard key={e.id} event={e} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function CalendarIcon({ date }: { date: Date }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex overflow-hidden rounded-md border bg-background shadow",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="w-fit shrink-0 bg-primary p-1.5 px-3 text-primary-foreground @xl:p-2 @xl:px-3.5">
|
||||||
|
{/* <span className="text-2xl font-bold">24</span> */}
|
||||||
|
<span className="font-semibold">{formatDate(date, "ddd")}</span>
|
||||||
|
</div>
|
||||||
|
<div className="center flex whitespace-nowrap px-3 text-sm font-medium text-muted-foreground @lg:text-base @xl:px-3.5">
|
||||||
|
<div className="">
|
||||||
|
{formatDate(date, "MMMM Do")}
|
||||||
|
<span className="-mt-2 block text-[10px] font-normal">
|
||||||
|
{relativeTime(date)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function CalendarIconOpacity({ date }: { date: Date }) {
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [top, setTop] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Add a scroll event listener to the window
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (ref.current) {
|
||||||
|
// Get the position of the div relative to the viewport
|
||||||
|
const divRect = ref.current.getBoundingClientRect();
|
||||||
|
// Change the opacity when the div reaches the top of the screen
|
||||||
|
if (divRect.top <= 110) {
|
||||||
|
setTop(true);
|
||||||
|
} else {
|
||||||
|
setTop(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("scroll", handleScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Remove the scroll event listener when the component unmounts
|
||||||
|
window.removeEventListener("scroll", handleScroll);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={cn(top && "opacity-50")}>
|
||||||
|
<CalendarIcon date={date} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
58
app/(app)/explore/page.tsx
Normal file
58
app/(app)/explore/page.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"use client";
|
||||||
|
import { NDKEvent, type NDKKind } from "@nostr-dev-kit/ndk";
|
||||||
|
import CalendarSection from "./_components/CalendarSection";
|
||||||
|
import useEvents from "@/lib/hooks/useEvents";
|
||||||
|
import { getTagValues } from "@/lib/nostr/utils";
|
||||||
|
import { fromUnix, daysOffset } from "@/lib/utils/dates";
|
||||||
|
export default function Page() {
|
||||||
|
const { events } = useEvents({
|
||||||
|
filter: {
|
||||||
|
kinds: [31923 as NDKKind],
|
||||||
|
limit: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const eventsByDay = groupEventsByDay(events);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex-col px-5 pt-5 sm:pt-7">
|
||||||
|
<div className="mx-auto max-w-[900px] space-y-4">
|
||||||
|
{eventsByDay.map((e) => (
|
||||||
|
<CalendarSection events={e} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupEventsByDay(events: NDKEvent[]) {
|
||||||
|
const eventDays: Record<string, NDKEvent[]> = {};
|
||||||
|
for (const event of events) {
|
||||||
|
const eventStartTime = getTagValues("start", event.tags);
|
||||||
|
if (!eventStartTime) continue;
|
||||||
|
const startDate = fromUnix(parseInt(eventStartTime));
|
||||||
|
const daysAway = daysOffset(startDate);
|
||||||
|
if (daysAway < 1) continue;
|
||||||
|
if (eventDays[`${daysAway}`]) {
|
||||||
|
eventDays[`${daysAway}`]!.push(event);
|
||||||
|
} else {
|
||||||
|
eventDays[`${daysAway}`] = [event];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const groupedArray = Object.entries(eventDays)
|
||||||
|
.sort(([aKey], [bKey, test]) => {
|
||||||
|
console.log("test", test);
|
||||||
|
const aDay = parseInt(aKey);
|
||||||
|
|
||||||
|
const bDay = parseInt(bKey);
|
||||||
|
if (aDay > bDay) {
|
||||||
|
return 1;
|
||||||
|
} else if (aDay < bDay) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
})
|
||||||
|
.map(([_, events]) => events);
|
||||||
|
console.log("object", eventDays);
|
||||||
|
console.log("returing", groupedArray);
|
||||||
|
return groupedArray;
|
||||||
|
}
|
89
components/Cards/CalendarEvent/LargeFeedCard.tsx
Normal file
89
components/Cards/CalendarEvent/LargeFeedCard.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { formatDate, fromUnix } from "@/lib/utils/dates";
|
||||||
|
import { BANNER } from "@/constants";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import Image from "next/image";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import SmallProfileLine from "@/components/ProfileContainers/SmallProfileLine";
|
||||||
|
import AvatarStack from "@/components/ProfileContainers/AvatarStack";
|
||||||
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import { getTagValues, getTagAllValues } from "@/lib/nostr/utils";
|
||||||
|
import { HiOutlineMapPin, HiOutlineUserCircle } from "react-icons/hi2";
|
||||||
|
|
||||||
|
type LargeFeedCardProps = {
|
||||||
|
event: NDKEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LargeFeedCard({ event }: LargeFeedCardProps) {
|
||||||
|
const { tags, pubkey, content } = event;
|
||||||
|
const image = getTagValues("image", tags);
|
||||||
|
const users = getTagAllValues("p", tags);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="relative flex justify-between gap-x-4 rounded-[1rem] bg-muted p-[0.5rem]">
|
||||||
|
<CardHeader className="w-3/5 justify-between p-0 pr-5">
|
||||||
|
<div className="">
|
||||||
|
<div className="flex">
|
||||||
|
<SmallProfileLine pubkey={pubkey} />
|
||||||
|
</div>
|
||||||
|
<div className="pl-3">
|
||||||
|
<CardTitle className="mt-3 line-clamp-2 text-xl leading-6">
|
||||||
|
{getTagValues("name", tags)}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="mt-2 line-clamp-4 text-[13px] leading-5">
|
||||||
|
{content}
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-x-4 pl-2">
|
||||||
|
{!!users.length && (
|
||||||
|
<div className="flex shrink-0 items-center gap-x-2">
|
||||||
|
<HiOutlineUserCircle className="h-5 w-5 text-primary" />
|
||||||
|
<AvatarStack
|
||||||
|
pubkeys={users.slice(0, 4)}
|
||||||
|
className="z-0 h-6 w-6 text-[9px]"
|
||||||
|
remaining={users.length - 4 > 2 ? users.length - 4 : 0}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center gap-x-2">
|
||||||
|
<HiOutlineMapPin className="h-5 w-5 shrink-0 text-primary" />
|
||||||
|
<p className="line-clamp-1 text-xs text-muted-foreground">
|
||||||
|
124 Main street, first ave, NY NY, 118034. Across the street from
|
||||||
|
too long
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full flex-col justify-end self-start pl-2 pt-2">
|
||||||
|
<div className="flex w-3/4 items-center justify-stretch gap-3">
|
||||||
|
<Button className="flex-1">RSVP</Button>
|
||||||
|
<Button variant={"outline"} className="flex-1">
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<div className="absolute inset-y-[0.5rem] right-[0.5rem] w-2/5">
|
||||||
|
{image ? (
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
unoptimized
|
||||||
|
alt="Image"
|
||||||
|
fill
|
||||||
|
className={cn(
|
||||||
|
"h-full max-h-[150px] rounded-[0.5rem] object-cover object-center max-sm:max-h-[100px]",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className=""></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
@ -129,7 +129,7 @@ export default function CalendarEventCard({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export function CardLoading({ className }: { className: string }) {
|
export function CardLoading({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
57
components/ProfileContainers/AvatarStack.tsx
Normal file
57
components/ProfileContainers/AvatarStack.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
import { cn, formatCount, getTwoLetters } from "@/lib/utils";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import useProfile from "@/lib/hooks/useProfile";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
|
|
||||||
|
type AvatarStackProps = {
|
||||||
|
pubkeys: string[];
|
||||||
|
remaining?: number;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zIndexes = ["z-50", "z-40", "z-30", "z-20", "z-10", "z-0"];
|
||||||
|
|
||||||
|
export default function AvatarStack({
|
||||||
|
pubkeys,
|
||||||
|
className,
|
||||||
|
remaining,
|
||||||
|
}: AvatarStackProps) {
|
||||||
|
return (
|
||||||
|
<div className="isolate flex -space-x-2 overflow-hidden py-[2px]">
|
||||||
|
{pubkeys.map((p, idx) => (
|
||||||
|
<User key={p} pubkey={p} className={cn(zIndexes[idx], className)} />
|
||||||
|
))}
|
||||||
|
{!!remaining && (
|
||||||
|
<Avatar
|
||||||
|
className={cn(
|
||||||
|
"relative inline-block h-8 w-8 rounded-full text-xs ring-2 ring-background",
|
||||||
|
className,
|
||||||
|
"z-0",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AvatarFallback className="bg-muted font-semibold text-primary">{`+${formatCount(
|
||||||
|
remaining,
|
||||||
|
)}`}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function User({ pubkey, className }: { pubkey: string; className: string }) {
|
||||||
|
const { profile } = useProfile(pubkey);
|
||||||
|
const npub = nip19.npubEncode(pubkey);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
className={cn(
|
||||||
|
"relative inline-block h-8 w-8 rounded-full ring-2 ring-background",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AvatarImage src={profile?.image} />
|
||||||
|
<AvatarFallback>{getTwoLetters({ npub, profile })}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
);
|
||||||
|
}
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { RiCloseFill } from "react-icons/ri";
|
import { RiCloseFill } from "react-icons/ri";
|
||||||
import { Avatar, AvatarImage, AvatarFallback } from "@radix-ui/react-avatar";
|
import { Avatar, AvatarImage, AvatarFallback } from "@radix-ui/react-avatar";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { formatDate } from "@/lib/utils/dates";
|
import { formatDate, fromUnix } from "@/lib/utils/dates";
|
||||||
import Actions from "./Actions";
|
import Actions from "./Actions";
|
||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { getTagAllValues, getTagValues } from "@/lib/nostr/utils";
|
import { getTagAllValues, getTagValues } from "@/lib/nostr/utils";
|
||||||
@ -67,7 +67,7 @@ export default function ArticlePage({ event }: ArticleProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="h-[20px] w-full"></div>
|
<div className="h-[20px] w-full"></div>
|
||||||
<div className="vmax-h-[calc(100vh_-_100px)] overflow-y-auto">
|
<div className="vmax-h-[calc(100vh_-_100px)] overflow-y-auto">
|
||||||
<article className="prose dark:prose-invert prose-zinc relative mx-auto max-w-3xl pt-7">
|
<article className="prose prose-zinc relative mx-auto max-w-3xl pt-7 dark:prose-invert">
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="flex items-center justify-between gap-1 lg:mb-2">
|
<div className="flex items-center justify-between gap-1 lg:mb-2">
|
||||||
{tags.map((t) => (
|
{tags.map((t) => (
|
||||||
@ -79,7 +79,7 @@ export default function ArticlePage({ event }: ArticleProps) {
|
|||||||
<div className="center text-xs text-muted-foreground/50">
|
<div className="center text-xs text-muted-foreground/50">
|
||||||
{!!createdAt && (
|
{!!createdAt && (
|
||||||
<span className="mr-2.5">
|
<span className="mr-2.5">
|
||||||
{formatDate(new Date(createdAt * 1000), "MMMM Do, YYYY")}
|
{formatDate(fromUnix(createdAt), "MMMM Do, YYYY")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="h-3 w-[1px] rounded-full bg-muted-foreground/50"></span>
|
<span className="h-3 w-[1px] rounded-full bg-muted-foreground/50"></span>
|
||||||
|
@ -60,21 +60,33 @@ export function relativeTime(timestamp: Date) {
|
|||||||
dayjs.updateLocale("en", {
|
dayjs.updateLocale("en", {
|
||||||
relativeTime: {
|
relativeTime: {
|
||||||
future: "in %s",
|
future: "in %s",
|
||||||
past: "%s ago",
|
past: "now",
|
||||||
s: "%s seconds",
|
s: "now",
|
||||||
m: "1 min",
|
m: "a minute",
|
||||||
mm: "%d mins",
|
mm: "%d minutes",
|
||||||
h: "1 hour",
|
h: "an hour",
|
||||||
hh: "%d hours",
|
hh: "%d hours",
|
||||||
d: "1 day",
|
d: "a day",
|
||||||
dd: "%d days",
|
dd: "%d days",
|
||||||
y: "1 year",
|
M: "a month",
|
||||||
|
MM: "%d months",
|
||||||
|
y: "a year",
|
||||||
yy: "%d years",
|
yy: "%d years",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
dayjs.extend(relative, config);
|
dayjs.extend(relative, config);
|
||||||
return dayjs(timestamp).fromNow();
|
return dayjs(timestamp).fromNow();
|
||||||
}
|
}
|
||||||
|
export function daysOffset(targetDate: Date) {
|
||||||
|
const currentDate = new Date();
|
||||||
|
|
||||||
|
// Calculate the time difference in milliseconds
|
||||||
|
const timeDifference = targetDate.getTime() - currentDate.getTime();
|
||||||
|
// Convert the time difference to days
|
||||||
|
const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
return daysDifference;
|
||||||
|
}
|
||||||
export function formatDate(timestamp: Date, format?: string) {
|
export function formatDate(timestamp: Date, format?: string) {
|
||||||
dayjs.extend(advancedFormat);
|
dayjs.extend(advancedFormat);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
@ -92,6 +104,9 @@ export function convertToTimezoneDate(inputDate: Date, _timezone: string) {
|
|||||||
return dayjs(inputDate).tz(_timezone).toDate();
|
return dayjs(inputDate).tz(_timezone).toDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fromUnix(unix: number) {
|
||||||
|
return new Date(unix * 1000);
|
||||||
|
}
|
||||||
export function addMinutesToDate(inputDate: Date, minutesToAdd: number) {
|
export function addMinutesToDate(inputDate: Date, minutesToAdd: number) {
|
||||||
if (!(inputDate instanceof Date)) {
|
if (!(inputDate instanceof Date)) {
|
||||||
throw new Error("Invalid date input");
|
throw new Error("Invalid date input");
|
||||||
@ -112,21 +127,6 @@ export function addMinutesToDate(inputDate: Date, minutesToAdd: number) {
|
|||||||
export function toUnix(inputDate: Date) {
|
export function toUnix(inputDate: Date) {
|
||||||
return dayjs(inputDate).unix();
|
return dayjs(inputDate).unix();
|
||||||
}
|
}
|
||||||
function timezoneDiff(ianatz: string) {
|
|
||||||
const date = new Date();
|
|
||||||
// suppose the date is 12:00 UTC
|
|
||||||
var invdate = new Date(
|
|
||||||
date.toLocaleString("en-US", {
|
|
||||||
timeZone: ianatz,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// then invdate will be 07:00 in Toronto
|
|
||||||
// and the diff is 5 hours
|
|
||||||
var diff = date.getTime() - invdate.getTime();
|
|
||||||
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertToTimezone(inputDate: Date, targetTimezone: string) {
|
export function convertToTimezone(inputDate: Date, targetTimezone: string) {
|
||||||
if (!(inputDate instanceof Date)) {
|
if (!(inputDate instanceof Date)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user