Skip to content

Commit b539e6f

Browse files
committed
lets users remove event submissions
1 parent e4b5593 commit b539e6f

File tree

7 files changed

+144
-37
lines changed

7 files changed

+144
-37
lines changed

backend/apps/events/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
path("submissions/", views.get_submissions, name="get_submissions"),
2222
path("submissions/<int:submission_id>/process/", views.process_submission, name="process_submission"),
2323
path("submissions/<int:submission_id>/review/", views.review_submission, name="review_submission"),
24+
path("submissions/<int:submission_id>/", views.delete_submission, name="delete_submission"),
2425
# Test endpoints
2526
path("test-similarity/", views.test_similarity, name="test_similarity"),
2627
]

backend/apps/events/views.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,3 +675,33 @@ def get_user_submissions(request):
675675
return Response(data)
676676
except Exception as e:
677677
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
678+
679+
680+
@api_view(["DELETE"])
681+
@permission_classes([ClerkAuthenticated])
682+
@ratelimit(key="ip", rate="30/hr", block=True)
683+
def delete_submission(request, submission_id):
684+
try:
685+
submission = get_object_or_404(EventSubmission, id=submission_id)
686+
687+
current_user_id = (getattr(request, "clerk_user", None) or {}).get("id")
688+
if not current_user_id or submission.submitted_by != current_user_id:
689+
return Response({"error": "Not authorized to delete this submission"}, status=status.HTTP_403_FORBIDDEN)
690+
691+
if submission.status == "approved":
692+
return Response({
693+
"error": "Approved submissions cannot be removed. Contact support if needed."
694+
}, status=status.HTTP_400_BAD_REQUEST)
695+
696+
# Delete linked event first (cascades to submission)
697+
event = submission.created_event
698+
if event:
699+
event.delete()
700+
return Response({"message": "Submission and linked event removed"}, status=status.HTTP_200_OK)
701+
702+
# Fallback: if no linked event, delete submission directly
703+
submission.delete()
704+
return Response({"message": "Submission removed"}, status=status.HTTP_200_OK)
705+
706+
except Exception as e:
707+
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

backend/scraping/generate_static_data.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212

1313
load_dotenv()
1414

