Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restyle Event Card. Make other changes on front end #131

Merged
merged 2 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
Expand Down
26 changes: 26 additions & 0 deletions client/src/components/main/EventList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Event } from "@/hooks/queries/event";
import { cn } from "@/lib/utils";

import EventCard from "../ui/EventCard";

export default function EventList({
events,
className,
emptyMessage = "No events upcoming at the moment! Check this space later.",
}: {
events: Event[];
className?: string;
emptyMessage?: string;
}) {
return (
<ul className={cn("space-y-2", className)}>
{events.length === 0 ? (
<h1 className="mt-8 px-4 text-center text-xl text-primary md:text-3xl">
{emptyMessage}
</h1>
) : (
events.map((event) => <EventCard key={event.id} event={event} />)
)}
</ul>
);
}
46 changes: 6 additions & 40 deletions client/src/components/main/EventPage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { format as dateFormat } from "date-fns";
import { Edit, Mail, X } from "lucide-react";
import { Edit } from "lucide-react";
import Image from "next/image";
import Link from "next/link";

import RsvpButton from "@/components/main/RsvpButton";
import RsvpListModal from "@/components/main/RsvpListModal";
import LogInModal from "@/components/ui/LogInModal";
import PageCard from "@/components/ui/page-card";
import SignUpModal from "@/components/ui/SignUpModal";
import { Event } from "@/hooks/queries/event";
import { useAddRsvp, useDeleteRsvp, useHasRsvp } from "@/hooks/useRsvp";
import { useUser } from "@/hooks/useUser";

import RsvpListModal from "./RsvpListModal";

