events
This commit is contained in:
parent
9b0996b18f
commit
f952f4d456
290
app/(app)/event/[naddr]/_components/Header.tsx
Normal file
290
app/(app)/event/[naddr]/_components/Header.tsx
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
"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 { HiOutlineLightningBolt } from "react-icons/hi";
|
||||||
|
import Spinner from "@/components/spinner";
|
||||||
|
import {
|
||||||
|
getTagAllValues,
|
||||||
|
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";
|
||||||
|
import { type NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import { btcToSats, formatNumber } from "@/lib/utils";
|
||||||
|
import { formatDate } from "@/lib/utils/dates";
|
||||||
|
import SmallCalendarIcon from "@/components/EventIcons/DateIcon";
|
||||||
|
import LocationIcon from "@/components/EventIcons/LocationIcon";
|
||||||
|
|
||||||
|
const EditListModal = dynamic(() => import("@/components/Modals/EditList"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
const CreateListEvent = dynamic(
|
||||||
|
() => import("@/components/Modals/ShortTextNoteOnList"),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const ConfirmModal = dynamic(() => import("@/components/Modals/Confirm"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function Header({ event }: { event: NDKEvent }) {
|
||||||
|
const { currentUser } = useCurrentUser();
|
||||||
|
const modal = useModal();
|
||||||
|
const { ndk } = useNDK();
|
||||||
|
const [checkingPayment, setCheckingPayment] = useState(false);
|
||||||
|
const [hasValidPayment, setHasValidPayment] = useState(false);
|
||||||
|
const [syncingUsers, setSyncingUsers] = useState(false);
|
||||||
|
const { pubkey, tags } = event;
|
||||||
|
const { profile } = useProfile(pubkey);
|
||||||
|
console.log("EVENT", tags);
|
||||||
|
|
||||||
|
const noteIds = getTagsValues("e", tags).filter(Boolean);
|
||||||
|
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 rawEvent = event.rawEvent();
|
||||||
|
const subscriptionsEnabled = !!getTagValues("subscriptions", rawEvent.tags);
|
||||||
|
const priceInBTC = parseFloat(getTagValues("price", rawEvent.tags) ?? "0");
|
||||||
|
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 || !currentUser || !ndk) return;
|
||||||
|
setCheckingPayment(true);
|
||||||
|
console.log("Checking payment");
|
||||||
|
try {
|
||||||
|
const result = await checkPayment(
|
||||||
|
ndk,
|
||||||
|
event.tagId(),
|
||||||
|
currentUser.pubkey,
|
||||||
|
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 || !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() {
|
||||||
|
try {
|
||||||
|
const result = await sendZap(
|
||||||
|
ndk!,
|
||||||
|
btcToSats(priceInBTC),
|
||||||
|
rawEvent,
|
||||||
|
`Access payment: ${title}`,
|
||||||
|
);
|
||||||
|
toast.success("Payment Sent!");
|
||||||
|
void handleCheckPayment();
|
||||||
|
} catch (err) {
|
||||||
|
console.log("error sending zap", err);
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!event) {
|
||||||
|
return (
|
||||||
|
<div className="center pt-20 text-primary">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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 flex-wrap items-center gap-3">
|
||||||
|
{!!currentUser && currentUser.pubkey === pubkey && (
|
||||||
|
<>
|
||||||
|
<Button onClick={() => modal?.show(<CreateListEvent />)}>
|
||||||
|
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 &&
|
||||||
|
(hasValidPayment ? (
|
||||||
|
<Button variant={"outline"}>Pending Sync</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
modal?.show(
|
||||||
|
<ConfirmModal
|
||||||
|
title={`Subscribe to ${title}`}
|
||||||
|
onConfirm={handleSendZap}
|
||||||
|
ctaBody={
|
||||||
|
<>
|
||||||
|
<span>Zap to Subscribe</span>
|
||||||
|
<HiOutlineLightningBolt className="h-4 w-4" />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p className="text-muted-forground">
|
||||||
|
{`Pay ${priceInBTC} BTC (${formatNumber(
|
||||||
|
btcToSats(priceInBTC),
|
||||||
|
)} sats) for year long access until ${formatDate(
|
||||||
|
new Date(
|
||||||
|
new Date().setFullYear(
|
||||||
|
new Date().getFullYear() + 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"MMM Do, YYYY",
|
||||||
|
)}`}
|
||||||
|
</p>
|
||||||
|
</ConfirmModal>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
RSVP
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex pt-1 @md:pt-2">
|
||||||
|
<div className="flex-1">
|
||||||
|
{!!description && (
|
||||||
|
<p className="line-clamp-3 text-sm text-muted-foreground md:text-sm">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 justify-end">
|
||||||
|
<div className="flex flex-col gap-3 pr-3">
|
||||||
|
{!!startDate && (
|
||||||
|
<div className="flex flex-1 items-center gap-3">
|
||||||
|
<SmallCalendarIcon date={startDate} />
|
||||||
|
<div className="">
|
||||||
|
<p className="text-bold text-base">
|
||||||
|
{formatDate(startDate, "dddd, MMMM Do")}
|
||||||
|
</p>
|
||||||
|
{!!endDate ? (
|
||||||
|
<p className="text-sm text-muted-foreground">{`${formatDate(
|
||||||
|
startDate,
|
||||||
|
"h:mm a",
|
||||||
|
)} to ${formatDate(endDate, "h:mm a")}`}</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-muted-foreground">{`${formatDate(
|
||||||
|
startDate,
|
||||||
|
"h:mm a",
|
||||||
|
)}`}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!!location && (
|
||||||
|
<div className="flex flex-1 items-center gap-3">
|
||||||
|
<LocationIcon />
|
||||||
|
<div className="">
|
||||||
|
{location.length > 2 ? (
|
||||||
|
<>
|
||||||
|
<p className="text-bold text-base">{location[1]}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{location[2]}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm">{location[0]}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
44
app/(app)/event/[naddr]/_components/ProfileInfo.tsx
Normal file
44
app/(app)/event/[naddr]/_components/ProfileInfo.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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 border bg-background/50 pl-0.5 pr-1 text-muted-foreground hover:shadow"
|
||||||
|
>
|
||||||
|
<Avatar className="center h-[16px] w-[16px] overflow-hidden rounded-[.25rem] bg-muted @sm:h-[18px] @sm:w-[18px]">
|
||||||
|
<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-[14px] ">{getNameToShow({ npub, profile })}</span>
|
||||||
|
{!!profile?.nip05 && <HiCheckBadge className="h-3 w-3 text-primary" />}
|
||||||
|
</div>
|
||||||
|
<HiMiniChevronRight className="h-4 w-4" />
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
61
app/(app)/event/[naddr]/page.tsx
Normal file
61
app/(app)/event/[naddr]/page.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"use client";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
|
import useEvents from "@/lib/hooks/useEvents";
|
||||||
|
import Spinner from "@/components/spinner";
|
||||||
|
import { getTagValues, getTagsValues } from "@/lib/nostr/utils";
|
||||||
|
import Feed from "@/containers/Feed";
|
||||||
|
import Header from "./_components/Header";
|
||||||
|
|
||||||
|
export default function EventPage({
|
||||||
|
params: { naddr },
|
||||||
|
}: {
|
||||||
|
params: {
|
||||||
|
naddr: string;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
const { type, data } = nip19.decode(naddr);
|
||||||
|
if (type !== "naddr") {
|
||||||
|
throw new Error("Invalid list");
|
||||||
|
}
|
||||||
|
const { identifier, kind, pubkey } = data;
|
||||||
|
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);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative mx-auto max-w-5xl space-y-4 p-2 sm:p-4">
|
||||||
|
<Header event={event} />
|
||||||
|
<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
|
||||||
|
filter={{
|
||||||
|
ids: noteIds,
|
||||||
|
}}
|
||||||
|
empty={() => (
|
||||||
|
<div className="text-center text-muted-foreground">
|
||||||
|
<p>No notes yet</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -5,7 +5,7 @@ type SmallCalendarIconProps = {
|
|||||||
};
|
};
|
||||||
export default function SmallCalendarIcon({ date }: SmallCalendarIconProps) {
|
export default function SmallCalendarIcon({ date }: SmallCalendarIconProps) {
|
||||||
return (
|
return (
|
||||||
<div className="center h-10 w-10 overflow-hidden rounded-sm border text-muted-foreground">
|
<div className="center h-10 w-10 overflow-hidden rounded-sm border bg-background text-muted-foreground">
|
||||||
<div className="w-full text-center">
|
<div className="w-full text-center">
|
||||||
<div className="bg-muted p-[2px] text-[10px] font-semibold uppercase">
|
<div className="bg-muted p-[2px] text-[10px] font-semibold uppercase">
|
||||||
{formatDate(date, "MMM")}
|
{formatDate(date, "MMM")}
|
||||||
|
@ -119,10 +119,12 @@ function CommandSearch({ location, onSelect }: CommandSearchProps) {
|
|||||||
>
|
>
|
||||||
{location ? (
|
{location ? (
|
||||||
<div className="flex max-w-full items-baseline gap-x-2">
|
<div className="flex max-w-full items-baseline gap-x-2">
|
||||||
|
<p className="line-clamp-1">
|
||||||
{location.name}
|
{location.name}
|
||||||
<span className="truncate text-xs text-muted-foreground">
|
<span className="ml-1 text-xs text-muted-foreground">
|
||||||
{location.address}
|
{location.address}
|
||||||
</span>
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
"Add a location..."
|
"Add a location..."
|
||||||
|
@ -1,29 +1,40 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import Template from "./Template";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import useAutosizeTextArea from "@/lib/hooks/useAutoSizeTextArea";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { HiX } from "react-icons/hi";
|
import { HiX } from "react-icons/hi";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import { randomId } from "@/lib/nostr";
|
||||||
addMinutesToDate,
|
import { unixTimeNowInSeconds } from "@/lib/nostr/dates";
|
||||||
convertToTimezoneDate,
|
import { addMinutesToDate, toUnix, convertToTimezone } from "@/lib/utils/dates";
|
||||||
convertToTimezone,
|
|
||||||
} from "@/lib/utils/dates";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { DatePicker } from "@/components/ui/date-picker";
|
import { DatePicker } from "@/components/ui/date-picker";
|
||||||
import { TimePicker } from "@/components/ui/time-picker";
|
import { TimePicker } from "@/components/ui/time-picker";
|
||||||
import { TimezoneSelector } from "../ui/timezone";
|
import { TimezoneSelector } from "@/components/ui/timezone";
|
||||||
import SmallCalendarIcon from "../EventIcons/DateIcon";
|
import { Label } from "@/components/ui/label";
|
||||||
import LocationIcon from "../EventIcons/LocationIcon";
|
|
||||||
import LocationSearchInput from "../LocationSearch";
|
import SmallCalendarIcon from "@/components/EventIcons/DateIcon";
|
||||||
|
import LocationIcon from "@/components/EventIcons/LocationIcon";
|
||||||
|
import LocationSearchInput from "@/components/LocationSearch";
|
||||||
|
|
||||||
|
import useAutosizeTextArea from "@/lib/hooks/useAutoSizeTextArea";
|
||||||
import { useModal } from "@/app/_providers/modal/provider";
|
import { useModal } from "@/app/_providers/modal/provider";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { createEvent } from "@/lib/actions/create";
|
||||||
|
import { useNDK } from "@/app/_providers/ndk";
|
||||||
|
import { type NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import useCurrentUser from "@/lib/hooks/useCurrentUser";
|
||||||
|
|
||||||
export default function CreateCalendarEventModal() {
|
export default function CreateCalendarEventModal() {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const now = new Date(new Date().setHours(12, 0, 0, 0));
|
const now = new Date(new Date().setHours(12, 0, 0, 0));
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const [error, setError] = useState("");
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
|
const [description, setDescription] = useState("");
|
||||||
const [startDate, setStartDate] = useState<Date>(now);
|
const [startDate, setStartDate] = useState<Date>(now);
|
||||||
const startTime = `${
|
const startTime = `${
|
||||||
startDate?.getHours().toLocaleString().length === 1
|
startDate?.getHours().toLocaleString().length === 1
|
||||||
@ -34,7 +45,7 @@ export default function CreateCalendarEventModal() {
|
|||||||
? "0" + startDate?.getMinutes().toLocaleString()
|
? "0" + startDate?.getMinutes().toLocaleString()
|
||||||
: startDate?.getMinutes()
|
: startDate?.getMinutes()
|
||||||
}`;
|
}`;
|
||||||
const [endDate, setEndDate] = useState<Date | undefined>(
|
const [endDate, setEndDate] = useState<Date>(
|
||||||
new Date(new Date().setHours(13)),
|
new Date(new Date().setHours(13)),
|
||||||
);
|
);
|
||||||
const endTime = `${
|
const endTime = `${
|
||||||
@ -54,6 +65,65 @@ export default function CreateCalendarEventModal() {
|
|||||||
name: string;
|
name: string;
|
||||||
coordinates: { lat: number; lng: number };
|
coordinates: { lat: number; lng: number };
|
||||||
}>();
|
}>();
|
||||||
|
const { ndk } = useNDK();
|
||||||
|
const { currentUser } = useCurrentUser();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
console.log("CALLED", ndk, currentUser);
|
||||||
|
if (!ndk || !currentUser) return;
|
||||||
|
setIsLoading(true);
|
||||||
|
if (!title) {
|
||||||
|
setError("Please add a title");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const random = randomId();
|
||||||
|
|
||||||
|
const tags: string[][] = [
|
||||||
|
["d", random],
|
||||||
|
["name", title],
|
||||||
|
["description", description],
|
||||||
|
["start", toUnix(convertToTimezone(startDate, timezone)).toString()],
|
||||||
|
["end", toUnix(convertToTimezone(endDate, timezone)).toString()],
|
||||||
|
["start_tzid", timezone],
|
||||||
|
];
|
||||||
|
if (location) {
|
||||||
|
tags.push([
|
||||||
|
"location",
|
||||||
|
`${location.name}, ${location.address}`,
|
||||||
|
location.name,
|
||||||
|
location.address,
|
||||||
|
]);
|
||||||
|
tags.push([
|
||||||
|
"address",
|
||||||
|
`${location.name}, ${location.address}`,
|
||||||
|
location.name,
|
||||||
|
location.address,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
console.log("Adding ", tags);
|
||||||
|
const preEvent = {
|
||||||
|
content: description,
|
||||||
|
pubkey: currentUser.pubkey,
|
||||||
|
created_at: unixTimeNowInSeconds(),
|
||||||
|
tags: tags,
|
||||||
|
kind: 31923,
|
||||||
|
};
|
||||||
|
const event = await createEvent(ndk, preEvent);
|
||||||
|
if (event) {
|
||||||
|
toast.success("Event Created!");
|
||||||
|
modal?.hide();
|
||||||
|
router.push(`/event/${event.encode()}`);
|
||||||
|
} else {
|
||||||
|
toast.error("An error occured");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("err", err);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (startDate && endDate) {
|
if (startDate && endDate) {
|
||||||
@ -99,7 +169,7 @@ export default function CreateCalendarEventModal() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="max-w-[300px] flex-1 divide-y overflow-hidden rounded-md bg-muted">
|
<div className="max-w-[300px] flex-1 divide-y overflow-hidden rounded-md bg-muted">
|
||||||
<div className="flex justify-between p-0.5 px-2 pl-3">
|
<div className="flex justify-between p-0.5 px-2 pl-3">
|
||||||
<div className="flex w-[70px] shrink-0 items-center">Start</div>
|
<div className="flex w-[50px] shrink-0 items-center">Start</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex max-w-full bg-secondary">
|
<div className="flex max-w-full bg-secondary">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@ -138,7 +208,7 @@ export default function CreateCalendarEventModal() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between p-0.5 px-2 pl-3">
|
<div className="flex justify-between p-0.5 px-2 pl-3">
|
||||||
<div className="flex w-[70px] shrink-0 items-center">End</div>
|
<div className="flex w-[50px] shrink-0 items-center">End</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex max-w-full bg-secondary">
|
<div className="flex max-w-full bg-secondary">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@ -208,6 +278,24 @@ export default function CreateCalendarEventModal() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<Label className="text-muted-foreground">Event details</Label>
|
||||||
|
<Textarea
|
||||||
|
className="mt-1"
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
placeholder="Some into about this event..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
loading={isLoading}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,8 +51,7 @@ export const getTagValues = (name: string, tags: string[][]) => {
|
|||||||
export const getTagAllValues = (name: string, tags: string[][]) => {
|
export const getTagAllValues = (name: string, tags: string[][]) => {
|
||||||
const [itemTag] = tags.filter((tag: string[]) => tag[0] === name);
|
const [itemTag] = tags.filter((tag: string[]) => tag[0] === name);
|
||||||
const itemValues = itemTag || [, undefined];
|
const itemValues = itemTag || [, undefined];
|
||||||
itemValues.shift();
|
return itemValues.map((i, idx) => (idx ? i : undefined)).filter(Boolean);
|
||||||
return itemValues;
|
|
||||||
};
|
};
|
||||||
export const getTagsValues = (name: string, tags: string[][]) => {
|
export const getTagsValues = (name: string, tags: string[][]) => {
|
||||||
const itemTags = tags.filter((tag: string[]) => tag[0] === name);
|
const itemTags = tags.filter((tag: string[]) => tag[0] === name);
|
||||||
|
@ -80,6 +80,11 @@ export function formatDate(timestamp: Date, format?: string) {
|
|||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
return dayjs(timestamp).format(format ?? "MMMM Do, YYYY");
|
return dayjs(timestamp).format(format ?? "MMMM Do, YYYY");
|
||||||
}
|
}
|
||||||
|
export function formatDateUnix(timestamp: number, format?: string) {
|
||||||
|
dayjs.extend(advancedFormat);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
return dayjs(timestamp * 1000).format(format ?? "MMMM Do, YYYY");
|
||||||
|
}
|
||||||
export function convertToTimezoneDate(inputDate: Date, _timezone: string) {
|
export function convertToTimezoneDate(inputDate: Date, _timezone: string) {
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user