calendar improvments
This commit is contained in:
parent
847fc2c190
commit
1b3f2b4d6e
@ -1,18 +1,19 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useModal } from "@/app/_providers/modal/provider";
|
import { useModal } from "@/app/_providers/modal/provider";
|
||||||
import RSVPModal from "@/components/Modals/RSVP";
|
import EditCalendarModal from "@/components/Modals/EditCalendar";
|
||||||
|
import { NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
|
||||||
type RSVPButtonProps = {
|
type RSVPButtonProps = {
|
||||||
eventReference: string;
|
event: NostrEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RSVPButton({ eventReference }: RSVPButtonProps) {
|
export default function RSVPButton({ event }: RSVPButtonProps) {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant={"outline"}
|
variant={"outline"}
|
||||||
onClick={() => modal?.show(<RSVPModal eventReference={eventReference} />)}
|
onClick={() => modal?.show(<EditCalendarModal listEvent={event} />)}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -135,7 +135,7 @@ export default function Header({ event }: { event: NDKEvent }) {
|
|||||||
{!!currentUser && currentUser.pubkey === pubkey && (
|
{!!currentUser && currentUser.pubkey === pubkey && (
|
||||||
<>
|
<>
|
||||||
<CreateEventButton eventReference={eventReference} />
|
<CreateEventButton eventReference={eventReference} />
|
||||||
<EditCalendarButton eventReference={eventReference} />
|
<EditCalendarButton event={event.rawEvent()} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,7 +46,6 @@ export default function EventPage({
|
|||||||
}
|
}
|
||||||
const { tags } = event;
|
const { tags } = event;
|
||||||
const eventReference = event.encode();
|
const eventReference = event.encode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative mx-auto max-w-5xl space-y-4 p-2 @container sm:p-4">
|
<div className="relative mx-auto max-w-5xl space-y-4 p-2 @container sm:p-4">
|
||||||
<Header event={event} />
|
<Header event={event} />
|
||||||
|
86
app/(app)/event/[naddr]/_components/CalendarInfo.tsx
Normal file
86
app/(app)/event/[naddr]/_components/CalendarInfo.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
"use client";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
|
import { getLettersPlain } from "@/lib/utils";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { HiMiniChevronRight, HiCalendarDays } from "react-icons/hi2";
|
||||||
|
import useEvents from "@/lib/hooks/useEvents";
|
||||||
|
import { getTagValues } from "@/lib/nostr/utils";
|
||||||
|
import ProfileInfo from "./ProfileInfo";
|
||||||
|
import { type NDKKind, type NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import { useNDK } from "@/app/_providers/ndk";
|
||||||
|
|
||||||
|
type CalendarInfoProps = {
|
||||||
|
eventReference: string;
|
||||||
|
};
|
||||||
|
export default function CalendarInfo({ eventReference }: CalendarInfoProps) {
|
||||||
|
console.log("eventReference", eventReference);
|
||||||
|
const { type, data } = nip19.decode(eventReference);
|
||||||
|
if (type !== "naddr") {
|
||||||
|
throw new Error("Invalid list");
|
||||||
|
}
|
||||||
|
const { pubkey } = data;
|
||||||
|
const { ndk } = useNDK();
|
||||||
|
const [event, setEvent] = useState<NDKEvent>();
|
||||||
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ndk || isFetching || event) return;
|
||||||
|
handleFetchEvent();
|
||||||
|
}, [ndk, eventReference]);
|
||||||
|
|
||||||
|
async function handleFetchEvent() {
|
||||||
|
if (!ndk) return;
|
||||||
|
setIsFetching(true);
|
||||||
|
|
||||||
|
const calendarEvent = await ndk
|
||||||
|
.fetchEvent({
|
||||||
|
authors: [pubkey],
|
||||||
|
["#a"]: [eventReference],
|
||||||
|
kinds: [31924 as NDKKind],
|
||||||
|
})
|
||||||
|
.catch((err) => console.log("err"));
|
||||||
|
if (calendarEvent) {
|
||||||
|
console.log("Found calendar", calendarEvent);
|
||||||
|
setEvent(calendarEvent);
|
||||||
|
}
|
||||||
|
setIsFetching(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return <ProfileInfo pubkey={pubkey} />;
|
||||||
|
}
|
||||||
|
const image = getTagValues("image", event.tags);
|
||||||
|
const name = getTagValues("name", event.tags);
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/calendar/${eventReference}`}
|
||||||
|
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={image} alt={name ?? "event"} />
|
||||||
|
<AvatarFallback className="text-[8px]">
|
||||||
|
{getLettersPlain(name)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="text-[14px] ">{name ?? "Calendar"}</span>
|
||||||
|
<HiCalendarDays className="h-3 w-3 text-primary" />
|
||||||
|
</div>
|
||||||
|
<HiMiniChevronRight className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoadingCalendarInfo() {
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
21
app/(app)/event/[naddr]/_components/EditEventButton.tsx
Normal file
21
app/(app)/event/[naddr]/_components/EditEventButton.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useModal } from "@/app/_providers/modal/provider";
|
||||||
|
import EditEventModal from "@/components/Modals/EditEvent";
|
||||||
|
import { NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
|
||||||
|
type EditEventProps = {
|
||||||
|
event: NostrEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function EditEvent({ event }: EditEventProps) {
|
||||||
|
const modal = useModal();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={"outline"}
|
||||||
|
onClick={() => modal?.show(<EditEventModal listEvent={event} />)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
@ -12,6 +12,7 @@ import {
|
|||||||
getTagsValues,
|
getTagsValues,
|
||||||
} from "@/lib/nostr/utils";
|
} from "@/lib/nostr/utils";
|
||||||
import ProfileInfo from "./ProfileInfo";
|
import ProfileInfo from "./ProfileInfo";
|
||||||
|
import CalendarInfo from "./CalendarInfo";
|
||||||
import useCurrentUser from "@/lib/hooks/useCurrentUser";
|
import useCurrentUser from "@/lib/hooks/useCurrentUser";
|
||||||
import { useNDK } from "@/app/_providers/ndk";
|
import { useNDK } from "@/app/_providers/ndk";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@ -30,13 +31,8 @@ import LocationIcon from "@/components/EventIcons/LocationIcon";
|
|||||||
const RSVPButton = dynamic(() => import("./RSVPButton"), {
|
const RSVPButton = dynamic(() => import("./RSVPButton"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
const CreateListEvent = dynamic(
|
|
||||||
() => import("@/components/Modals/ShortTextNoteOnList"),
|
const EditEventButton = dynamic(() => import("./EditEventButton"), {
|
||||||
{
|
|
||||||
ssr: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const ConfirmModal = dynamic(() => import("@/components/Modals/Confirm"), {
|
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -151,26 +147,14 @@ export default function Header({ event }: { event: NDKEvent }) {
|
|||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<ProfileInfo pubkey={pubkey} />
|
<CalendarInfo eventReference={eventReference} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center justify-end gap-3">
|
<div className="flex flex-wrap items-center justify-end gap-3">
|
||||||
{/* {!!currentUser && currentUser.pubkey === pubkey && (
|
{!!currentUser && currentUser.pubkey === pubkey && (
|
||||||
<>
|
<EditEventButton event={event.rawEvent()} />
|
||||||
<Button onClick={() => modal?.show(<CreateListEvent />)}>
|
)}
|
||||||
Invite Users
|
{!isMember && <RSVPButton eventReference={eventReference} />}
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() =>
|
|
||||||
modal?.show(<EditEventModal listEvent={rawEvent} />)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)} */}
|
|
||||||
<RSVPButton eventReference={eventReference} />
|
|
||||||
{/* {!isMember &&
|
{/* {!isMember &&
|
||||||
(hasValidPayment ? (
|
(hasValidPayment ? (
|
||||||
<Button variant={"outline"}>Pending Sync</Button>
|
<Button variant={"outline"}>Pending Sync</Button>
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn, getLettersPlain, getTwoLetters } from "@/lib/utils";
|
||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { BANNER } from "@/constants/app";
|
import { BANNER } from "@/constants/app";
|
||||||
import { getNameToShow } from "@/lib/utils";
|
import { getNameToShow } from "@/lib/utils";
|
||||||
@ -21,6 +21,7 @@ import { getTagAllValues, getTagValues } from "@/lib/nostr/utils";
|
|||||||
import { useNDK } from "@/app/_providers/ndk";
|
import { useNDK } from "@/app/_providers/ndk";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
|
||||||
type CalendarCardProps = {
|
type CalendarCardProps = {
|
||||||
calendar: NDKEvent;
|
calendar: NDKEvent;
|
||||||
@ -35,6 +36,9 @@ export default function CalendarCard({ calendar }: CalendarCardProps) {
|
|||||||
const [upcomingEvents, setUpcomingEvents] = useState<NDKEvent[]>([]);
|
const [upcomingEvents, setUpcomingEvents] = useState<NDKEvent[]>([]);
|
||||||
const [isFetching, setIsFetching] = useState(false);
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
const name = getTagValues("name", tags);
|
const name = getTagValues("name", tags);
|
||||||
|
const image = getTagValues("image", tags);
|
||||||
|
const banner =
|
||||||
|
getTagValues("banner", tags) ?? profile?.image ?? profile?.banner ?? BANNER;
|
||||||
const description = content ?? getTagValues("about", tags);
|
const description = content ?? getTagValues("about", tags);
|
||||||
const calendarEvents = getTagAllValues("a", tags);
|
const calendarEvents = getTagAllValues("a", tags);
|
||||||
const calendarEventIdentifiers = calendarEvents
|
const calendarEventIdentifiers = calendarEvents
|
||||||
@ -78,7 +82,7 @@ export default function CalendarCard({ calendar }: CalendarCardProps) {
|
|||||||
<Card className="relative h-[350px] w-[250px] min-w-[250] overflow-hidden">
|
<Card className="relative h-[350px] w-[250px] min-w-[250] overflow-hidden">
|
||||||
<Image
|
<Image
|
||||||
alt="background"
|
alt="background"
|
||||||
src={profile?.banner ?? BANNER}
|
src={banner}
|
||||||
className="absolute inset-0 object-cover"
|
className="absolute inset-0 object-cover"
|
||||||
fill
|
fill
|
||||||
unoptimized
|
unoptimized
|
||||||
@ -95,18 +99,20 @@ export default function CalendarCard({ calendar }: CalendarCardProps) {
|
|||||||
{description}
|
{description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<Image
|
<Avatar className="absolute left-1/2 top-1/2 !aspect-square h-[100px] w-[100px] -translate-x-1/2 -translate-y-[70%] transform overflow-hidden rounded-lg bg-muted object-cover transition-all duration-300 group-hover:left-[50px] group-hover:top-[65px] group-hover:h-[70px] group-hover:w-[70px]">
|
||||||
|
<AvatarImage src={image ?? BANNER} height={100} width={100} />
|
||||||
|
<AvatarFallback>
|
||||||
|
{getLettersPlain(name ?? profile?.displayName ?? profile?.name)}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
{/* <Image
|
||||||
alt="user"
|
alt="user"
|
||||||
src={
|
src={image ?? BANNER}
|
||||||
profile?.image ??
|
|
||||||
profile?.picture ??
|
|
||||||
`https://bitcoinfaces.xyz/api/get-image?name=${npub}&onchain=false`
|
|
||||||
}
|
|
||||||
className="absolute left-1/2 top-1/2 aspect-square -translate-x-1/2 -translate-y-[70%] transform overflow-hidden rounded-lg bg-muted object-cover transition-all duration-300 group-hover:left-[50px] group-hover:top-[65px] group-hover:w-[70px]"
|
className="absolute left-1/2 top-1/2 aspect-square -translate-x-1/2 -translate-y-[70%] transform overflow-hidden rounded-lg bg-muted object-cover transition-all duration-300 group-hover:left-[50px] group-hover:top-[65px] group-hover:w-[70px]"
|
||||||
height={100}
|
height={100}
|
||||||
width={100}
|
width={100}
|
||||||
unoptimized
|
unoptimized
|
||||||
/>
|
/> */}
|
||||||
<Card className="absolute top-full min-h-full w-5/6 overflow-hidden transition-all duration-300 group-hover:top-1/3">
|
<Card className="absolute top-full min-h-full w-5/6 overflow-hidden transition-all duration-300 group-hover:top-1/3">
|
||||||
<CardHeader className="border-b p-4 pb-3">
|
<CardHeader className="border-b p-4 pb-3">
|
||||||
<CardTitle>Upcoming Events:</CardTitle>
|
<CardTitle>Upcoming Events:</CardTitle>
|
||||||
|
@ -11,14 +11,8 @@ import { addMinutesToDate, toUnix, convertToTimezone } from "@/lib/utils/dates";
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { DatePicker } from "@/components/ui/date-picker";
|
|
||||||
import { TimePicker } from "@/components/ui/time-picker";
|
|
||||||
import { TimezoneSelector } from "@/components/ui/timezone";
|
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
import SmallCalendarIcon from "@/components/EventIcons/DateIcon";
|
|
||||||
import LocationIcon from "@/components/EventIcons/LocationIcon";
|
|
||||||
import LocationSearchInput from "@/components/LocationSearch";
|
|
||||||
import Spinner from "../spinner";
|
import Spinner from "../spinner";
|
||||||
|
|
||||||
import useAutosizeTextArea from "@/lib/hooks/useAutoSizeTextArea";
|
import useAutosizeTextArea from "@/lib/hooks/useAutoSizeTextArea";
|
||||||
@ -40,6 +34,13 @@ export default function CreateCalendarEventModal() {
|
|||||||
imageUrl,
|
imageUrl,
|
||||||
status: imageStatus,
|
status: imageStatus,
|
||||||
} = useImageUpload("event");
|
} = useImageUpload("event");
|
||||||
|
const {
|
||||||
|
ImageUploadButton: BannerImageUploadButton,
|
||||||
|
clear: clearBanner,
|
||||||
|
imagePreview: bannerImagePreview,
|
||||||
|
imageUrl: bannerImageUrl,
|
||||||
|
status: bannerImageStatus,
|
||||||
|
} = useImageUpload("event");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
@ -73,6 +74,9 @@ export default function CreateCalendarEventModal() {
|
|||||||
if (imageUrl) {
|
if (imageUrl) {
|
||||||
tags.push(["image", imageUrl]);
|
tags.push(["image", imageUrl]);
|
||||||
}
|
}
|
||||||
|
if (bannerImageUrl) {
|
||||||
|
tags.push(["banner", bannerImageUrl]);
|
||||||
|
}
|
||||||
const preEvent = {
|
const preEvent = {
|
||||||
content: description,
|
content: description,
|
||||||
pubkey: currentUser.pubkey,
|
pubkey: currentUser.pubkey,
|
||||||
@ -134,7 +138,7 @@ export default function CreateCalendarEventModal() {
|
|||||||
placeholder="Some into about this calendar..."
|
placeholder="Some into about this calendar..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end gap-3">
|
||||||
{imagePreview ? (
|
{imagePreview ? (
|
||||||
<div className="relative overflow-hidden rounded-xl">
|
<div className="relative overflow-hidden rounded-xl">
|
||||||
<div className="">
|
<div className="">
|
||||||
@ -178,13 +182,58 @@ export default function CreateCalendarEventModal() {
|
|||||||
</Button>
|
</Button>
|
||||||
</ImageUploadButton>
|
</ImageUploadButton>
|
||||||
)}
|
)}
|
||||||
|
{bannerImagePreview ? (
|
||||||
|
<div className="relative overflow-hidden rounded-xl">
|
||||||
|
<div className="">
|
||||||
|
<Image
|
||||||
|
alt="Image"
|
||||||
|
height="288"
|
||||||
|
width="288"
|
||||||
|
src={bannerImagePreview}
|
||||||
|
className={cn(
|
||||||
|
"bg-bckground h-full rounded-xl object-cover object-center max-sm:max-h-[100px]",
|
||||||
|
bannerImageStatus === "uploading" && "grayscale",
|
||||||
|
bannerImageStatus === "error" && "blur-xl",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{bannerImageStatus === "uploading" && (
|
||||||
|
<button className="center absolute left-1 top-1 rounded-full bg-foreground bg-opacity-70 p-1 text-background hover:bg-opacity-100">
|
||||||
|
<Spinner />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{bannerImageStatus === "success" && (
|
||||||
|
<button
|
||||||
|
onClick={clearBanner}
|
||||||
|
className="center absolute left-1 top-1 rounded-full bg-foreground bg-opacity-70 p-1 hover:bg-opacity-100"
|
||||||
|
>
|
||||||
|
<HiX
|
||||||
|
className="block h-4 w-4 text-background"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ImageUploadButton>
|
||||||
|
<Button
|
||||||
|
className=""
|
||||||
|
variant={"outline"}
|
||||||
|
loading={bannerImageStatus === "uploading"}
|
||||||
|
>
|
||||||
|
{bannerImageUrl ? "Uploaded!" : "Upload Banner Image"}
|
||||||
|
</Button>
|
||||||
|
</ImageUploadButton>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
disabled={imageStatus === "uploading"}
|
disabled={
|
||||||
|
imageStatus === "uploading" || bannerImageStatus === "uploading"
|
||||||
|
}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
Create
|
Create
|
||||||
|
102
components/Modals/EditCalendar.tsx
Normal file
102
components/Modals/EditCalendar.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
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 EditCalendarSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
about: z.string().optional(),
|
||||||
|
image: z.string().optional(),
|
||||||
|
banner: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type EditListType = z.infer<typeof EditCalendarSchema>;
|
||||||
|
|
||||||
|
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("Calendar Updated!");
|
||||||
|
modal?.hide();
|
||||||
|
}
|
||||||
|
}, [events]);
|
||||||
|
|
||||||
|
async function handleSubmit(listData: EditListType) {
|
||||||
|
setIsLoading(true);
|
||||||
|
const newTags = Object.entries(listData);
|
||||||
|
setSent(true);
|
||||||
|
const result = await updateList(
|
||||||
|
ndk!,
|
||||||
|
{ ...listEvent, content: listData.about ?? "" },
|
||||||
|
newTags,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const defaultValues: Partial<EditListType> = {
|
||||||
|
name: getTagValues("name", listEvent.tags),
|
||||||
|
image:
|
||||||
|
getTagValues("image", listEvent.tags) ??
|
||||||
|
getTagValues("picture", listEvent.tags),
|
||||||
|
banner: getTagValues("banner", listEvent.tags),
|
||||||
|
about: listEvent.content,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormModal
|
||||||
|
title="Edit Calendar"
|
||||||
|
fields={[
|
||||||
|
{
|
||||||
|
label: "Name",
|
||||||
|
type: "input",
|
||||||
|
slug: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "About",
|
||||||
|
type: "text-area",
|
||||||
|
slug: "about",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Image",
|
||||||
|
type: "upload",
|
||||||
|
slug: "image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Banner Image",
|
||||||
|
type: "upload",
|
||||||
|
slug: "banner",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
defaultValues={defaultValues ?? {}}
|
||||||
|
formSchema={EditCalendarSchema}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
isSubmitting={isLoading}
|
||||||
|
cta={{
|
||||||
|
text: "Save Changes",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -12,7 +12,7 @@ import { getTagValues } from "@/lib/nostr/utils";
|
|||||||
|
|
||||||
const EditListSchema = z.object({
|
const EditListSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().optional(),
|
about: z.string().optional(),
|
||||||
image: z.string().optional(),
|
image: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ export default function EditListModal({ listEvent }: EditListModalProps) {
|
|||||||
if (events.length) {
|
if (events.length) {
|
||||||
console.log("Done!");
|
console.log("Done!");
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
toast.success("List Updated!");
|
toast.success("Event Updated!");
|
||||||
modal?.hide();
|
modal?.hide();
|
||||||
}
|
}
|
||||||
}, [events]);
|
}, [events]);
|
||||||
@ -50,7 +50,7 @@ export default function EditListModal({ listEvent }: EditListModalProps) {
|
|||||||
setSent(true);
|
setSent(true);
|
||||||
const result = await updateList(
|
const result = await updateList(
|
||||||
ndk!,
|
ndk!,
|
||||||
{ ...listEvent, content: listData.description ?? "" },
|
{ ...listEvent, content: listData.about ?? "" },
|
||||||
newTags,
|
newTags,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -61,12 +61,12 @@ export default function EditListModal({ listEvent }: EditListModalProps) {
|
|||||||
image:
|
image:
|
||||||
getTagValues("image", listEvent.tags) ??
|
getTagValues("image", listEvent.tags) ??
|
||||||
getTagValues("picture", listEvent.tags),
|
getTagValues("picture", listEvent.tags),
|
||||||
description: listEvent.content,
|
about: listEvent.content,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormModal
|
<FormModal
|
||||||
title="Edit List"
|
title="Edit Event"
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
label: "Name",
|
label: "Name",
|
||||||
@ -74,14 +74,14 @@ export default function EditListModal({ listEvent }: EditListModalProps) {
|
|||||||
slug: "name",
|
slug: "name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Image",
|
label: "About",
|
||||||
type: "input",
|
type: "text-area",
|
||||||
slug: "image",
|
slug: "about",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Description",
|
label: "Image",
|
||||||
type: "text-area",
|
type: "upload",
|
||||||
slug: "description",
|
slug: "image",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
defaultValues={defaultValues ?? {}}
|
defaultValues={defaultValues ?? {}}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { type ReactNode } from "react";
|
import { useEffect, type ReactNode } from "react";
|
||||||
import * as z from "zod";
|
import * as z from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { HiOutlineChevronDown } from "react-icons/hi2";
|
import { HiOutlineChevronDown, HiXMark } from "react-icons/hi2";
|
||||||
|
import Image from "next/image";
|
||||||
import {
|
import {
|
||||||
FieldErrors,
|
FieldErrors,
|
||||||
useForm,
|
useForm,
|
||||||
@ -47,6 +48,8 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import Spinner from "../spinner";
|
||||||
|
import useImageUpload from "@/lib/hooks/useImageUpload";
|
||||||
|
|
||||||
type FieldOptions =
|
type FieldOptions =
|
||||||
| "toggle"
|
| "toggle"
|
||||||
@ -54,6 +57,7 @@ type FieldOptions =
|
|||||||
| "input"
|
| "input"
|
||||||
| "number"
|
| "number"
|
||||||
| "text-area"
|
| "text-area"
|
||||||
|
| "upload"
|
||||||
| "custom";
|
| "custom";
|
||||||
|
|
||||||
type DefaultFieldType<TSchema> = {
|
type DefaultFieldType<TSchema> = {
|
||||||
@ -276,6 +280,13 @@ export default function FormModal<TSchema extends FieldValues>({
|
|||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
) : type === "upload" ? (
|
||||||
|
<FormControl>
|
||||||
|
<ImageUpload
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
) : type === "number" ? (
|
) : type === "number" ? (
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
@ -316,3 +327,70 @@ export default function FormModal<TSchema extends FieldValues>({
|
|||||||
</Template>
|
</Template>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ImageUpload({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value?: string;
|
||||||
|
onChange: (imageUrl: string) => void;
|
||||||
|
}) {
|
||||||
|
const { status, imageUrl, clear, ImageUploadButton } =
|
||||||
|
useImageUpload("event");
|
||||||
|
const handleClear = () => {
|
||||||
|
clear();
|
||||||
|
onChange("");
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (imageUrl && value !== imageUrl) {
|
||||||
|
onChange(imageUrl);
|
||||||
|
}
|
||||||
|
}, [imageUrl]);
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
{value || imageUrl ? (
|
||||||
|
<div className="relative overflow-hidden rounded-xl">
|
||||||
|
<div className="">
|
||||||
|
<Image
|
||||||
|
alt="Image"
|
||||||
|
height="288"
|
||||||
|
width="288"
|
||||||
|
src={(value || imageUrl) as string}
|
||||||
|
className={cn(
|
||||||
|
"bg-bckground h-full rounded-xl object-cover object-center max-sm:max-h-[100px]",
|
||||||
|
status === "uploading" && "grayscale",
|
||||||
|
status === "error" && "blur-xl",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{status === "uploading" && (
|
||||||
|
<button className="center absolute left-1 top-1 rounded-full bg-foreground bg-opacity-70 p-1 text-background hover:bg-opacity-100">
|
||||||
|
<Spinner />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{(status === "success" || value) && (
|
||||||
|
<button
|
||||||
|
onClick={handleClear}
|
||||||
|
className="center absolute left-1 top-1 rounded-full bg-foreground bg-opacity-70 p-1 hover:bg-opacity-100"
|
||||||
|
>
|
||||||
|
<HiXMark
|
||||||
|
className="block h-4 w-4 text-background"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ImageUploadButton>
|
||||||
|
<Button
|
||||||
|
className=""
|
||||||
|
variant={"outline"}
|
||||||
|
loading={status === "uploading"}
|
||||||
|
>
|
||||||
|
{imageUrl ? "Uploaded!" : "Upload"}
|
||||||
|
</Button>
|
||||||
|
</ImageUploadButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -43,7 +43,15 @@ export interface ButtonProps
|
|||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
(
|
(
|
||||||
{ className, variant, size, asChild = false, loading = false, ...props },
|
{
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
type = "button",
|
||||||
|
loading = false,
|
||||||
|
...props
|
||||||
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const Comp = asChild ? Slot : "button";
|
const Comp = asChild ? Slot : "button";
|
||||||
@ -52,6 +60,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
type={type}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="text-transparent">{props.children}</div>
|
<div className="text-transparent">{props.children}</div>
|
||||||
@ -65,6 +74,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
type={type}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { getTagAllValues } from "@/lib/nostr/utils";
|
import { getTagsValues } from "@/lib/nostr/utils";
|
||||||
import { groupEventsByDay } from ".";
|
import { groupEventsByDay } from ".";
|
||||||
import { useNDK } from "@/app/_providers/ndk";
|
import { useNDK } from "@/app/_providers/ndk";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
@ -18,11 +18,13 @@ export default function EventsFromCalendar({
|
|||||||
loader: Loader,
|
loader: Loader,
|
||||||
empty: Empty,
|
empty: Empty,
|
||||||
}: EventsFromCalendar) {
|
}: EventsFromCalendar) {
|
||||||
const calendarEvents = getTagAllValues("a", calendar.tags);
|
const calendarEvents = getTagsValues("a", calendar.tags);
|
||||||
const { ndk } = useNDK();
|
const { ndk } = useNDK();
|
||||||
const [events, setEvents] = useState<NDKEvent[]>([]);
|
const [events, setEvents] = useState<NDKEvent[]>([]);
|
||||||
const [isFetching, setIsFetching] = useState(false);
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
|
||||||
const calendarEventIdentifiers = calendarEvents
|
const calendarEventIdentifiers = calendarEvents
|
||||||
|
.filter(Boolean)
|
||||||
.map((e) => nip19.decode(e))
|
.map((e) => nip19.decode(e))
|
||||||
.filter(({ type }) => type === "naddr")
|
.filter(({ type }) => type === "naddr")
|
||||||
.map((e) => e.data as nip19.AddressPointer);
|
.map((e) => e.data as nip19.AddressPointer);
|
||||||
@ -33,6 +35,7 @@ export default function EventsFromCalendar({
|
|||||||
const events: NDKEvent[] = [];
|
const events: NDKEvent[] = [];
|
||||||
const promiseArray = [];
|
const promiseArray = [];
|
||||||
for (const info of data) {
|
for (const info of data) {
|
||||||
|
console.log("INFO", info);
|
||||||
const calendarEventPromise = ndk
|
const calendarEventPromise = ndk
|
||||||
.fetchEvent({
|
.fetchEvent({
|
||||||
authors: [info.pubkey],
|
authors: [info.pubkey],
|
||||||
|
@ -244,6 +244,7 @@ export async function createEventOnList(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const multipleTag = ["a", "p", "e"];
|
||||||
export async function updateList(
|
export async function updateList(
|
||||||
ndk: NDK,
|
ndk: NDK,
|
||||||
list: NostrEvent,
|
list: NostrEvent,
|
||||||
@ -254,7 +255,13 @@ export async function updateList(
|
|||||||
const index = tags.findIndex(([tK]) => tK === key);
|
const index = tags.findIndex(([tK]) => tK === key);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
// Replace old
|
// Replace old
|
||||||
|
if (multipleTag.includes(key)) {
|
||||||
|
if (value !== tags[index]?.[1]) {
|
||||||
|
tags.push([key, value]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
tags[index] = [key, value];
|
tags[index] = [key, value];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tags.push([key, value]);
|
tags.push([key, value]);
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,15 @@ export function getNameToShow(user: {
|
|||||||
user.profile?.displayName ?? user.profile?.name ?? truncateText(user.npub)
|
user.profile?.displayName ?? user.profile?.name ?? truncateText(user.npub)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export function getLettersPlain(text?: string) {
|
||||||
|
if (!text) return "";
|
||||||
|
const splitString = text
|
||||||
|
.split(" ")
|
||||||
|
.map((s) => s[0])
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ");
|
||||||
|
return splitString;
|
||||||
|
}
|
||||||
export function getTwoLetters(user: {
|
export function getTwoLetters(user: {
|
||||||
npub: string;
|
npub: string;
|
||||||
profile?: {
|
profile?: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user