diff --git a/backend/degree/views.py b/backend/degree/views.py index 7aa359b67..a569f8aa6 100644 --- a/backend/degree/views.py +++ b/backend/degree/views.py @@ -85,9 +85,15 @@ def retrieve(self, request, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK) def create(self, request, *args, **kwargs): - if request.data.get("name") is None: + name = request.data.get("name") + if name is None: raise ValidationError({"name": "This field is required."}) - new_degree_plan = DegreePlan(name=request.data.get("name"), person=self.request.user) + if DegreePlan.objects.filter(name=name, person=self.request.user).exists(): + return Response( + {"warning": f"A degree plan with name {name} already exists."}, + status=status.HTTP_409_CONFLICT + ) + new_degree_plan = DegreePlan(name=name, person=self.request.user) new_degree_plan.save() serializer = self.get_serializer(new_degree_plan) return Response(serializer.data, status=status.HTTP_201_CREATED) diff --git a/frontend/degree-plan/components/OnboardingPanels/CreateWithTranscriptPanel.tsx b/frontend/degree-plan/components/OnboardingPanels/CreateWithTranscriptPanel.tsx index 5c3a38978..ef0c53d33 100644 --- a/frontend/degree-plan/components/OnboardingPanels/CreateWithTranscriptPanel.tsx +++ b/frontend/degree-plan/components/OnboardingPanels/CreateWithTranscriptPanel.tsx @@ -11,6 +11,7 @@ import { Column, ColumnsContainer, CourseContainer, + ErrorText, customSelectStylesCourses, customSelectStylesLeft, customSelectStylesRight, @@ -38,7 +39,7 @@ import { interpolateSemesters, } from "@/components/FourYearPlan/Semesters"; import { TRANSFER_CREDIT_SEMESTER_KEY } from "@/constants"; -import { postFetcher, useSWRCrud } from "@/hooks/swrcrud"; +import { postFetcher, getCsrf } from "@/hooks/swrcrud"; import { getMajorOptions } from "@/utils/parseUtils"; type WelcomeLayoutProps = { @@ -77,13 +78,12 @@ export default function CreateWithTranscriptPanel({ const [loading, setLoading] = useState(false); const [name, setName] = useState(""); + const [nameAlreadyExists, setNameAlreadyExists] = useState(false); + const { data: options } = useSWR("/api/options"); const { data: degrees, isLoading: isLoadingDegrees } = useSWR< DegreeListing[] >(`/api/degree/degrees`); - const { create: createDegreeplan } = useSWRCrud( - "/api/degree/degreeplans" - ); // Workaround solution to only input courses once degree has been created and degreeID exists. // Will likely change in the future! @@ -124,8 +124,23 @@ export default function CreateWithTranscriptPanel({ const handleAddDegrees = () => { setLoading(true); - createDegreeplan({ name: name }).then((res) => { - if (res) { + + const createDegreeplan = async () => { + // Need to handle the case where degree plan of same name already exists. + const res = await fetch("/api/degree/degreeplans", { + credentials: "include", + mode: "same-origin", + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": getCsrf(), + "Accept": "application/json", + } as HeadersInit, + body: JSON.stringify({ name: name }), + }); + + if (res.ok) { + const _new = await res.json(); if (startingYear && graduationYear) { const semesters = interpolateSemesters( startingYear.value, @@ -133,17 +148,29 @@ export default function CreateWithTranscriptPanel({ ); semesters[TRANSFER_CREDIT_SEMESTER_KEY] = []; window.localStorage.setItem( - getLocalSemestersKey(res.id), + getLocalSemestersKey(_new.id), JSON.stringify(semesters) ); } - postFetcher(`/api/degree/degreeplans/${res.id}/degrees`, { + postFetcher(`/api/degree/degreeplans/${_new.id}/degrees`, { degree_ids: majors.map((m) => m.value.id), }); // add degree - setActiveDegreeplan(res); - setDegreeID(res.id); + setActiveDegreeplan(_new); + setDegreeID(_new.id); + } else if (res.status === 409) { + // Case where degree plan of same name already exists. + setNameAlreadyExists(true); + setLoading(false); + + setTimeout(() => { + setNameAlreadyExists(false); + }, 5000); + } else { + console.error(await res.text()); } - }); + } + + createDegreeplan().then(() => {}); }; const complete = @@ -203,6 +230,14 @@ export default function CreateWithTranscriptPanel({ onChange={(e) => setName(e.target.value)} placeholder="" /> + + A degree plan with this name already exists. Please choose a different name. + diff --git a/frontend/degree-plan/hooks/swrcrud.ts b/frontend/degree-plan/hooks/swrcrud.ts index 3a68ef180..877b24272 100644 --- a/frontend/degree-plan/hooks/swrcrud.ts +++ b/frontend/degree-plan/hooks/swrcrud.ts @@ -12,7 +12,7 @@ interface SWRCrudError extends Error { /** * @returns {string | boolean} The CSRF token used by the Django REST Framework */ -const getCsrf = (): string | boolean => { +export const getCsrf = (): string | boolean => { const result = document.cookie && document.cookie