Skip to content

Commit 2f292b3

Browse files
committed
Add integration for contests, and proper error handling for groups as well
1 parent 28dc2ea commit 2f292b3

File tree

3 files changed

+138
-18
lines changed

3 files changed

+138
-18
lines changed

app/[orgId]/contests/page.tsx

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { GenericListing, ColumnDef } from "@/mint/generic-listing";
44
import { GenericEditor, Field } from "@/mint/generic-editor";
55
import { Contest, mockContests } from "./mockData";
66
import { useEffect, useState } from "react";
7+
import { useParams } from "next/navigation";
8+
import { useToast } from "@/hooks/use-toast";
9+
import { formatValidationErrors } from "@/utils/error";
710
import { z } from "zod";
811

912
const columns: ColumnDef<Contest>[] = [
@@ -53,38 +56,113 @@ const injectProblemsCount = (contests: Contest[]) => {
5356
};
5457

5558
export default function ContestsPage() {
59+
const params = useParams();
60+
const orgId = params.orgId as string;
61+
const { toast } = useToast();
62+
5663
const [contests, setContests] = useState<Contest[]>([]);
5764
const [selectedContest, setSelectedContest] = useState<Contest | null>(null);
5865
const [isEditorOpen, setIsEditorOpen] = useState(false);
5966

6067
useEffect(() => {
61-
setContests(injectProblemsCount(mockContests));
62-
}, []);
68+
const fetchContests = async () => {
69+
try {
70+
const response = await fetch(`/api/orgs/${orgId}/contests`);
71+
if (!response.ok) {
72+
const errorData = await response.json().catch(() => ({}));
73+
throw new Error(formatValidationErrors(errorData));
74+
}
75+
const data = await response.json();
76+
setContests(injectProblemsCount(data));
77+
} catch (error) {
78+
console.error("Error fetching contests:", error);
79+
toast({
80+
variant: "destructive",
81+
title: "Error",
82+
description: error instanceof Error ? error.message : "Failed to fetch contests",
83+
});
84+
// Fallback to mock data in case of error
85+
setContests(injectProblemsCount(mockContests));
86+
}
87+
};
88+
fetchContests();
89+
}, [orgId, toast]);
6390

6491
const deleteContest = async (contest: Contest) => {
6592
try {
66-
// Simulate API call
67-
await new Promise((resolve) => setTimeout(resolve, 500));
93+
const response = await fetch(`/api/orgs/${orgId}/contests/${contest.nameId}`, {
94+
method: "DELETE",
95+
});
96+
97+
if (!response.ok) {
98+
const errorData = await response.json().catch(() => ({}));
99+
throw new Error(formatValidationErrors(errorData));
100+
}
68101

69-
// Update the state after successful API call
70102
setContests((prevContests) =>
71103
prevContests.filter((c) => c.id !== contest.id),
72104
);
105+
toast({
106+
title: "Success",
107+
description: "Contest deleted successfully",
108+
});
73109
return Promise.resolve();
74110
} catch (error) {
111+
console.error("Error deleting contest:", error);
112+
toast({
113+
variant: "destructive",
114+
title: "Error",
115+
description: error instanceof Error ? error.message : "Failed to delete contest",
116+
});
75117
return Promise.reject(error);
76118
}
77119
};
78120

