Skip to content

Commit f281362

Browse files
authored
feat: add ability to delete events and add confirmation dialogs (#35)
…elete, publish
1 parent d721d75 commit f281362

File tree

15 files changed

+278
-144
lines changed

15 files changed

+278
-144
lines changed

apps/api/src/modules/events/route.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ describe("Event route", () => {
257257
});
258258
});
259259

260-
describe("POST /v1/events/:id", () => {
260+
describe("PUT /v1/events/:id", () => {
261261
beforeEach(async () => {
262262
await db.insert(eventsTable).values({
263263
id: "existing-event",
@@ -273,7 +273,7 @@ describe("Event route", () => {
273273
setMockAuth({ userId: null, sessionClaims: null });
274274

275275
const response = await app.inject({
276-
method: "POST",
276+
method: "PUT",
277277
url: "/v1/events/existing-event",
278278
payload: { title: "Updated Title" },
279279
});
@@ -289,7 +289,7 @@ describe("Event route", () => {
289289
});
290290

291291
const response = await app.inject({
292-
method: "POST",
292+
method: "PUT",
293293
url: "/v1/events/existing-event",
294294
payload: { title: "Updated Title" },
295295
});
@@ -304,7 +304,7 @@ describe("Event route", () => {
304304
});
305305

306306
const response = await app.inject({
307-
method: "POST",
307+
method: "PUT",
308308
url: "/v1/events/existing-event",
309309
payload: {
310310
title: "Updated Event Title",
@@ -327,7 +327,7 @@ describe("Event route", () => {
327327
});
328328

329329
const response = await app.inject({
330-
method: "POST",
330+
method: "PUT",
331331
url: "/v1/events/non-existing-event",
332332
payload: { title: "Updated Title" },
333333
});

apps/api/src/modules/events/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const eventRoutes = async (server: FastifyInstance) => {
6565
return reply.status(201).send(newEvent);
6666
});
6767

68-
server.post("/:id", async (request, reply) => {
68+
server.put("/:id", async (request, reply) => {
6969
const { userId, sessionClaims } = getAuth(request);
7070
const role = sessionClaims?.metadata?.role;
7171

apps/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
"sonner": "^2.0.7",
5858
"tailwind-merge": "^3.0.2",
5959
"tailwindcss": "^4.0.6",
60-
"tw-animate-css": "^1.3.6",
6160
"unified": "^11.0.5",
6261
"unist-util-visit": "^5.0.0",
6362
"vite-tsconfig-paths": "^6.0.2",
@@ -75,6 +74,7 @@
7574
"@vitejs/plugin-react": "^5.0.4",
7675
"jsdom": "^27.0.0",
7776
"prettier": "^3.5.3",
77+
"tw-animate-css": "^1.4.0",
7878
"typescript": "^5.7.2",
7979
"vite": "^7.1.7",
8080
"vitest": "^3.0.5",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { useNavigate } from '@tanstack/react-router'
2+
import {
3+
Dialog,
4+
DialogContent,
5+
DialogDescription,
6+
DialogHeader,
7+
DialogTitle,
8+
DialogTrigger,
9+
} from '@/components/ui/dialog.tsx'
10+
import { Button } from '@/components/ui/button.tsx'
11+
import { useDeleteEvent } from '@/lib/hooks/use-delete-event.tsx'
12+
13+
function DeleteEventButton({ eventId }: { eventId: string }) {
14+
const navigate = useNavigate({ from: '/events/$eventId' })
15+
const { deleteEvent, isDeleting } = useDeleteEvent(eventId)
16+
17+
return (
18+
<Dialog>
19+
<DialogTrigger asChild>
20+
<Button variant="secondary">Delete</Button>
21+
</DialogTrigger>
22+
<DialogContent>
23+
<DialogHeader>
24+
<DialogTitle>Delete Event</DialogTitle>
25+
<DialogDescription>
26+
Are you sure you want to delete this event? This action is permanent
27+
and will remove all associated registrations and data.
28+
</DialogDescription>
29+
</DialogHeader>
30+
<div className="flex justify-start gap-3 mt-4">
31+
<Button
32+
disabled={isDeleting}
33+
onClick={() => {
34+
deleteEvent(undefined, {
35+
onSuccess: () => {
36+
void navigate({ to: '/', replace: true })
37+
},
38+
})
39+
}}
40+
>
41+
{isDeleting ? 'Deleting...' : 'Confirm Delete'}
42+
</Button>
43+
</div>
44+
</DialogContent>
45+
</Dialog>
46+
)
47+
}
48+
49+
export default DeleteEventButton
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Button } from '@/components/ui/button.tsx'
2+
import { usePublishEvent } from '@/lib/hooks/use-publish-event.tsx'
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogHeader,
8+
DialogTitle,
9+
DialogTrigger,
10+
} from '@/components/ui/dialog.tsx'
11+
12+
function PublishEventButton({ eventId }: { eventId: string }) {
13+
const { publishEvent, isPublishing } = usePublishEvent(eventId)
14+
15+
return (
16+
<Dialog>
17+
<DialogTrigger asChild>
18+
<Button>Publish</Button>
19+
</DialogTrigger>
20+
<DialogContent>
21+
<DialogHeader>
22+
<DialogTitle>Publish Event</DialogTitle>
23+
<DialogDescription>
24+
Are you sure you want to publish this event? Once published, it will
25+
be visible in the search results and students will be able to
26+
register.
27+
</DialogDescription>
28+
</DialogHeader>
29+
<div className="flex justify-start gap-3 mt-4">
30+
<Button onClick={() => publishEvent()} disabled={isPublishing}>
31+
{isPublishing ? 'Publishing' : 'Publish'}
32+
</Button>
33+
</div>
34+
</DialogContent>
35+
</Dialog>
36+
)
37+
}
38+
39+
export default PublishEventButton
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useState } from 'react'
2+
import type { CustomField } from '@events.comp-soc.com/shared'
3+
import { Button } from '@/components/ui/button.tsx'
4+
import { useCommitteeAuth } from '@/lib/auth.ts'
5+
import EventRegistrationFormDialog from '@/components/forms/event-registration-form-dialog.tsx'
6+
7+
function RegisterEventButton({
8+
form,
9+
title,
10+
}: {
11+
form: Array<CustomField>
12+
title: string
13+
}) {
14+
const [open, setOpen] = useState(false)
15+
const { isAuthenticated } = useCommitteeAuth()
16+
17+
return (
18+
<>
19+
<Button onClick={() => setOpen(!open)} disabled={!isAuthenticated}>
20+
Register Now
21+
</Button>
22+
<EventRegistrationFormDialog
23+
onFormSubmit={() => {}}
24+
formStructure={form}
25+
isOpen={open}
26+
onOpenChange={() => setOpen(!open)}
27+
eventTitle={title}
28+
/>
29+
</>
30+
)
31+
}
32+
33+
export default RegisterEventButton

apps/web/src/components/forms/modify-event-form.tsx

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
} from '@/components/ui/tooltip.tsx'
6060
import { Switch } from '@/components/ui/switch.tsx'
6161
import { ButtonGroup } from '@/components/ui/button-group.tsx'
62+
import { DEFAULT_FIELDS } from '@/config/event-form.ts'
6263

6364
export const EventFormSchema = z
6465
.object({
@@ -102,41 +103,26 @@ export const FormToRequest = EventFormSchema.transform((form) => {
102103
capacity: form.capacity ? parseInt(form.capacity, 10) : null,
103104
date: date.toISOString(),
104105
aboutMarkdown: form.aboutMarkdown || null,
105-
locationURL: form.locationURL ? `https://${form.locationURL}` : null,
106+
locationURL: form.locationURL ? form.locationURL : null,
106107
form: form.registrationFormEnabled ? form.customFields : null,
107108
}
108109
}).pipe(EventContractSchema)
109110

110111
function ModifyEventForm({
111112
onFormSubmit,
113+
onCancel,
112114
defaultValues,
113115
isModify = false,
114116
isLoading = false,
115117
}: {
116118
onFormSubmit: (value: z.infer<typeof EventFormSchema>) => void
117119
defaultValues: z.infer<typeof EventFormSchema>
120+
onCancel?: () => void
118121
isModify?: boolean
119122
isLoading?: boolean
120123
}) {
121124
const [open, setOpen] = useState(false)
122125

123-
const getDefaultCustomFields = (): Array<CustomField> => [
124-
{
125-
id: `field-${Date.now()}-1`,
126-
type: 'select',
127-
label: 'University Year',
128-
required: true,
129-
options: ['1', '2', '3', '4', 'Masters', 'PhD'],
130-
},
131-
{
132-
id: `field-${Date.now()}-2`,
133-
type: 'select',
134-
label: 'Dietary Requirements',
135-
options: ['None', 'Vegetarian', 'Vegan', 'Gluten-Free', 'Halal'],
136-
required: true,
137-
},
138-
]
139-
140126
const form = useForm({
141127
defaultValues,
142128
validators: {
@@ -167,9 +153,8 @@ function ModifyEventForm({
167153
form.setFieldValue('customFields', [...customFields, newField])
168154
}
169155

170-
const addDefaultFields = () => {
171-
const defaultFields = getDefaultCustomFields()
172-
form.setFieldValue('customFields', [...customFields, ...defaultFields])
156+
const setDefaultFields = () => {
157+
form.setFieldValue('customFields', [...DEFAULT_FIELDS])
173158
}
174159

175160
const removeCustomField = (id: string) => {
@@ -613,7 +598,7 @@ function ModifyEventForm({
613598
size="sm"
614599
disabled={isLoading}
615600
variant="outline"
616-
onClick={addDefaultFields}
601+
onClick={setDefaultFields}
617602
>
618603
Default
619604
</Button>
@@ -771,14 +756,26 @@ function ModifyEventForm({
771756
className="flex-col sm:flex-row gap-2 sm:gap-3"
772757
>
773758
{isModify ? (
774-
<Button
775-
type="submit"
776-
form="event-form"
777-
disabled={isLoading}
778-
className="w-full sm:w-auto"
779-
>
780-
{isLoading ? 'Updating' : 'Update'}
781-
</Button>
759+
<>
760+
<Button
761+
type="submit"
762+
form="event-form"
763+
disabled={isLoading}
764+
className="w-full sm:w-auto"
765+
>
766+
{isLoading ? 'Updating' : 'Update'}
767+
</Button>
768+
{onCancel && (
769+
<Button
770+
variant="secondary"
771+
disabled={isLoading}
772+
onClick={onCancel}
773+
className="w-full sm:w-auto"
774+
>
775+
Back
776+
</Button>
777+
)}
778+
</>
782779
) : (
783780
<>
784781
<Button

apps/web/src/config/event-form.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { CustomField } from '@events.comp-soc.com/shared'
2+
3+
export const DEFAULT_FIELDS: Array<CustomField> = [
4+
{
5+
id: `field-${Date.now()}-1`,
6+
type: 'input',
7+
label: 'University Email',
8+
required: true,
9+
},
10+
{
11+
id: `field-${Date.now()}-1`,
12+
type: 'select',
13+
label: 'University Year',
14+
required: true,
15+
options: ['1', '2', '3', '4', 'Masters', 'PhD'],
16+
},
17+
{
18+
id: `field-${Date.now()}-2`,
19+
type: 'select',
20+
label: 'Dietary Requirements',
21+
options: ['None', 'Vegetarian', 'Vegan', 'Gluten-Free', 'Halal'],
22+
required: true,
23+
},
24+
]

apps/web/src/config/mocks.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)