type EventPageProps = {
event: Event;
};
Expand Down Expand Up @@ -42,44 +41,11 @@ export const EventPage = ({
const end_time_fmt = dateFormat(end_time, "hh:mm aa");

const user_query = useUser();
const rsvp_query = useHasRsvp(id);
const { mutate: addRsvp } = useAddRsvp(id);
const { mutate: deleteRsvp } = useDeleteRsvp(id);

const attendeeControls = () => {
if (status != "Upcoming") {
return <></>;
} else if (rsvp_query.data) {
return (
<button
className="flex items-center justify-between gap-2 rounded-xl border border-black px-3 py-1 hover:bg-[#9DAD93]"
onClick={() => {
deleteRsvp();
}}
>
Remove RSVP <X strokeWidth="1" size="20" />
</button>
);
} else {
return (
<button
className="flex items-center justify-between gap-2 rounded-xl border border-black px-3 py-1 hover:bg-[#9DAD93]"
onClick={() => {
addRsvp();
}}
>
Send RSVP <Mail strokeWidth="1" size="20" />
</button>
);
}
};

const controls = () => {
if (
user_query.error?.response?.status === 401 ||
rsvp_query.error?.response?.status === 401 ||
user_query.data === undefined ||
rsvp_query.data === undefined
user_query.data === undefined
) {
return (
<span>
Expand All @@ -101,8 +67,8 @@ export const EventPage = ({
);
} else if (user_query.isLoading) {
return <>Loading</>;
} else if (user_query.data.role === "Attendee") {
return attendeeControls();
} else if (user_query.data.role === "Attendee" && status === "Upcoming") {
return <RsvpButton eventId={id} />;
} else {
return (
<div className="mt-2 flex gap-2">
Expand Down
79 changes: 79 additions & 0 deletions client/src/components/main/RsvpButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Loader, MailPlus, MailX, Trash, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "sonner";

import { useGetEvent } from "@/hooks/queries/event";
import { useDelayedPending } from "@/hooks/useDelayedPending";
import { useAddRsvp, useDeleteRsvp, useHasRsvp } from "@/hooks/useRsvp";
import { useUser } from "@/hooks/useUser";

export default function RsvpButton({ eventId }: { eventId: number }) {
const { data: eventData } = useGetEvent(eventId);
const { data: userData } = useUser();
const { data: hasRsvp } = useHasRsvp(eventId);
const { mutate: addRsvp, isPending: addPending } = useAddRsvp(eventId);
const { mutate: deleteRsvp, isPending: deletePending } =
useDeleteRsvp(eventId);

const isPending = addPending || deletePending;
const showPending = useDelayedPending(isPending);

if (
hasRsvp == undefined ||
eventData == undefined ||
userData?.role != "Attendee"
)
return <></>;

return (
<button
className="flex w-[9.8rem] items-center justify-between gap-2 rounded-xl border border-black px-2.5 py-1.5 hover:bg-[#9DAD93]"
onClick={() => {
if (isPending) return;
if (hasRsvp) {
deleteRsvp(undefined, {
onSuccess: () =>
toast(
"You have removed your RSVP. We'll let the event organisers know!",
{ duration: 5000 },
),
});
} else {
addRsvp(undefined, {
onSuccess: () =>
toast(`Your RSVP to ${eventData.title} has been sent!`, {
duration: 5000,
icon: "🎉",
}),
});
}
}}
>
<InsideButton isPending={showPending} hasRsvp={hasRsvp} />
</button>
);
}

const InsideButton = ({
isPending,
hasRsvp,
}: {
isPending: boolean;
hasRsvp: boolean;
}) => {
if (isPending) {
return <Loader className="mx-auto animate-spin" />;
} else if (hasRsvp) {
return (
<>
Cancel RSVP <Trash2 strokeWidth="1" size="20" />
</>
);
} else {
return (
<>
Send RSVP <MailPlus strokeWidth="1" size="20" />
</>
);
}
};
4 changes: 3 additions & 1 deletion client/src/components/main/header/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export default function Layout({ children }: { children: ReactElement }) {
)}
>
<Header />
{children}
<div className="mx-auto my-3 w-[95%] max-w-screen-2xl md:my-4">
{children}
</div>
<Toaster position="bottom-center" richColors />
</main>
);
Expand Down
134 changes: 134 additions & 0 deletions client/src/components/ui/EventCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { format as dateFormat } from "date-fns";
import Image from "next/image";
import Link from "next/link";

import RsvpButton from "@/components/main/RsvpButton";
import { Event } from "@/hooks/queries/event";
import { cn } from "@/lib/utils";

export default function EventCard({
event: {
id,
start_time,
end_time,
title,
description,
branch,
location,
image,
},
}: {
event: Event;
}) {
const startTimeFmt = dateFormat(start_time, "hh:mm aa");
const endTimeFmt = dateFormat(end_time, "hh:mm aa");
const startDateFmt = dateFormat(start_time, "EEEE, do MMM");
const endDateFmt = dateFormat(end_time, "EEEE, do MMM");
const isMultiDay = startDateFmt !== endDateFmt;

return (
<div className="flex flex-col gap-4 rounded-md border px-3 py-4 text-black lg:flex-row xl:gap-8 xl:p-6">
{/* Image */}
<Link
href={`/event/${id}`}
className={`relative h-52 w-full overflow-hidden rounded-md sm:h-60 md:h-64 lg:order-3 lg:h-64 lg:w-96 ${!image && "hidden items-center justify-center border lg:flex"} `}
>
{image ? (
<Image
fill
src={image}
alt={`Image for ${title}`}
className="h-full w-full object-cover"
/>
) : (
<span className="italic">No Image</span>
)}
</Link>

{/* Date and Time (Desktop) */}
<div className="mt-2 hidden w-64 flex-col items-center xl:flex">
<div className="flex flex-col items-center">
<span className="text-2xl font-semibold tracking-tight">
{startDateFmt}
</span>
{isMultiDay && (
<>
<span className="font-medium italic">until</span>
<span className="text-2xl font-semibold tracking-tight">
{endDateFmt}
</span>
</>
)}
</div>
<div className="mb-3 mt-2 self-stretch border-b border-black"></div>
<span className="mb-3 text-center">
{startTimeFmt} - {endTimeFmt}
</span>
<RsvpButton eventId={id} />
</div>

{/* Title and Description (Desktop) */}
<div className="hidden min-w-0 flex-1 xl:block">
<Link href={`/event/${id}`} className="mb-4 block">
<h2 className="text-3xl font-semibold tracking-tight">{title}</h2>
</Link>
<div className="flex">
<BranchBadge branch={branch.name} />
<span className="overflow-hidden text-ellipsis whitespace-nowrap font-medium">
{location}
</span>
</div>
<div className="mb-4 mt-3 border-b border-black" />
<p className="line-clamp-5">{description}</p>
</div>

{/* Title, Description, Date and Time (Mobile/Tablet) */}
<div className="flex-1 xl:hidden">
<Link href={`/event/${id}`} className="block">
<h2 className="text-3xl font-semibold tracking-tight">{title}</h2>
</Link>
<p className="mt-1 w-4/5 overflow-hidden text-ellipsis whitespace-nowrap">
{location}
</p>
<div className="mb-2 mt-3 border-b"></div>
<div className="flex justify-between font-medium tracking-tight">
<span className="max-w-[45%]">{startDateFmt}</span>
<span className="text-right">
{isMultiDay ? (
startTimeFmt
) : (
<>
<span className="text-nowrap">{startTimeFmt}</span> -{" "}
<span className="text-nowrap">{endTimeFmt}</span>
</>
)}
</span>
</div>
<div className="mt-2 flex justify-between">
<BranchBadge branch={branch.name} className="" />
<RsvpButton eventId={id} />
</div>
<p className="mt-4 line-clamp-2 md:line-clamp-3">{description}</p>
</div>
</div>
);
}

function BranchBadge({
branch,
className,
}: {
branch: string;
className?: string;
}) {
return (
<span
className={cn(
"mr-3 inline-flex items-center rounded-lg bg-primary px-5 py-0.5 font-medium text-white",
className,
)}
>
{branch}
</span>
);
}
Loading
Loading