79121
const saveContest = async (contest: Contest) => {
80-
if (selectedContest) {
81-
// Update existing contest
82-
setContests(contests.map((c) => (c.id === contest.id ? contest : c)));
83-
} else {
84-
// Add new contest
85-
setContests([...contests, { ...contest, id: Date.now() }]);
122+
try {
123+
const url = selectedContest
124+
? `/api/orgs/${orgId}/contests/${contest.nameId}`
125+
: `/api/orgs/${orgId}/contests`;
126+
127+
const response = await fetch(url, {
128+
method: selectedContest ? "PATCH" : "POST",
129+
headers: {
130+
"Content-Type": "application/json",
131+
},
132+
body: JSON.stringify(contest),
133+
});
134+
135+
if (!response.ok) {
136+
const errorData = await response.json().catch(() => ({}));
137+
throw new Error(formatValidationErrors(errorData));
138+
}
139+
140+
const savedContest = await response.json();
141+
142+
if (selectedContest) {
143+
setContests(contests.map((c) => (c.id === savedContest.id ? savedContest : c)));
144+
toast({
145+
title: "Success",
146+
description: "Contest updated successfully",
147+
});
148+
} else {
149+
setContests([...contests, savedContest]);
150+
toast({
151+
title: "Success",
152+
description: "Contest created successfully",
153+
});
154+
}
155+
156+
setIsEditorOpen(false);
157+
} catch (error) {
158+
console.error("Error saving contest:", error);
159+
toast({
160+
variant: "destructive",
161+
title: "Validation Error",
162+
description: error instanceof Error ? error.message : "Failed to save contest",
163+
});
164+
throw error;
86165
}
87-
setIsEditorOpen(false);
88166
};
89167

90168
return (

app/[orgId]/groups/page.tsx

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { GenericEditor, Field } from "@/mint/generic-editor";
55
import { Group, mockGroups } from "./mockData";
66
import { useEffect, useState } from "react";
77
import { useParams } from "next/navigation";
8+
import { useToast } from "@/hooks/use-toast";
9+
import { formatValidationErrors } from "@/utils/error";
810
import { z } from "zod";
911

1012
const columns: ColumnDef<Group>[] = [
@@ -61,6 +63,7 @@ const fields: Field[] = [
6163
export default function GroupsPage() {
6264
const params = useParams();
6365
const orgId = params.orgId as string;
66+
const { toast } = useToast();
6467

6568
const [groups, setGroups] = useState<Group[]>([]);
6669
const [selectedGroup, setSelectedGroup] = useState<Group | null>(null);
@@ -71,18 +74,24 @@ export default function GroupsPage() {
7174
try {
7275
const response = await fetch(`/api/orgs/${orgId}/groups`);
7376
if (!response.ok) {
74-
throw new Error("Failed to fetch groups");
77+
const errorData = await response.json().catch(() => ({}));
78+
throw new Error(formatValidationErrors(errorData));
7579
}
7680
const data = await response.json();
7781
setGroups(injectUsersCount(data));
7882
} catch (error) {
7983
console.error("Error fetching groups:", error);
84+
toast({
85+
variant: "destructive",
86+
title: "Error",
87+
description: error instanceof Error ? error.message : "Failed to fetch groups",
88+
});
8089
// Fallback to mock data in case of error
8190
setGroups(injectUsersCount(mockGroups));
8291
}
8392
};
8493
fetchGroups();
85-
}, [orgId]);
94+
}, [orgId, toast]);
8695

8796
const deleteGroup = async (group: Group) => {
8897
try {
@@ -91,13 +100,23 @@ export default function GroupsPage() {
91100
});
92101

93102
if (!response.ok) {
94-
throw new Error("Failed to delete group");
103+
const errorData = await response.json().catch(() => ({}));
104+
throw new Error(formatValidationErrors(errorData));
95105
}
96106

97107
setGroups((prevGroups) => prevGroups.filter((g) => g.id !== group.id));
108+
toast({
109+
title: "Success",
110+
description: "Group deleted successfully",
111+
});
98112
return Promise.resolve();
99113
} catch (error) {
100114
console.error("Error deleting group:", error);
115+
toast({
116+
variant: "destructive",
117+
title: "Error",
118+
description: error instanceof Error ? error.message : "Failed to delete group",
119+
});
101120
return Promise.reject(error);
102121
}
103122
};
@@ -117,22 +136,34 @@ export default function GroupsPage() {
117136
});
118137

119138
if (!response.ok) {
120-
throw new Error(
121-
`Failed to ${selectedGroup ? "update" : "create"} group`,
122-
);
139+
const errorData = await response.json().catch(() => ({}));
140+
throw new Error(formatValidationErrors(errorData));
123141
}
124142

125143
const savedGroup = await response.json();
126144

127145
if (selectedGroup) {
128146
setGroups(groups.map((g) => (g.id === savedGroup.id ? savedGroup : g)));
147+
toast({
148+
title: "Success",
149+
description: "Group updated successfully",
150+
});
129151
} else {
130152
setGroups([...groups, savedGroup]);
153+
toast({
154+
title: "Success",
155+
description: "Group created successfully",
156+
});
131157
}
132158

133159
setIsEditorOpen(false);
134160
} catch (error) {
135161
console.error("Error saving group:", error);
162+
toast({
163+
variant: "destructive",
164+
title: "Validation Error",
165+
description: error instanceof Error ? error.message : "Failed to save group",
166+
});
136167
throw error;
137168
}
138169
};

utils/error.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const formatValidationErrors = (errorData: any) => {
2+
if (errorData?.errors && Array.isArray(errorData.errors)) {
3+
return errorData.errors
4+
.map((err: any) => {
5+
const field = err.path?.[0] ? `${err.path[0]}: ` : "";
6+
return `${field}${err.message}`;
7+
})
8+
.join("\n");
9+
}
10+
return "Unknown error occurred";
11+
};

0 commit comments

Comments
 (0)