Skip to content

Commit 17dc383

Browse files
feat: event settings page and form
1 parent edd8c03 commit 17dc383

13 files changed

Lines changed: 495 additions & 37 deletions

File tree

apps/api/internal/api/handlers/events.go

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"fmt"
77
"net/http"
8-
"reflect"
98
"time"
109

1110
"github.com/go-chi/chi/v5"
@@ -19,6 +18,7 @@ import (
1918
"github.com/swamphacks/core/apps/api/internal/db/sqlc"
2019
"github.com/swamphacks/core/apps/api/internal/email"
2120
"github.com/swamphacks/core/apps/api/internal/parse"
21+
. "github.com/swamphacks/core/apps/api/internal/parse"
2222
"github.com/swamphacks/core/apps/api/internal/ptr"
2323
"github.com/swamphacks/core/apps/api/internal/services"
2424
"github.com/swamphacks/core/apps/api/internal/web"
@@ -146,6 +146,22 @@ func (h *EventHandler) GetEventByID(w http.ResponseWriter, r *http.Request) {
146146
res.Send(w, http.StatusOK, event)
147147
}
148148

149+
type UpdateEventFields struct {
150+
Name Optional[string] `json:"name"`
151+
Description Optional[*string] `json:"description"`
152+
Location Optional[*string] `json:"location"`
153+
LocationUrl Optional[*string] `json:"location_url"`
154+
MaxAttendees Optional[*int32] `json:"max_attendees"`
155+
ApplicationOpen Optional[time.Time] `json:"application_open"`
156+
ApplicationClose Optional[time.Time] `json:"application_close"`
157+
RsvpDeadline Optional[*time.Time] `json:"rsvp_deadline"`
158+
DecisionRelease Optional[*time.Time] `json:"decision_release"`
159+
StartTime Optional[time.Time] `json:"start_time"`
160+
EndTime Optional[time.Time] `json:"end_time"`
161+
WebsiteUrl Optional[*string] `json:"website_url"`
162+
IsPublished Optional[bool] `json:"is_published"`
163+
}
164+
149165
func (h *EventHandler) UpdateEventById(w http.ResponseWriter, r *http.Request) {
150166
eventIdStr := chi.URLParam(r, "eventId")
151167
if eventIdStr == "" {
@@ -158,7 +174,7 @@ func (h *EventHandler) UpdateEventById(w http.ResponseWriter, r *http.Request) {
158174
return
159175
}
160176

161-
var req sqlc.UpdateEventByIdParams
177+
var req UpdateEventFields
162178

163179
decoder := json.NewDecoder(r.Body)
164180
decoder.DisallowUnknownFields() // Prevents requests with extraneous fields
@@ -167,25 +183,50 @@ func (h *EventHandler) UpdateEventById(w http.ResponseWriter, r *http.Request) {
167183
return
168184
}
169185

170-
// Refactorme: could be improved by unmarshalling values into a generic that can include nil information
171-
// Todo: make sure that non nullable values can't be updated to null
172-
// Todo: Time validation
173-
req.NameDoUpdate = reflect.ValueOf(req.Name).IsValid()
174-
req.DescriptionDoUpdate = reflect.ValueOf(req.Description).IsValid()
175-
req.LocationDoUpdate = reflect.ValueOf(req.Location).IsValid()
176-
req.LocationUrlDoUpdate = reflect.ValueOf(req.LocationUrl).IsValid()
177-
req.MaxAttendeesDoUpdate = reflect.ValueOf(req.MaxAttendees).IsValid()
178-
req.ApplicationOpenDoUpdate = reflect.ValueOf(req.ApplicationOpen).IsValid()
179-
req.ApplicationCloseDoUpdate = reflect.ValueOf(req.ApplicationClose).IsValid()
180-
req.RsvpDeadlineDoUpdate = reflect.ValueOf(req.RsvpDeadline).IsValid()
181-
req.DecisionReleaseDoUpdate = reflect.ValueOf(req.DecisionRelease).IsValid()
182-
req.StartTimeDoUpdate = reflect.ValueOf(req.StartTime).IsValid()
183-
req.EndTimeDoUpdate = reflect.ValueOf(req.EndTime).IsValid()
184-
req.WebsiteUrlDoUpdate = reflect.ValueOf(req.WebsiteUrl).IsValid()
185-
req.IsPublishedDoUpdate = reflect.ValueOf(req.IsPublished).IsValid()
186-
req.ID = eventId
187-
188-
event, err := h.eventService.UpdateEventById(r.Context(), req)
186+
var params = sqlc.UpdateEventByIdParams{
187+
NameDoUpdate: req.Name.Present,
188+
Name: req.Name.Value,
189+
190+
DescriptionDoUpdate: req.Description.Present,
191+
Description: req.Description.Value,
192+
193+
LocationDoUpdate: req.Location.Present,
194+
Location: req.Location.Value,
195+
196+
LocationUrlDoUpdate: req.LocationUrl.Present,
197+
LocationUrl: req.LocationUrl.Value,
198+
199+
MaxAttendeesDoUpdate: req.MaxAttendees.Present,
200+
MaxAttendees: req.MaxAttendees.Value,
201+
202+
ApplicationOpenDoUpdate: req.ApplicationOpen.Present,
203+
ApplicationOpen: req.ApplicationOpen.Value,
204+
205+
ApplicationCloseDoUpdate: req.ApplicationClose.Present,
206+
ApplicationClose: req.ApplicationClose.Value,
207+
208+
RsvpDeadlineDoUpdate: req.RsvpDeadline.Present,
209+
RsvpDeadline: req.RsvpDeadline.Value,
210+
211+
DecisionReleaseDoUpdate: req.DecisionRelease.Present,
212+
DecisionRelease: req.DecisionRelease.Value,
213+
214+
StartTimeDoUpdate: req.StartTime.Present,
215+
StartTime: req.StartTime.Value,
216+
217+
EndTimeDoUpdate: req.EndTime.Present,
218+
EndTime: req.EndTime.Value,
219+
220+
WebsiteUrlDoUpdate: req.WebsiteUrl.Present,
221+
WebsiteUrl: req.WebsiteUrl.Value,
222+
223+
IsPublishedDoUpdate: req.IsPublished.Present,
224+
IsPublished: &req.IsPublished.Value,
225+
226+
ID: eventId,
227+
}
228+
229+
event, err := h.eventService.UpdateEventById(r.Context(), params)
189230

190231
if err != nil {
191232
switch err {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- +goose Up
2+
-- +goose StatementBegin
3+
ALTER TABLE events
4+
ADD COLUMN banner TEXT;
5+
-- +goose StatementEnd
6+
7+
-- +goose Down
8+
-- +goose StatementBegin
9+
ALTER TABLE events
10+
DROP COLUMN banner;
11+
-- +goose StatementEnd
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package parse
2+
3+
import "encoding/json"
4+
5+
type Optional[T any] struct {
6+
Value T
7+
Present bool
8+
}
9+
10+
func (o *Optional[T]) UnmarshalJSON(data []byte) error {
11+
o.Present = true
12+
return json.Unmarshal(data, &o.Value)
13+
}

apps/web/src/components/ui/DatePicker/DatePicker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function DatePicker<T extends DateValue>({
4141
>
4242
{label && <Label isRequired={props.isRequired}>{label}</Label>}
4343
<FieldGroup className="min-w-[208px] w-auto border-1 rounded-sm items-center bg-input-bg">
44-
<DateInput className="flex-1 min-w-[150px] px-2 py-1.5 text-sm" />
44+
<DateInput className="flex-1 min-w-[150px] px-2 py-2 text-sm" />
4545
<Button
4646
variant="icon"
4747
className="w-6 mr-1 rounded-sm outline-offset-0 px-0 py-1"

apps/web/src/features/Admin/EventManager/components/AddEventModal.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,28 @@ import {
66
OverlayTriggerStateContext,
77
Text,
88
type DateRange,
9-
type DateValue,
109
} from "react-aria-components";
1110
import z from "zod";
1211
import { useCreateAdminEvent } from "../hooks/useCreateAdminEvent";
1312
import { useFormErrors } from "@/components/Form";
1413
import { Modal } from "@/components/ui/Modal";
1514
import { DateRangePicker } from "@/components/ui/DateRangePicker";
1615
import { useContext } from "react";
17-
18-
const calenderDateTimeSchema = z
19-
.custom<DateValue>()
20-
.transform((val) => val.toDate("UTC"));
16+
import { DateTimeSchema } from "@/utils/customSchemas";
2117

2218
const addEventSchema = z.object({
2319
eventName: z.string().min(1, "Event name is required"),
2420
eventDateRange: z.object(
2521
{
26-
start: calenderDateTimeSchema,
27-
end: calenderDateTimeSchema,
22+
start: DateTimeSchema,
23+
end: DateTimeSchema,
2824
},
2925
"Must specify a date range.",
3026
),
3127
applicationDateRange: z.object(
3228
{
33-
start: calenderDateTimeSchema,
34-
end: calenderDateTimeSchema,
29+
start: DateTimeSchema,
30+
end: DateTimeSchema,
3531
},
3632
"Must specify a date range.",
3733
),
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { api } from "@/lib/ky";
2+
import type { operations } from "@/lib/openapi/schema";
3+
4+
type Event =
5+
operations["get-single-event"]["responses"]["201"]["content"]["application/json"];
6+
7+
export async function getEventById(eventId: string): Promise<Event> {
8+
const result = await api.get<Event>(`events/${eventId}`).json();
9+
10+
return result;
11+
}

0 commit comments

Comments
 (0)