@@ -7,11 +7,13 @@ import type {
77 SchoolOption ,
88} from "@/types" ;
99import React , { useState , useEffect } from "react" ;
10- import { deleteFetcher , postFetcher , useSWRCrud } from "@/hooks/swrcrud" ;
10+ import { deleteFetcher , postFetcher , useSWRCrud , getCsrf , normalizeFinalSlash } from "@/hooks/swrcrud" ;
1111import useSWR , { useSWRConfig } from "swr" ;
1212import ModalContainer from "../common/ModalContainer" ;
1313import Select from "react-select" ;
1414import { schoolOptions } from "@/components/OnboardingPanels/SharedComponents" ;
15+ import { ErrorText } from "@/components/OnboardingPanels/SharedComponents" ;
16+ import { assertValueType } from "@/types" ;
1517
1618export type ModalKey =
1719 | "plan-create"
@@ -157,7 +159,6 @@ const ModalInterior = ({
157159} : ModalInteriorProps ) => {
158160 const {
159161 create : createDegreeplan ,
160- update : updateDegreeplan ,
161162 remove : deleteDegreeplan ,
162163 } = useSWRCrud < DegreePlan > ( "/api/degree/degreeplans" ) ;
163164
@@ -222,6 +223,85 @@ const ModalInterior = ({
222223 await mutate ( `/api/degree/degreeplans/${ degreeplanId } ` ) ; // use updated degree plan returned
223224 } ;
224225
226+
227+
228+ // Update degree plan handling error case where degree plan of same name already exists.
229+ const [ sameNameError , setSameNameError ] = useState ( false ) ;
230+
231+ const updateDegreeplanWithErrorHandling = async ( updatedData : Partial < DegreePlan > , id : number | string | null ) => {
232+ if ( ! id ) return ;
233+
234+ const key = normalizeFinalSlash ( `/api/degree/degreeplans/${ id } ` ) ;
235+ const res = await fetch ( key , {
236+ credentials : "include" ,
237+ mode : "same-origin" ,
238+ method : "PATCH" ,
239+ headers : {
240+ "Content-Type" : "application/json" ,
241+ "X-CSRFToken" : getCsrf ( ) ,
242+ "Accept" : "application/json" ,
243+ } as HeadersInit ,
244+ body : JSON . stringify ( { name : name } ) ,
245+ } ) ;
246+
247+ if ( res . ok ) {
248+ const updated = await res . json ( ) ;
249+
250+ // Handle mutation
251+ // Code adapted from swrcrud.ts
252+ const idKey = "id" as keyof DegreePlan ;
253+
254+ mutate ( key , updated , {
255+ optimisticData : ( data ?: DegreePlan ) => {
256+ const optimistic = { ...data , ...updatedData } as DegreePlan ;
257+ assertValueType ( optimistic , idKey , id )
258+ optimistic . id = Number ( id ) ; // does this work?
259+ return ( { id, ...data , ...updatedData } as DegreePlan )
260+ } ,
261+ revalidate : false ,
262+ throwOnError : false
263+ } )
264+
265+ const endpoint = "/api/degree/degreeplans" ;
266+ mutate ( endpoint , updated , {
267+ optimisticData : ( list ?: Array < DegreePlan > ) => {
268+ if ( ! list ) return [ ] ;
269+ const index = list . findIndex ( ( item : DegreePlan ) => String ( item [ idKey ] ) === id ) ;
270+ if ( index === - 1 ) {
271+ mutate ( endpoint ) // trigger revalidation
272+ return list ;
273+ }
274+ list . splice ( index , 1 , { ...list [ index ] , ...updatedData } ) ;
275+ return list ;
276+ } ,
277+ populateCache : ( updated : DegreePlan , list ?: Array < DegreePlan > ) => {
278+ if ( ! list ) return [ ] ;
279+ if ( ! updated ) return list ;
280+ const index = list . findIndex ( ( item : DegreePlan ) => item [ idKey ] === updated [ idKey ] ) ;
281+ if ( index === - 1 ) {
282+ console . warn ( "swrcrud: update: updated element not found in list view" ) ;
283+ mutate ( endpoint ) ; // trigger revalidation
284+ return list ;
285+ }
286+ list . splice ( index , 1 , updated ) ;
287+ return list
288+ } ,
289+ revalidate : false ,
290+ throwOnError : false
291+ } )
292+
293+ close ( ) ; // only close if update is successful
294+ } else if ( res . status === 409 ) {
295+ setSameNameError ( true ) ;
296+
297+ setTimeout ( ( ) => {
298+ setSameNameError ( false ) ;
299+ } , 5000 ) ;
300+ } else {
301+ throw new Error ( await res . text ( ) ) ;
302+ }
303+ } ;
304+
225305 switch ( modalKey ) {
226306 case "plan-create" :
227307 return (
@@ -262,18 +342,20 @@ const ModalInterior = ({
262342 "id" in modalObject &&
263343 "name" in modalObject
264344 ) {
265- updateDegreeplan ( { name } , modalObject . id ) ;
345+ updateDegreeplanWithErrorHandling ( { name : name } , modalObject . id ) ;
266346 if ( modalObject . id == activeDegreePlan ?. id ) {
267347 let newNameDegPlan = modalObject ;
268348 newNameDegPlan . name = name ;
269349 setActiveDegreeplan ( newNameDegPlan ) ;
270350 }
351+ } else {
352+ close ( ) ;
271353 }
272- close ( ) ;
273354 } }
274355 >
275356 Rename
276357 </ ModalButton >
358+ { sameNameError && < ErrorText style = { { color : "red" } } > A degree plan with this name already exists.</ ErrorText > }
277359 </ ModalInteriorWrapper >
278360 ) ;
279361 case "plan-remove" :
0 commit comments