Skip to content

Commit dfcd663

Browse files
committed
Fix event updating bugs
1 parent 011f65a commit dfcd663

File tree

2 files changed

+72
-39
lines changed

2 files changed

+72
-39
lines changed

src/api/routes/events.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ const baseSchema = z.object({
126126
const requestSchema = baseSchema.extend({
127127
repeats: z.optional(z.enum(repeatOptions)),
128128
repeatEnds: z.string().optional(),
129-
repeatExcludes: z.array(z.string().date()).min(1).max(100).optional().meta({
129+
repeatExcludes: z.array(z.string().date()).max(100).optional().meta({
130130
description:
131131
"Dates to exclude from recurrence rules (in the America/Chicago timezone).",
132132
}),
@@ -346,35 +346,36 @@ const eventsPlugin: FastifyPluginAsyncZodOpenApi = async (
346346
throw new UnauthenticatedError({ message: "Username not found." });
347347
}
348348
try {
349+
const updatableFields = Object.keys(postRequestSchema.shape);
349350
const entryUUID = request.params.id;
350-
const updateData = {
351-
...request.body,
352-
updatedAt: new Date().toISOString(),
353-
};
354-
355-
Object.keys(updateData).forEach(
356-
(key) =>
357-
(updateData as Record<string, any>)[key] === undefined &&
358-
delete (updateData as Record<string, any>)[key],
359-
);
360-
361-
if (Object.keys(updateData).length === 0) {
362-
throw new ValidationError({
363-
message: "At least one field must be updated.",
364-
});
365-
}
351+
const requestData = request.body;
366352

367-
const updateExpressionParts: string[] = [];
353+
const setParts: string[] = [];
354+
const removeParts: string[] = [];
368355
const expressionAttributeNames: Record<string, string> = {};
369356
const expressionAttributeValues: Record<string, any> = {};
370357

371-
for (const [key, value] of Object.entries(updateData)) {
372-
updateExpressionParts.push(`#${key} = :${key}`);
373-
expressionAttributeNames[`#${key}`] = key;
374-
expressionAttributeValues[`:${key}`] = value;
375-
}
358+
setParts.push("#updatedAt = :updatedAt");
359+
expressionAttributeNames["#updatedAt"] = "updatedAt";
360+
expressionAttributeValues[":updatedAt"] = new Date().toISOString();
376361

377-
const updateExpression = `SET ${updateExpressionParts.join(", ")}`;
362+
updatableFields.forEach((key) => {
363+
if (Object.hasOwn(requestData, key)) {
364+
setParts.push(`#${key} = :${key}`);
365+
expressionAttributeNames[`#${key}`] = key;
366+
expressionAttributeValues[`:${key}`] =
367+
requestData[key as keyof typeof requestData];
368+
} else {
369+
removeParts.push(`#${key}`);
370+
expressionAttributeNames[`#${key}`] = key;
371+
}
372+
});
373+
374+
// Construct the final UpdateExpression by combining SET and REMOVE
375+
let updateExpression = `SET ${setParts.join(", ")}`;
376+
if (removeParts.length > 0) {
377+
updateExpression += ` REMOVE ${removeParts.join(", ")}`;
378+
}
378379

379380
const command = new UpdateItemCommand({
380381
TableName: genericConfig.EventsDynamoTableName,
@@ -400,7 +401,7 @@ const eventsPlugin: FastifyPluginAsyncZodOpenApi = async (
400401
// we know updateData has no undefines because we filtered them out.
401402
updatedEntry = {
402403
...oldEntry,
403-
...updateData,
404+
...requestData,
404405
} as unknown as IUpdateDiscord;
405406
} catch (e: unknown) {
406407
if (

src/ui/pages/events/ManageEvent.page.tsx

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,21 @@ import { useApi } from "@ui/util/api";
2525
import { AllOrganizationList as orgList } from "@acm-uiuc/js-shared";
2626
import { AppRoles } from "@common/roles";
2727
import { EVENT_CACHED_DURATION } from "@common/config";
28-
import { IconInfoCircle, IconPlus, IconTrash } from "@tabler/icons-react";
28+
import {
29+
IconAlertCircle,
30+
IconInfoCircle,
31+
IconPlus,
32+
IconTrash,
33+
} from "@tabler/icons-react";
2934
import {
3035
MAX_METADATA_KEYS,
3136
MAX_KEY_LENGTH,
3237
MAX_VALUE_LENGTH,
3338
metadataSchema,
3439
} from "@common/types/events";
3540
import { zod4Resolver as zodResolver } from "mantine-form-zod-resolver";
41+
import FullScreenLoader from "@ui/components/AuthContext/LoadingScreen";
42+
import { X } from "vitest/dist/chunks/reporters.d.BFLkQcL6";
3643

3744
export function capitalizeFirstLetter(string: string) {
3845
return string.charAt(0).toUpperCase() + string.slice(1);
@@ -75,9 +82,11 @@ const baseBodySchema = z.object({
7582

7683
const requestBodySchema = baseBodySchema
7784
.extend({
85+
start: z.coerce.date(),
86+
end: z.coerce.date(),
7887
repeats: z.optional(z.enum(repeatOptions)).nullable(),
7988
repeatEnds: z.date().optional(),
80-
repeatExcludes: z.array(z.date()).max(100).optional(),
89+
repeatExcludes: z.array(z.coerce.date()).max(100).optional(),
8190
})
8291
.refine((data) => (data.repeatEnds ? data.repeats !== undefined : true), {
8392
message: "Repeat frequency is required when Repeat End is specified.",
@@ -95,6 +104,7 @@ type EventPostRequest = z.infer<typeof requestBodySchema>;
95104

96105
export const ManageEventPage: React.FC = () => {
97106
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
107+
const [isLoading, setIsLoading] = useState<boolean>(true);
98108
const navigate = useNavigate();
99109
const api = useApi("core");
100110

@@ -104,6 +114,7 @@ export const ManageEventPage: React.FC = () => {
104114

105115
useEffect(() => {
106116
if (!isEditing) {
117+
setIsLoading(false);
107118
return;
108119
}
109120
const getEvent = async () => {
@@ -136,10 +147,14 @@ export const ManageEventPage: React.FC = () => {
136147
metadata: eventData.metadata || {},
137148
};
138149
form.setValues(formValues);
150+
setIsLoading(false);
139151
} catch (error) {
140152
console.error("Error fetching event data:", error);
141153
notifications.show({
142-
message: "Failed to fetch event data, please try again.",
154+
title: "Failed to fetch event data",
155+
message: "Please try again or contact support.",
156+
color: "red",
157+
icon: <IconAlertCircle size={16} />,
143158
});
144159
}
145160
};
@@ -176,12 +191,21 @@ export const ManageEventPage: React.FC = () => {
176191
if (form.values.locationLink === "") {
177192
form.setFieldValue("locationLink", undefined);
178193
}
179-
if (form.values.repeatExcludes?.length === 0) {
180-
form.setFieldValue("repeatExcludes", undefined);
181-
}
182-
}, [form.values.locationLink, form.values.repeatExcludes]);
194+
}, [form.values.locationLink]);
183195

184-
const handleSubmit = async (values: EventPostRequest) => {
196+
const handleSubmit = async () => {
197+
const result = form.validate();
198+
if (result.hasErrors) {
199+
console.warn(result.errors);
200+
notifications.show({
201+
title: "Validation failed",
202+
message: "Please review the errors and try again.",
203+
color: "red",
204+
icon: <IconAlertCircle size={16} />,
205+
});
206+
return;
207+
}
208+
const values = form.values;
185209
try {
186210
setIsSubmitting(true);
187211

@@ -195,9 +219,10 @@ export const ManageEventPage: React.FC = () => {
195219
values.repeatEnds && values.repeats
196220
? dayjs(values.repeatEnds).format("YYYY-MM-DD[T]HH:mm:00")
197221
: undefined,
198-
repeatExcludes: values.repeatExcludes
199-
? values.repeatExcludes.map((x) => dayjs(x).format("YYYY-MM-DD"))
200-
: undefined,
222+
repeatExcludes:
223+
values.repeatExcludes && values.repeatExcludes.length > 0
224+
? values.repeatExcludes.map((x) => dayjs(x).format("YYYY-MM-DD"))
225+
: [],
201226
repeats: values.repeats ? values.repeats : undefined,
202227
metadata:
203228
Object.keys(values.metadata || {}).length > 0
@@ -301,7 +326,9 @@ export const ManageEventPage: React.FC = () => {
301326

302327
setMetadataKeys(newMetadataKeys);
303328
}, [Object.keys(form.values.metadata || {}).length]);
304-
329+
if (isLoading) {
330+
return <FullScreenLoader />;
331+
}
305332
return (
306333
<AuthGuard
307334
resourceDef={{ service: "core", validRoles: [AppRoles.EVENTS_MANAGER] }}
@@ -328,7 +355,7 @@ export const ManageEventPage: React.FC = () => {
328355
</Alert>
329356
)}
330357

331-
<form onSubmit={form.onSubmit(handleSubmit)}>
358+
<form>
332359
<TextInput
333360
label="Event Title"
334361
withAsterisk
@@ -501,7 +528,12 @@ export const ManageEventPage: React.FC = () => {
501528
)}
502529
</Box>
503530

504-
<Button type="submit" mt="md">
531+
<Button
532+
mt="md"
533+
onClick={() => {
534+
handleSubmit();
535+
}}
536+
>
505537
{isSubmitting ? (
506538
<>
507539
<Loader size={16} color="white" />

0 commit comments

Comments
 (0)