15+
# Ensure this script always uses development settings, even in CI environments
16+
os.environ["PRODUCTION"] = "0"
17+
1518
os.environ.setdefault(
1619
"DJANGO_SETTINGS_MODULE",
1720
os.getenv("DJANGO_SETTINGS_MODULE", "config.settings.development"),
Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,33 @@
1-
import { useQuery } from '@tanstack/react-query';
1+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
22
import { useApi } from '@/shared/hooks/useApi';
33
import { useAuth } from '@clerk/clerk-react';
4+
import type { EventSubmission } from '@/features/events/types/submission';
45

56
export const useUserSubmissions = () => {
67
const { isSignedIn, userId } = useAuth();
78
const { events } = useApi();
9+
const queryClient = useQueryClient();
810

9-
return useQuery({
11+
// Fetch current user's submissions
12+
const submissionsQuery = useQuery<EventSubmission[]>({
1013
queryKey: ['user-submissions', userId],
1114
queryFn: () => events.getUserSubmissions(),
1215
enabled: isSignedIn && !!userId,
1316
staleTime: 30 * 1000, // 30 seconds - submissions don't change often
1417
gcTime: 5 * 60 * 1000, // 5 minutes
1518
});
19+
20+
// Delete a submission owned by the current user
21+
const { mutate: removeSubmission, isPending: isDeleting } = useMutation({
22+
mutationFn: (submissionId: number) => events.deleteSubmission(submissionId),
23+
onSuccess: async () => {
24+
await queryClient.invalidateQueries({ queryKey: ['user-submissions'] });
25+
},
26+
});
27+
28+
return {
29+
...submissionsQuery,
30+
removeSubmission,
31+
isDeleting,
32+
};
1633
};

frontend/src/features/events/pages/MySubmissionsPage.tsx

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,75 @@
1-
import { useUserSubmissions } from '@/features/events/hooks/useUserSubmissions'
2-
import { Card, CardContent, CardHeader, CardTitle } from '@/shared/components/ui/card'
3-
import { Badge } from '@/shared/components/ui/badge'
4-
import { Button } from '@/shared/components/ui/button'
5-
import { Calendar, ExternalLink, Clock, CheckCircle, XCircle, AlertCircle } from 'lucide-react'
6-
import { formatPrettyDate } from '@/shared/lib/dateUtils'
7-
import type { EventSubmission } from '@/features/events/types/submission'
8-
import { useNavigate } from 'react-router-dom'
1+
import { useUserSubmissions } from "@/features/events/hooks/useUserSubmissions";
2+
import {
3+
Card,
4+
CardContent,
5+
CardHeader,
6+
CardTitle,
7+
} from "@/shared/components/ui/card";
8+
import { Badge } from "@/shared/components/ui/badge";
9+
import { Button } from "@/shared/components/ui/button";
10+
import {
11+
Calendar,
12+
ExternalLink,
13+
Clock,
14+
CheckCircle,
15+
XCircle,
16+
AlertCircle,
17+
Trash2,
18+
} from "lucide-react";
19+
import { formatPrettyDate } from "@/shared/lib/dateUtils";
20+
import type { EventSubmission } from "@/features/events/types/submission";
21+
import { useNavigate } from "react-router-dom";
922

1023
export function MySubmissionsPage() {
11-
const { data: submissions = [], isLoading } = useUserSubmissions()
12-
const navigate = useNavigate()
24+
const {
25+
data: submissions = [],
26+
isLoading,
27+
removeSubmission,
28+
isDeleting,
29+
} = useUserSubmissions();
30+
const navigate = useNavigate();
1331
// Type assertion to fix TypeScript issues
14-
const submissionsArray = submissions as EventSubmission[]
32+
const submissionsArray = submissions as EventSubmission[];
1533

1634
const getStatusIcon = (status: string) => {
1735
switch (status) {
18-
case 'approved':
19-
return <CheckCircle className="h-4 w-4 text-green-600" />
20-
case 'rejected':
21-
return <XCircle className="h-4 w-4 text-red-600" />
22-
case 'pending':
23-
return <AlertCircle className="h-4 w-4 text-yellow-600" />
36+
case "approved":
37+
return <CheckCircle className="h-4 w-4 text-green-600" />;
38+
case "rejected":
39+
return <XCircle className="h-4 w-4 text-red-600" />;
40+
case "pending":
41+
return <AlertCircle className="h-4 w-4 text-yellow-600" />;
2442
default:
25-
return <AlertCircle className="h-4 w-4 text-gray-600" />
43+
return <AlertCircle className="h-4 w-4 text-gray-600" />;
2644
}
27-
}
45+
};
2846

2947
const getStatusBadgeVariant = (status: string) => {
3048
switch (status) {
31-
case 'approved':
32-
return 'default' as const
33-
case 'rejected':
34-
return 'destructive' as const
35-
case 'pending':
36-
return 'secondary' as const
49+
case "approved":
50+
return "default" as const;
51+
case "rejected":
52+
return "destructive" as const;
53+
case "pending":
54+
return "secondary" as const;
3755
default:
38-
return 'outline' as const
56+
return "outline" as const;
3957
}
40-
}
58+
};
4159

4260
if (isLoading) {
4361
return (
4462
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-12 px-4 sm:px-6 lg:px-8">
4563
<div className="max-w-6xl mx-auto">
4664
<div className="text-center py-12">
4765
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
48-
<p className="mt-4 text-gray-600 dark:text-gray-400">Loading your submissions...</p>
66+
<p className="mt-4 text-gray-600 dark:text-gray-400">
67+
Loading your submissions...
68+
</p>
4969
</div>
5070
</div>
5171
</div>
52-
)
72+
);
5373
}
5474

5575
return (
@@ -77,7 +97,7 @@ export function MySubmissionsPage() {
7797
<p className="text-gray-600 dark:text-gray-400 mb-6">
7898
Submit your first event to get started!
7999
</p>
80-
<Button onClick={() => navigate('/submit')}>
100+
<Button onClick={() => navigate("/submit")}>
81101
Submit Event
82102
</Button>
83103
</div>
@@ -86,12 +106,17 @@ export function MySubmissionsPage() {
86106
) : (
87107
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
88108
{submissionsArray.map((submission) => (
89-
<Card key={submission.id} className="hover:shadow-lg transition-shadow">
109+
<Card
110+
key={submission.id}
111+
className="hover:shadow-lg transition-shadow"
112+
>
90113
<CardHeader className="pb-3">
91114
<div className="flex items-start justify-between">
92115
<div className="flex items-center gap-2">
93116
{getStatusIcon(submission.status)}
94-
<CardTitle className="text-lg">Submission #{submission.id}</CardTitle>
117+
<CardTitle className="text-lg">
118+
Submission #{submission.id}
119+
</CardTitle>
95120
</div>
96121
<Badge variant={getStatusBadgeVariant(submission.status)}>
97122
{submission.status}
@@ -162,19 +187,42 @@ export function MySubmissionsPage() {
162187
<Button
163188
variant="outline"
164189
size="sm"
165-
onClick={() => navigate(`/events/${submission.created_event_id}`)}
190+
onClick={() =>
191+
navigate(`/events/${submission.created_event_id}`)
192+
}
166193
className="w-full"
167194
>
168195
View Created Event
169196
</Button>
170197
</div>
171198
)}
199+
200+
{/* Remove Button (only if not approved) */}
201+
{submission.status !== "approved" && (
202+
<Button
203+
variant="destructive"
204+
size="sm"
205+
className="w-full"
206+
disabled={isDeleting}
207+
onClick={() => {
208+
const confirmed = window.confirm(
209+
"Remove this submission? If it created an event, that event will also be removed. This cannot be undone."
210+
);
211+
if (confirmed) {
212+
removeSubmission(submission.id);
213+
}
214+
}}
215+
>
216+
<Trash2 className="h-4 w-4 mr-2" />
217+
Remove Submission
218+
</Button>
219+
)}
172220
</CardContent>
173221
</Card>
174222
))}
175223
</div>
176224
)}
177225
</div>
178226
</div>
179-
)
227+
);
180228
}

frontend/src/features/events/pages/SubmitEventPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export function SubmitEventPage() {
136136
<Input
137137
id="source_url"
138138
type="url"
139-
placeholder="https://example.com/event-page"
139+
placeholder="https://wusa.ca/event/watch-party"
140140
className="h-12 text-base"
141141
{...register('source_url')}
142142
disabled={isLoading}

frontend/src/shared/api/EventsAPIClient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ class EventsAPIClient {
142142
);
143143
}
144144

145+
/**
146+
* Deletes a user's own submission.
147+
* Corresponds to a DELETE request to /api/events/submissions/{id}/
148+
*/
149+
async deleteSubmission(submissionId: number): Promise<{ message: string }> {
150+
return this.apiClient.delete(`events/submissions/${submissionId}/`);
151+
}
152+
145153
/**
146154
* Exports events as ICS file.
147155
* Corresponds to a GET request to /api/events/export/ics/

0 commit comments

Comments
 (0)