calendar improvments
This commit is contained in:
parent
847fc2c190
commit
1b3f2b4d6e
@ -1,18 +1,19 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
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 = {
|
||||
eventReference: string;
|
||||
event: NostrEvent;
|
||||
};
|
||||
|
||||
export default function RSVPButton({ eventReference }: RSVPButtonProps) {
|
||||
export default function RSVPButton({ event }: RSVPButtonProps) {
|
||||
const modal = useModal();
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={"outline"}
|
||||
onClick={() => modal?.show(<RSVPModal eventReference={eventReference} />)}
|
||||
onClick={() => modal?.show(<EditCalendarModal listEvent={event} />)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
|
@ -135,7 +135,7 @@ export default function Header({ event }: { event: NDKEvent }) {
|
||||
{!!currentUser && currentUser.pubkey === pubkey && (
|
||||
<>
|
||||
<CreateEventButton eventReference={eventReference} />
|
||||
<EditCalendarButton eventReference={eventReference} />
|
||||
<EditCalendarButton event={event.rawEvent()} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -46,7 +46,6 @@ export default function EventPage({
|
||||
}
|
||||
const { tags } = event;
|
||||
const eventReference = event.encode();
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto max-w-5xl space-y-4 p-2 @container sm:p-4">
|
||||
<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,
|
||||
} from "@/lib/nostr/utils";
|
||||
import ProfileInfo from "./ProfileInfo";
|
||||
import CalendarInfo from "./CalendarInfo";
|
||||
import useCurrentUser from "@/lib/hooks/useCurrentUser";
|
||||
import { useNDK } from "@/app/_providers/ndk";
|
||||
import { toast } from "sonner";
|
||||
@ -30,13 +31,8 @@ import LocationIcon from "@/components/EventIcons/LocationIcon";
|
||||
const RSVPButton = dynamic(() => import("./RSVPButton"), {
|
||||
ssr: false,
|
||||
});
|
||||
const CreateListEvent = dynamic(
|
||||
() => import("@/components/Modals/ShortTextNoteOnList"),
|
||||
{
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const ConfirmModal = dynamic(() => import("@/components/Modals/Confirm"), {
|
||||
|
||||
const EditEventButton = dynamic(() => import("./EditEventButton"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
@ -151,26 +147,14 @@ export default function Header({ event }: { event: NDKEvent }) {
|
||||
{title}
|
||||
</h2>
|
||||
<div className="flex items-center">
|
||||
<ProfileInfo pubkey={pubkey} />
|
||||
<CalendarInfo eventReference={eventReference} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-end gap-3">
|
||||
{/* {!!currentUser && currentUser.pubkey === pubkey && (
|
||||
<>
|
||||
<Button onClick={() => modal?.show(<CreateListEvent />)}>
|
||||
Invite Users
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
modal?.show(<EditEventModal listEvent={rawEvent} />)
|
||||
}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</>
|
||||
)} */}
|
||||
<RSVPButton eventReference={eventReference} />
|
||||
{!!currentUser && currentUser.pubkey === pubkey && (
|
||||
<EditEventButton event={event.rawEvent()} />
|
||||
)}
|
||||
{!isMember && <RSVPButton eventReference={eventReference} />}
|
||||
{/* {!isMember &&
|
||||
(hasValidPayment ? (
|
||||
<Button variant={"outline"}>Pending Sync</Button>
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn, getLettersPlain, getTwoLetters } from "@/lib/utils";
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { BANNER } from "@/constants/app";
|
||||
import { getNameToShow } from "@/lib/utils";
|
||||
@ -21,6 +21,7 @@ import { getTagAllValues, getTagValues } from "@/lib/nostr/utils";
|
||||
import { useNDK } from "@/app/_providers/ndk";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
|
||||
type CalendarCardProps = {
|
||||
calendar: NDKEvent;
|
||||
@ -35,6 +36,9 @@ export default function CalendarCard({ calendar }: CalendarCardProps) {
|
||||
const [upcomingEvents, setUpcomingEvents] = useState<NDKEvent[]>([]);
|
||||
const [isFetching, setIsFetching] = useState(false);
|
||||
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 calendarEvents = getTagAllValues("a", tags);
|
||||
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">
|
||||
<Image
|
||||
alt="background"
|
||||
src={profile?.banner ?? BANNER}
|
||||
src={banner}
|
||||
className="absolute inset-0 object-cover"
|
||||
fill
|
||||
unoptimized
|
||||
@ -95,18 +99,20 @@ export default function CalendarCard({ calendar }: CalendarCardProps) {
|
||||
{description}
|
||||
</CardDescription>
|
||||
</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"
|
||||
src={
|
||||
profile?.image ??
|
||||
profile?.picture ??
|
||||
`https://bitcoinfaces.xyz/api/get-image?name=${npub}&onchain=false`
|
||||
}
|
||||
src={image ?? BANNER}
|
||||
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}
|
||||
width={100}
|
||||
unoptimized
|
||||
/>
|
||||
/> */}
|
||||
<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">
|
||||
<CardTitle>Upcoming Events:</CardTitle>
|
||||
|
@ -11,14 +11,8 @@ import { addMinutesToDate, toUnix, 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 { TimePicker } from "@/components/ui/time-picker";
|
||||
import { TimezoneSelector } from "@/components/ui/timezone";
|
||||
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 useAutosizeTextArea from "@/lib/hooks/useAutoSizeTextArea";
|
||||
@ -40,6 +34,13 @@ export default function CreateCalendarEventModal() {
|
||||
imageUrl,
|
||||
status: imageStatus,
|
||||
} = useImageUpload("event");
|
||||
const {
|
||||
ImageUploadButton: BannerImageUploadButton,
|
||||
clear: clearBanner,
|
||||
imagePreview: bannerImagePreview,
|
||||
imageUrl: bannerImageUrl,
|
||||
status: bannerImageStatus,
|
||||
} = useImageUpload("event");
|
||||
const [error, setError] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
@ -73,6 +74,9 @@ export default function CreateCalendarEventModal() {
|
||||
if (imageUrl) {
|
||||
tags.push(["image", imageUrl]);
|
||||
}
|
||||
if (bannerImageUrl) {
|
||||
tags.push(["banner", bannerImageUrl]);
|
||||
}
|
||||
const preEvent = {
|
||||
content: description,
|
||||
pubkey: currentUser.pubkey,
|
||||
@ -134,7 +138,7 @@ export default function CreateCalendarEventModal() {
|
||||
placeholder="Some into about this calendar..."
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<div className="flex justify-end gap-3">
|
||||
{imagePreview ? (
|
||||
<div className="relative overflow-hidden rounded-xl">
|
||||
<div className="">
|
||||
@ -178,13 +182,58 @@ export default function CreateCalendarEventModal() {
|
||||
</Button>
|
||||
</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 className="flex">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
loading={isLoading}
|
||||
disabled={imageStatus === "uploading"}
|
||||
disabled={
|
||||
imageStatus === "uploading" || bannerImageStatus === "uploading"
|
||||
}
|
||||
className="w-full"
|
||||
>
|
||||
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({
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
about: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
});
|
||||
|
||||
@ -39,7 +39,7 @@ export default function EditListModal({ listEvent }: EditListModalProps) {
|
||||
if (events.length) {
|
||||
console.log("Done!");
|
||||
setIsLoading(false);
|
||||
toast.success("List Updated!");
|
||||
toast.success("Event Updated!");
|
||||
modal?.hide();
|
||||
}
|
||||
}, [events]);
|
||||
@ -50,7 +50,7 @@ export default function EditListModal({ listEvent }: EditListModalProps) {
|
||||
setSent(true);
|
||||
const result = await updateList(
|
||||
ndk!,
|
||||
{ ...listEvent, content: listData.description ?? "" },
|
||||
{ ...listEvent, content: listData.about ?? "" },
|
||||
newTags,
|
||||
);
|
||||
}
|
||||
@ -61,12 +61,12 @@ export default function EditListModal({ listEvent }: EditListModalProps) {
|
||||
image:
|
||||
getTagValues("image", listEvent.tags) ??
|
||||
getTagValues("picture", listEvent.tags),
|
||||
description: listEvent.content,
|
||||
about: listEvent.content,
|
||||
};
|
||||
|
||||
return (
|
||||
<FormModal
|
||||
title="Edit List"
|
||||
title="Edit Event"
|
||||
fields={[
|
||||
{
|
||||
label: "Name",
|
||||
@ -74,14 +74,14 @@ export default function EditListModal({ listEvent }: EditListModalProps) {
|
||||
slug: "name",
|
||||
},
|
||||
{
|
||||
label: "Image",
|
||||
type: "input",
|
||||
slug: "image",
|
||||
label: "About",
|
||||
type: "text-area",
|
||||
slug: "about",
|
||||
},
|
||||
{
|
||||
label: "Description",
|
||||
type: "text-area",
|
||||
slug: "description",
|
||||
label: "Image",
|
||||
type: "upload",
|
||||
slug: "image",
|
||||
},
|
||||
]}
|
||||
defaultValues={defaultValues ?? {}}
|
||||
|
@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { type ReactNode } from "react";
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
import * as z from "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 {
|
||||
FieldErrors,
|
||||
useForm,
|
||||
@ -47,6 +48,8 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import Spinner from "../spinner";
|
||||
import useImageUpload from "@/lib/hooks/useImageUpload";
|
||||
|
||||
type FieldOptions =
|
||||
| "toggle"
|
||||
@ -54,6 +57,7 @@ type FieldOptions =
|
||||
| "input"
|
||||
| "number"
|
||||
| "text-area"
|
||||
| "upload"
|
||||
| "custom";
|
||||
|
||||
type DefaultFieldType<TSchema> = {
|
||||
@ -276,6 +280,13 @@ export default function FormModal<TSchema extends FieldValues>({
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
) : type === "upload" ? (
|
||||
<FormControl>
|
||||
<ImageUpload
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
) : type === "number" ? (
|
||||
<FormControl>
|
||||
<Input
|
||||
@ -316,3 +327,70 @@ export default function FormModal<TSchema extends FieldValues>({
|
||||
</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>(
|
||||
(
|
||||
{ className, variant, size, asChild = false, loading = false, ...props },
|
||||
{
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
type = "button",
|
||||
loading = false,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
@ -52,6 +60,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
type={type}
|
||||
{...props}
|
||||
>
|
||||
<div className="text-transparent">{props.children}</div>
|
||||
@ -65,6 +74,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
type={type}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import { useState, useEffect } from "react";
|
||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||
import { getTagAllValues } from "@/lib/nostr/utils";
|
||||
import { getTagsValues } from "@/lib/nostr/utils";
|
||||
import { groupEventsByDay } from ".";
|
||||
import { useNDK } from "@/app/_providers/ndk";
|
||||
import { nip19 } from "nostr-tools";
|
||||
@ -18,11 +18,13 @@ export default function EventsFromCalendar({
|
||||
loader: Loader,
|
||||
empty: Empty,
|
||||
}: EventsFromCalendar) {
|
||||
const calendarEvents = getTagAllValues("a", calendar.tags);
|
||||
const calendarEvents = getTagsValues("a", calendar.tags);
|
||||
const { ndk } = useNDK();
|
||||
const [events, setEvents] = useState<NDKEvent[]>([]);
|
||||
const [isFetching, setIsFetching] = useState(false);
|
||||
|
||||
const calendarEventIdentifiers = calendarEvents
|
||||
.filter(Boolean)
|
||||
.map((e) => nip19.decode(e))
|
||||
.filter(({ type }) => type === "naddr")
|
||||
.map((e) => e.data as nip19.AddressPointer);
|
||||
@ -33,6 +35,7 @@ export default function EventsFromCalendar({
|
||||
const events: NDKEvent[] = [];
|
||||
const promiseArray = [];
|
||||
for (const info of data) {
|
||||
console.log("INFO", info);
|
||||
const calendarEventPromise = ndk
|
||||
.fetchEvent({
|
||||
authors: [info.pubkey],
|
||||
|
@ -244,6 +244,7 @@ export async function createEventOnList(
|
||||
return true;
|
||||
}
|
||||
|
||||
const multipleTag = ["a", "p", "e"];
|
||||
export async function updateList(
|
||||
ndk: NDK,
|
||||
list: NostrEvent,
|
||||
@ -254,7 +255,13 @@ export async function updateList(
|
||||
const index = tags.findIndex(([tK]) => tK === key);
|
||||
if (index !== -1) {
|
||||
// Replace old
|
||||
if (multipleTag.includes(key)) {
|
||||
if (value !== tags[index]?.[1]) {
|
||||
tags.push([key, value]);
|
||||
}
|
||||
} else {
|
||||
tags[index] = [key, value];
|
||||
}
|
||||
} else {
|
||||
tags.push([key, value]);
|
||||
}
|
||||
|
@ -37,6 +37,15 @@ export function getNameToShow(user: {
|
||||
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: {
|
||||
npub: string;
|
||||
profile?: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user