Skip to content
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
33 changes: 31 additions & 2 deletions apps/web/src/actions/checkin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,43 @@ import { UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE } from "@/lib/constants/";
import { checkInUserClient, checkInUserList } from "@/lib/queries/checkins";
import { adminCheckinSchema, universityIDSplitter } from "db/zod";
import { CheckinResult } from "@/lib/types/events";
import { revalidatePath } from "next/cache";
import { headers } from "next/headers";
import { getEventById } from "@/lib/queries/events";
import { returnValidationErrors } from "next-safe-action";
import z from "zod";
import { isWithinInterval } from "date-fns";

const { ALREADY_CHECKED_IN, SUCCESS, FAILED, SOME_FAILED } = CheckinResult;
const {
ALREADY_CHECKED_IN,
SUCCESS,
FAILED,
SOME_FAILED,
EVENT_NOT_FOUND,
CHECKIN_NOT_AVAILABLE,
} = CheckinResult;

export const checkInUserAction = userAction
.schema(userCheckinSchemaFormified)
.action(async ({ parsedInput }) => {
const { eventID } = parsedInput;
const event = await getEventById(eventID);
if (!event) {
returnValidationErrors(z.null(), {
_errors: [EVENT_NOT_FOUND],
});
}

const currentDateUTC = new Date();
const isCheckinAvailable = isWithinInterval(currentDateUTC, {
start: event.checkinStart,
end: event.checkinEnd,
});
if (!isCheckinAvailable) {
returnValidationErrors(z.null(), {
_errors: [CHECKIN_NOT_AVAILABLE],
});
}

try {
await checkInUserClient(parsedInput);
} catch (e) {
Expand Down
27 changes: 25 additions & 2 deletions apps/web/src/components/dash/admin/events/EditEventForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ import {
} from "@/components/ui/alert-dialog";
import { Switch } from "@/components/ui/switch";
import { useState, useEffect } from "react";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { isAfter, addHours, isBefore } from "date-fns";
import { getLocalTimeZone, parseAbsolute } from "@internationalized/date";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand Down Expand Up @@ -126,6 +125,30 @@ export default function EditEventForm({
return true;
}

const eventStartTime = form.watch("start");
const eventEndTime = form.watch("end");
const checkinStartTime = form.watch("checkinStart");
const checkinEndTime = form.watch("checkinEnd");

useEffect(() => {
if (isAfter(eventStartTime, eventEndTime)) {
form.setValue("end", addHours(eventStartTime, 1));
}
}, [eventStartTime]);

useEffect(() => {
if (isAfter(checkinStartTime, checkinEndTime)) {
form.setValue("checkinEnd", addHours(checkinStartTime, 1));
}
}, [checkinStartTime]);

useEffect(() => {
if (isBefore(checkinEndTime, eventEndTime)) {
form.setValue("checkinStart", eventStartTime);
form.setValue("checkinEnd", eventEndTime);
}
}, [eventEndTime]);

useEffect(() => {
if (Object.keys(form.formState.errors).length > 0) {
console.log("Errors: ", form.formState.errors);
Expand Down
29 changes: 28 additions & 1 deletion apps/web/src/components/dash/admin/events/NewEventForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import { useAction } from "next-safe-action/hooks";
import { put } from "@/lib/client/file-upload";
import { createEvent } from "@/actions/events/createNewEvent";
import { ONE_HOUR_IN_MILLISECONDS } from "@/lib/constants";
import { bucketEventThumbnailBaseUrl } from "config";
import type { NewEventFormProps } from "@/lib/types/events";
import {
Select,
Expand All @@ -54,6 +53,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { isAfter, isBefore, addHours } from "date-fns";

const formSchema = insertEventSchemaFormified;

Expand Down Expand Up @@ -116,6 +116,33 @@ export default function NewEventForm({
return true;
}

const eventStartTime = form.watch("start");
const eventEndTime = form.watch("end");
const checkinStartTime = form.watch("checkinStart");
const checkinEndTime = form.watch("checkinEnd");

useEffect(() => {
if (isAfter(eventStartTime, eventEndTime)) {
form.setValue("end", addHours(eventStartTime, 1));
}
}, [eventStartTime]);

useEffect(() => {
if (
isAfter(checkinStartTime, checkinEndTime) &&
hasDifferentCheckinTime
) {
form.setValue("checkinEnd", addHours(checkinStartTime, 1));
}
}, [checkinStartTime]);

useEffect(() => {
if (isBefore(checkinEndTime, eventEndTime) && hasDifferentCheckinTime) {
form.setValue("checkinStart", eventStartTime);
form.setValue("checkinEnd", eventEndTime);
}
}, [eventEndTime]);

const {
execute: runCreateEvent,
status: actionStatus,
Expand Down
7 changes: 6 additions & 1 deletion apps/web/src/components/dash/admin/overview/KeyMetrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
CheckCircleIcon,
BarChart,
ArrowUpIcon,
ArrowDownIcon,
UsersIcon,
TrendingUpIcon,
} from "lucide-react";
Expand Down Expand Up @@ -117,7 +118,11 @@ export default async function KeyMetrics() {
<div className="text-xl font-bold sm:text-2xl">
{growthRate}%
</div>
<ArrowUpIcon className="h-3 w-3 text-green-500 sm:h-4 sm:w-4" />
{growthRate > 0 ? (
<ArrowUpIcon className="h-4 w-4 text-green-500" />
) : growthRate < 0 ? (
<ArrowDownIcon className="h-4 w-4 text-red-500" />
) : null}
</div>
<p className="text-xs text-muted-foreground">
vs last month
Expand Down
19 changes: 11 additions & 8 deletions apps/web/src/components/events/EventsCardView.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { EventAndCategoriesType } from "@/lib/types/events";
import EventCardComponent from "./EventCardComponent";
import { ScrollArea } from "../ui/scroll-area";
import { isAfter } from "date-fns";
import { isEventCurrentlyHappening, isEventCheckinAllowed } from "@/lib/utils";
import { isAfter, isWithinInterval } from "date-fns";
export default function EventsCardView({
events,
clientTimeZone,
Expand All @@ -21,15 +20,19 @@ export default function EventsCardView({
key={event.id}
event={event}
isPast={isAfter(currentDateUTC, event.end)}
isEventCurrentlyHappening={isEventCurrentlyHappening(
isEventCurrentlyHappening={isWithinInterval(
currentDateUTC,
event.start,
event.end,
{
start: event.start,
end: event.end,
},
)}
isEventCheckinAllowed={isEventCheckinAllowed(
isEventCheckinAllowed={isWithinInterval(
currentDateUTC,
event.checkinStart,
event.checkinEnd,
{
start: event.checkinStart,
end: event.checkinEnd,
},
)}
clientTimezone={clientTimeZone}
/>
Expand Down
9 changes: 6 additions & 3 deletions apps/web/src/components/events/EventsView.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import EventsCardView from "./EventsCardView";
import EventsCalendarView from "./EventsCalendarView";
import { db, like, gte, and, lt } from "db";
import { db, like, gte, and, lt, eq } from "db";
import { events } from "db/schema";
import type { SearchParams } from "@/lib/types/shared";
import { EVENT_FILTERS } from "@/lib/constants/events";
import { unstable_noStore as noStore } from "next/cache";
import PageError from "../shared/PageError";
import { headers } from "next/headers";
import { getClientTimeZone, getUTCDate } from "@/lib/utils";
import { getRequestContext } from "@cloudflare/next-on-pages";

Expand Down Expand Up @@ -48,7 +47,11 @@ export default async function EventsView({ params }: { params: SearchParams }) {
},
},
},
where: and(eventSearchQuery, dateComparison),
where: and(
eventSearchQuery,
dateComparison,
eq(events.isHidden, false),
),
orderBy: events.start,
})
.then((events) => {
Expand Down
51 changes: 11 additions & 40 deletions apps/web/src/components/events/id/EventDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { getEventDetails } from "@/lib/queries/events";
import PageError from "../../shared/PageError";
import EventImage from "../shared/EventImage";
import { headers } from "next/headers";
import { TWENTY_FOUR_HOURS, ONE_HOUR_IN_MILLISECONDS } from "@/lib/constants";
import c from "config";
import {
getClientTimeZone,
getUTCDate,
isEventCurrentlyHappening,
} from "@/lib/utils";
import { differenceInHours, isAfter } from "date-fns";
import { getClientTimeZone, getUTCDate } from "@/lib/utils";
import { isAfter, isWithinInterval } from "date-fns";
import { formatInTimeZone } from "date-fns-tz";
import {
EVENT_DATE_FORMAT_STRING,
EVENT_TIME_FORMAT_STRING,
} from "@/lib/constants/events";
import EventDetailsLiveIndicator from "../shared/EventDetailsLiveIndicator";
import EventCategories from "../EventCategories";
import {
BellRing,
Expand Down Expand Up @@ -66,14 +60,13 @@ export default async function EventDetails({
if (!event) {
return <PageError message="Event Not Found" href="/events" />;
}
const { start, end, checkinStart, checkinEnd } = event;
const { start, end } = event;
const currentDateUTC = getUTCDate();
const isEventPassed = isAfter(currentDateUTC, end);
const isEventHappening = isEventCurrentlyHappening(
currentDateUTC,
start,
end,
);
const isEventHappening = isWithinInterval(currentDateUTC, {
start: start,
end: end,
});

const startTime = formatInTimeZone(
start,
Expand All @@ -97,15 +90,6 @@ export default async function EventDetails({

const checkInUrl = `/events/${event.id}/checkin`;

const isCheckinAvailable =
checkinStart <= currentDateUTC && currentDateUTC <= checkinEnd;

const checkInMessage = isCheckinAvailable
? "Ready to check-in? Click here!"
: isEventPassed
? "Check-in is closed"
: `Check-in starts on ${formatInTimeZone(start, clientTimeZone, `${EVENT_TIME_FORMAT_STRING} @${EVENT_DATE_FORMAT_STRING}`)}`;

const eventCalendarLink = {
title: event.name,
description: event.description,
Expand All @@ -114,19 +98,6 @@ export default async function EventDetails({
location: event.location,
};

const detailsProps = {
event,
startTime,
startDate: startDateFormatted,
formattedEventDuration,
checkInUrl,
checkInMessage,
eventCalendarLink,
isEventPassed,
isCheckinAvailable,
isEventHappening,
};

const { thumbnailUrl, location, description, points } = event;
const width = 500;
const height = 500;
Expand Down Expand Up @@ -183,16 +154,16 @@ export default async function EventDetails({

<div className="flex items-center justify-start gap-3">
<MapPin size={24} />
<p className=" flex">{event.location}</p>
<p className=" flex">{location}</p>
</div>

<div className="flex gap-x-3">
<CircleArrowUp size={24} />
<h3>
<span className="text-blue-500">
{event.points}
{points}
</span>{" "}
pt{event.points != 1 ? "s" : ""}
pt{points != 1 ? "s" : ""}
</h3>
</div>
</div>
Expand Down Expand Up @@ -263,7 +234,7 @@ export default async function EventDetails({
href={`/api/ics-calendar?event_id=${id}`}
target="_blank"
className="flex w-auto justify-between gap-3 rounded-md px-3 py-2 text-primary-foreground md:max-w-[7.5rem] lg:max-w-none"
download={`event_${event.id}.ics`}
download={`event_${id}.ics`}
>
<Image
src={iCalIcon}
Expand Down
24 changes: 14 additions & 10 deletions apps/web/src/components/events/id/checkin/EventCheckin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import EventCheckinForm from "./EventCheckinForm";
import { getClientTimeZone } from "@/lib/utils";
import { headers } from "next/headers";
import { formatInTimeZone } from "date-fns-tz";
import { isAfter } from "date-fns";
import { isBefore, isWithinInterval } from "date-fns";
import {
EVENT_TIME_FORMAT_STRING,
EVENT_DATE_FORMAT_STRING,
Expand All @@ -30,11 +30,6 @@ export default async function EventCheckin({

const href = `/events/${event.id}`;

const isPassed = isAfter(currentDateUTC, event.end);

if (isPassed) {
return <PageError message="Event has already passed" href={href} />;
}
const userEventData = await getUserDataAndCheckin(eventID, clerkId);

if (!userEventData) {
Expand All @@ -52,13 +47,22 @@ export default async function EventCheckin({
return <PageError message="You have already checked in" href={href} />;
}

const isCheckinAvailable =
event.checkinStart <= currentDateUTC &&
currentDateUTC <= event.checkinEnd;
const isCheckinAvailable = isWithinInterval(currentDateUTC, {
start: event.checkinStart,
end: event.checkinEnd,
});

if (!isCheckinAvailable) {
const isDateBeforeCheckinStart = isBefore(
currentDateUTC,
event.checkinStart,
);
const errorMessage = isDateBeforeCheckinStart
? `Check-in does not start until ${formatInTimeZone(event.checkinStart, clientTimeZone, `${EVENT_TIME_FORMAT_STRING} @ ${EVENT_DATE_FORMAT_STRING}`)}`
: `Check-in for this event ended on ${formatInTimeZone(event.checkinEnd, clientTimeZone, `${EVENT_TIME_FORMAT_STRING} @ ${EVENT_DATE_FORMAT_STRING}`)}`;
return (
<PageError
message={`Check-in does not start until ${formatInTimeZone(event.checkinStart, clientTimeZone, `${EVENT_TIME_FORMAT_STRING} @ ${EVENT_DATE_FORMAT_STRING}`)}`}
message={errorMessage}
href={href}
className="text-base md:px-12 lg:px-16"
/>
Expand Down
Loading