Skip to content

Commit 9e7e177

Browse files
authored
Fix event management metadata UI (#388)
Fixes #387 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Internal improvements to metadata field synchronization and management in event editing—no end-user visible changes to functionality or behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 220eef4 commit 9e7e177

File tree

2 files changed

+134
-50
lines changed

2 files changed

+134
-50
lines changed

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

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -180,32 +180,26 @@ const EventFormComponent: React.FC<EventFormProps> = ({
180180
Array<{ id: string; key: string; value: string }>
181181
>([]);
182182

183-
// Sync metadata entries with form values
183+
// Track if we've initialized from form data to avoid re-syncing on user edits
184+
const initializedRef = React.useRef(false);
185+
186+
// Sync metadata entries with form values only when loading/initializing
184187
useEffect(() => {
185188
const currentMetadata = form.values.metadata || {};
186189
const currentKeys = Object.keys(currentMetadata);
187190

188-
// Only update if keys have changed
189-
const entriesKeys = metadataEntries.map((e) => e.key);
190-
const keysChanged =
191-
currentKeys.length !== entriesKeys.length ||
192-
!currentKeys.every((k) => entriesKeys.includes(k));
193-
194-
if (keysChanged) {
191+
// Only sync if we haven't initialized yet and there's metadata to load
192+
if (!initializedRef.current && currentKeys.length > 0) {
195193
setMetadataEntries(
196-
currentKeys.map((key) => {
197-
const existing = metadataEntries.find((e) => e.key === key);
198-
return {
199-
id:
200-
existing?.id ||
201-
`meta-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
202-
key,
203-
value: currentMetadata[key],
204-
};
205-
}),
194+
currentKeys.map((key) => ({
195+
id: `meta-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
196+
key,
197+
value: currentMetadata[key],
198+
})),
206199
);
200+
initializedRef.current = true;
207201
}
208-
}, [Object.keys(form.values.metadata || {}).join(",")]);
202+
}, [form.values.metadata]);
209203

210204
const addMetadataField = () => {
211205
const currentMetadata = { ...form.values.metadata };
@@ -221,49 +215,74 @@ const EventFormComponent: React.FC<EventFormProps> = ({
221215
tempKey = `key${parseInt(tempKey.replace("key", ""), 10) + 1}`;
222216
}
223217

224-
form.setValues({
225-
...form.values,
226-
metadata: {
227-
...currentMetadata,
228-
[tempKey]: "",
229-
},
218+
const newId = `meta-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
219+
220+
// Update both form metadata and entries together
221+
form.setFieldValue("metadata", {
222+
...currentMetadata,
223+
[tempKey]: "",
230224
});
225+
226+
setMetadataEntries([
227+
...metadataEntries,
228+
{ id: newId, key: tempKey, value: "" },
229+
]);
231230
};
232231

233-
const updateMetadataValue = (oldKey: string, value: string) => {
234-
form.setValues({
235-
...form.values,
236-
metadata: {
237-
...form.values.metadata,
238-
[oldKey]: value,
239-
},
232+
const updateMetadataValue = (entryId: string, value: string) => {
233+
const entry = metadataEntries.find((e) => e.id === entryId);
234+
if (!entry) {
235+
return;
236+
}
237+
238+
// Update form metadata
239+
form.setFieldValue("metadata", {
240+
...form.values.metadata,
241+
[entry.key]: value,
240242
});
243+
244+
// Update entry value
245+
setMetadataEntries(
246+
metadataEntries.map((e) => (e.id === entryId ? { ...e, value } : e)),
247+
);
241248
};
242249

243-
const updateMetadataKey = (oldKey: string, newKey: string) => {
244-
if (oldKey === newKey) {
250+
const updateMetadataKey = (entryId: string, newKey: string) => {
251+
const entry = metadataEntries.find((e) => e.id === entryId);
252+
if (!entry || entry.key === newKey) {
245253
return;
246254
}
247255

256+
// Update form metadata
248257
const metadata = { ...form.values.metadata };
249-
const value = metadata[oldKey];
250-
delete metadata[oldKey];
258+
const value = metadata[entry.key];
259+
delete metadata[entry.key];
251260
metadata[newKey] = value;
252261

253-
form.setValues({
254-
...form.values,
255-
metadata,
256-
});
262+
form.setFieldValue("metadata", metadata);
263+
264+
// Update entry key
265+
setMetadataEntries(
266+
metadataEntries.map((e) =>
267+
e.id === entryId ? { ...e, key: newKey } : e,
268+
),
269+
);
257270
};
258271

259-
const removeMetadataField = (key: string) => {
272+
const removeMetadataField = (entryId: string) => {
273+
const entry = metadataEntries.find((e) => e.id === entryId);
274+
if (!entry) {
275+
return;
276+
}
277+
278+
// Update form metadata
260279
const currentMetadata = { ...form.values.metadata };
261-
delete currentMetadata[key];
280+
delete currentMetadata[entry.key];
262281

263-
form.setValues({
264-
...form.values,
265-
metadata: currentMetadata,
266-
});
282+
form.setFieldValue("metadata", currentMetadata);
283+
284+
// Remove entry
285+
setMetadataEntries(metadataEntries.filter((e) => e.id !== entryId));
267286
};
268287

269288
return (
@@ -452,7 +471,7 @@ const EventFormComponent: React.FC<EventFormProps> = ({
452471
<TextInput
453472
label="Key"
454473
value={key}
455-
onChange={(e) => updateMetadataKey(key, e.currentTarget.value)}
474+
onChange={(e) => updateMetadataKey(id, e.currentTarget.value)}
456475
error={keyError}
457476
style={{ flex: 1 }}
458477
/>
@@ -462,7 +481,7 @@ const EventFormComponent: React.FC<EventFormProps> = ({
462481
label="Value"
463482
value={value}
464483
onChange={(e) =>
465-
updateMetadataValue(key, e.currentTarget.value)
484+
updateMetadataValue(id, e.currentTarget.value)
466485
}
467486
error={valueError}
468487
/>
@@ -472,7 +491,7 @@ const EventFormComponent: React.FC<EventFormProps> = ({
472491
<ActionIcon
473492
color="red"
474493
variant="light"
475-
onClick={() => removeMetadataField(key)}
494+
onClick={() => removeMetadataField(id)}
476495
mt={30}
477496
>
478497
<IconTrash size={16} />

tests/e2e/events.spec.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect } from "@playwright/test";
22
import { capitalizeFirstLetter, getUpcomingEvents, test } from "./base.js";
33
import { describe } from "node:test";
44

5-
describe("Events tests", () => {
5+
describe("Events page load test", () => {
66
test("A user can login and view the upcoming events", async ({
77
page,
88
becomeUser,
@@ -50,3 +50,68 @@ describe("Events tests", () => {
5050
expect(page.url()).toEqual("https://core.aws.qa.acmuiuc.org/events/manage");
5151
});
5252
});
53+
54+
test.describe.serial("Event lifecycle test", () => {
55+
const testId = `Events-E2E-${Date.now()}`;
56+
test("A user can create an event", async ({ page, becomeUser }) => {
57+
await becomeUser(page);
58+
await page.locator("a").filter({ hasText: "Events" }).click();
59+
await page.getByRole("button", { name: "Create Event" }).click();
60+
await page.getByRole("tab", { name: "From Scratch" }).click();
61+
await page.getByRole("textbox", { name: "Event Title" }).click();
62+
await page.getByRole("textbox", { name: "Event Title" }).fill(testId);
63+
await page.getByRole("textbox", { name: "Event Description" }).click();
64+
await page
65+
.getByRole("textbox", { name: "Event Description" })
66+
.fill("E2E Testing Event");
67+
await page.getByRole("button", { name: "Start Date & Time" }).click();
68+
await page.getByRole("spinbutton", { name: "--" }).nth(0).click();
69+
await page.getByRole("spinbutton", { name: "--" }).nth(0).fill("023");
70+
await page.getByRole("spinbutton", { name: "--" }).nth(1).click();
71+
await page.getByRole("spinbutton", { name: "--" }).nth(1).fill("030");
72+
await page.getByRole("button").filter({ hasText: /^$/ }).nth(2).click();
73+
await page.getByRole("button", { name: "End Date & Time" }).click();
74+
await page.getByRole("spinbutton", { name: "--" }).first().click();
75+
await page.getByRole("spinbutton", { name: "--" }).first().fill("023");
76+
await page.getByRole("spinbutton", { name: "--" }).nth(1).fill("059");
77+
await page.getByRole("button").filter({ hasText: /^$/ }).nth(2).click();
78+
await page.getByRole("textbox", { name: "Event Location" }).dblclick();
79+
await page
80+
.getByRole("textbox", { name: "Event Location" })
81+
.fill("ACM Room");
82+
await page.getByRole("textbox", { name: "Host" }).click();
83+
await page.getByRole("textbox", { name: "Host" }).fill("Infrastructure");
84+
await page.getByText("Infrastructure Committee").click();
85+
await page.getByRole("textbox", { name: "Paid Event ID" }).click();
86+
await page.getByRole("textbox", { name: "Paid Event ID" }).fill("abcd123");
87+
await page.getByRole("button", { name: "Add Field" }).click();
88+
await page.getByRole("textbox", { name: "Key" }).click();
89+
await page.getByRole("textbox", { name: "Key" }).fill("form1");
90+
await page.getByRole("textbox", { name: "Value" }).click();
91+
await page.getByRole("textbox", { name: "Value" }).fill("value1");
92+
await page.getByRole("button", { name: "Add Field" }).click();
93+
await page.getByRole("textbox", { name: "Key" }).nth(1).fill("form2");
94+
await page.getByRole("textbox", { name: "Value" }).nth(1).click();
95+
await page.getByRole("textbox", { name: "Value" }).nth(1).fill("value2");
96+
await page.getByRole("button", { name: "Create Event" }).click();
97+
});
98+
test("A user can delete an event", async ({ page, becomeUser }) => {
99+
await becomeUser(page);
100+
await page.locator("a").filter({ hasText: "Events" }).click();
101+
const table = page.getByTestId("events-table");
102+
await expect(table).toBeVisible();
103+
const row = table.locator(`tbody tr:has-text("${testId}")`);
104+
await expect(row).toBeVisible();
105+
await row.getByRole("button", { name: "Delete" }).click();
106+
await expect(
107+
page.locator(':text("Are you sure you want to delete the event")'),
108+
).toBeVisible();
109+
await page
110+
.getByRole("dialog")
111+
.getByRole("button", { name: "Delete" })
112+
.click();
113+
await expect(
114+
page.locator(':text("The event was successfully deleted.")'),
115+
).toBeVisible();
116+
});
117+
});

0 commit comments

Comments
 (0)