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

allow form to be used for both editing and creating #815

Merged
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react"
import AdminEventForm from "./AdminEventForm"
import { Timestamp } from "firebase/firestore"

const meta: Meta<typeof AdminEventForm> = {
component: AdminEventForm
Expand All @@ -9,3 +10,21 @@ export default meta
type Story = StoryObj<typeof meta>

export const DefaultAdminEventForm: Story = {}

export const DefaultAdminEventFormEditView: Story = {
args: {
isEditMode: true,
defaultData: {
title: "Goon Cave",
description:
"Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eaque officiis similique repellat accusantium eos dignissimos suscipit voluptatibus? Quia nobis repellendus quam, aliquid, veritatis eos fuga eligendi harum dolorum vero facere!",
location: "straight zhao's basement",
sign_up_start_date: Timestamp.fromDate(new Date(2024, 10, 4)),
sign_up_end_date: Timestamp.fromDate(new Date(2024, 11, 5)),
physical_start_date: Timestamp.fromDate(new Date(2024, 10, 3)),
physical_end_date: Timestamp.fromDate(new Date(2024, 11, 3)),
google_forms_link:
"https://www.reddit.com/r/VrGoonCave/comments/18gnrk8/welcome_to_vrgooncave/"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import {
EventMessages,
EventRenderingUtils
} from "@/components/generic/Event/EventUtils"
import Button from "@/components/generic/FigmaButtons/FigmaButton"
import TextInput from "@/components/generic/TextInputComponent/TextInput"
import { DateUtils } from "@/components/utils/DateUtils"
import { CreateEventBody } from "@/models/Events"
import { Timestamp } from "firebase/firestore"
import Image from "next/image"
import { FormEvent, useState } from "react"
import { FormEvent, useMemo, useState } from "react"

interface IAdminEventForm {
/**
Expand All @@ -23,6 +28,18 @@ interface IAdminEventForm {
* To be called after user submits the new data for the event
*/
handlePostEvent: (data: CreateEventBody) => void
/**
* If the panel should suggest that the event is being edited, instead of created
*
* Does the create mode if set to `false`
*/
isEditMode?: boolean
/**
* What to pre-fill the form with - _generally_ only to be used
* if {@link isEditMode} is set to true (which would have the previous
* values of the event.)
*/
defaultData?: CreateEventBody["data"]
}

export const AdminEventFormKeys = {
Expand All @@ -42,12 +59,20 @@ const Divider = () => <span className="bg-gray-3 my-3 h-[1px] w-full" />

const AdminEventForm = ({
handlePostEvent,
generateImageLink
generateImageLink,
isEditMode = false,
defaultData
}: IAdminEventForm) => {
const [isSubmitting, setIsSubmitting] = useState<boolean>(false)

const [uploadedImage, setUploadedImage] = useState<File | undefined>()

const formTitle = useMemo(
() =>
isEditMode ? `Edit Event ${defaultData?.title || ""}` : "Create Event",
[isEditMode, defaultData]
)

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
const data = new FormData(e.currentTarget)
Expand Down Expand Up @@ -86,7 +111,7 @@ const AdminEventForm = ({

if (
confirm(
`Are you sure you want to create the new event with title ${body.title}?`
EventMessages.adminEventPostConfirmation(!!isEditMode, body.title)
)
) {
handlePostEvent({
Expand All @@ -102,7 +127,7 @@ const AdminEventForm = ({
}
return (
<div className="relative my-4 flex w-full flex-col items-center rounded-md bg-white p-2">
<h2 className="text-dark-blue-100">Create Event</h2>
<h2 className="text-dark-blue-100">{formTitle}</h2>
<form
onSubmit={handleSubmit}
className="flex w-full max-w-[800px] flex-col gap-2"
Expand All @@ -111,11 +136,13 @@ const AdminEventForm = ({
name={AdminEventFormKeys.TITLE}
type="text"
label="Title"
defaultValue={defaultData?.title}
data-testid={AdminEventFormKeys.TITLE}
required
/>
<label htmlFor={AdminEventFormKeys.DESCRIPTION}>Description</label>
<textarea
defaultValue={defaultData?.description}
name={AdminEventFormKeys.DESCRIPTION}
data-testid={AdminEventFormKeys.DESCRIPTION}
/>
Expand Down Expand Up @@ -146,6 +173,7 @@ const AdminEventForm = ({
name={AdminEventFormKeys.LOCATION}
type="text"
label="Location"
defaultValue={defaultData?.location}
data-testid={AdminEventFormKeys.LOCATION}
required
/>
Expand All @@ -156,12 +184,30 @@ const AdminEventForm = ({
name={AdminEventFormKeys.SIGN_UP_START_DATE}
data-testid={AdminEventFormKeys.SIGN_UP_START_DATE}
type="datetime-local"
value={
defaultData &&
EventRenderingUtils.dateTimeLocalPlaceHolder(
new Date(
DateUtils.timestampMilliseconds(
defaultData?.sign_up_start_date
)
)
)
}
label="Sign Up Start Date"
required
/>
<TextInput
name={AdminEventFormKeys.SIGN_UP_END_DATE}
data-testid={AdminEventFormKeys.SIGN_UP_END_DATE}
value={
defaultData?.sign_up_end_date &&
EventRenderingUtils.dateTimeLocalPlaceHolder(
new Date(
DateUtils.timestampMilliseconds(defaultData?.sign_up_end_date)
)
)
}
type="datetime-local"
label="Sign Up End Date (If exists)"
/>
Expand All @@ -173,13 +219,33 @@ const AdminEventForm = ({
<TextInput
name={AdminEventFormKeys.PHYSICAL_START_DATE}
data-testid={AdminEventFormKeys.PHYSICAL_START_DATE}
value={
defaultData?.physical_start_date &&
EventRenderingUtils.dateTimeLocalPlaceHolder(
new Date(
DateUtils.timestampMilliseconds(
defaultData?.physical_start_date
)
)
)
}
type="datetime-local"
label="Start Date of Event"
required
/>
<TextInput
name={AdminEventFormKeys.PHYSICAL_END_DATE}
data-testid={AdminEventFormKeys.PHYSICAL_END_DATE}
value={
defaultData?.physical_end_date &&
EventRenderingUtils.dateTimeLocalPlaceHolder(
new Date(
DateUtils.timestampMilliseconds(
defaultData?.physical_end_date
)
)
)
}
type="datetime-local"
label="End Date of Event"
/>
Expand All @@ -188,6 +254,7 @@ const AdminEventForm = ({
<TextInput
name={AdminEventFormKeys.GOOGLE_FORMS_LINK}
data-testid={AdminEventFormKeys.GOOGLE_FORMS_LINK}
defaultValue={defaultData?.google_forms_link}
type="url"
label="Google Forms Link"
/>
Expand All @@ -196,7 +263,7 @@ const AdminEventForm = ({
type="submit"
data-testid="post-event-button"
>
Add Event
{isEditMode ? "Update Event" : "Add Event"}
</Button>
</form>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const EventsCardPreview = ({
<div
className={`border-gray-3 navbar-shadow flex w-full flex-col items-center
justify-center gap-2 rounded-md border bg-white p-4 sm:flex-row
sm:px-10 sm:py-${variant === "admin" ? 10 : 12} ${isPastEvent && "brightness-50"}`}
sm:px-10 ${variant === "admin" ? "sm:py-10" : "sm:py-12"} ${isPastEvent && "brightness-50"}`}
>
<div className="border-gray-3 h-fit max-h-[150px] w-[200px] overflow-hidden border">
<Image
Expand Down
33 changes: 33 additions & 0 deletions client/src/components/generic/Event/EventUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { EventRenderingUtils } from "./EventUtils"

describe("dateTimeLocalPlaceHolder", () => {
it("should return a formatted string in ISO 8601 format without milliseconds", () => {
const date = new Date("2024-11-01T23:34:15.123Z")
const result = EventRenderingUtils.dateTimeLocalPlaceHolder(date)
expect(result).toBe("2024-11-01T23:34:15")
})

it("should handle dates without milliseconds correctly", () => {
const date = new Date("2024-11-01T23:34:15Z")
const result = EventRenderingUtils.dateTimeLocalPlaceHolder(date)
expect(result).toBe("2024-11-01T23:34:15")
})

it("should handle different time zones correctly", () => {
const date = new Date("2024-11-01T23:34:15.123+09:00")
const result = EventRenderingUtils.dateTimeLocalPlaceHolder(date)
expect(result).toBe("2024-11-01T14:34:15")
})

it("should handle leap years correctly", () => {
const date = new Date("2024-02-29T23:34:15.123Z")
const result = EventRenderingUtils.dateTimeLocalPlaceHolder(date)
expect(result).toBe("2024-02-29T23:34:15")
})

it("should handle dates before 1970 correctly", () => {
const date = new Date("1969-12-31T23:34:15.123Z")
const result = EventRenderingUtils.dateTimeLocalPlaceHolder(date)
expect(result).toBe("1969-12-31T23:34:15")
})
})
25 changes: 25 additions & 0 deletions client/src/components/generic/Event/EventUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ export const IMAGE_PLACEHOLDER_SRC =
* Static methods to format strings related to events
*/
export const EventMessages = {
/**
* Message to be displayed for confirming event creation or editing
*
* @param isEditing boolean indicating if the event is being edited
* @param title the title of the event
* @returns a formatted, user-readable string asking for confirmation
*/
adminEventPostConfirmation: (isEditing: boolean, title: string) => {
if (isEditing) {
return `Are you sure you want to edit the event ${title}?`
} else {
return `Are you sure you want to create the new event with title ${title}?`
}
},
/**
* Message to be displayed if sign ups have opened in relation to the sign up date
*
Expand Down Expand Up @@ -72,6 +86,17 @@ export const EventDateComparisons = {
} as const

export const EventRenderingUtils = {
/**
* Generates a placeholder string for a local date and time input field
*
* @param date the date object to be formatted
* @returns a formatted string in ISO 8601 format without milliseconds
*/
dateTimeLocalPlaceHolder: (date: Date) => {
const isoString = date.toISOString()
const placeholderString = isoString.substring(0, isoString.lastIndexOf("."))
return placeholderString
},
/**
* Utility function to convert a raw {@link Event} into {@link IEventsCardPreview}
*
Expand Down
Loading