@@ -14,9 +14,11 @@ import { Button } from "@/components/ui/button";
1414import { MultiSelect } from "@/components/multi-select" ;
1515import LabeledInput from "@/components/ui/LabeledInput" ;
1616import LabeledSelect from "@/components/ui/LabeledSelect" ;
17- import axios from "axios" ;
17+ import axios , { AxiosError } from "axios" ;
1818import toast from "react-hot-toast" ;
1919
20+ type ReportResponse = { error ?: string ; message ?: string } ;
21+
2022interface ReportTagModalProps {
2123 paperId : string ;
2224 subject ?: string ;
@@ -40,8 +42,8 @@ const ReportTagModal = ({
4042 const [ internalOpen , setInternalOpen ] = useState ( false ) ;
4143 const isControlled = open !== undefined && setOpen !== undefined ;
4244
43- const modalOpen = isControlled ? open ! : internalOpen ;
44- const modalSetOpen = isControlled ? setOpen ! : setInternalOpen ;
45+ const modalOpen = isControlled ? open : internalOpen ;
46+ const modalSetOpen = isControlled ? setOpen : setInternalOpen ;
4547 const [ comment , setComment ] = useState ( "" ) ;
4648 const [ email , setEmail ] = useState ( "" ) ;
4749 const [ selectedCategories , setSelectedCategories ] = useState < string [ ] > ( [ ] ) ;
@@ -64,14 +66,14 @@ const ReportTagModal = ({
6466 const isDirty = useMemo ( ( ) => {
6567 if ( selectedCategories . length === 0 ) return false ;
6668 for ( const c of selectedCategories ) {
67- const curr = ( categoryValues [ c ] || "" ) . trim ( ) ;
68- const orig = ( originalCategoryValues [ c ] || "" ) . trim ( ) ;
69+ const curr = ( categoryValues [ c ] ?? "" ) . trim ( ) ;
70+ const orig = ( originalCategoryValues [ c ] ?? "" ) . trim ( ) ;
6971 if ( curr !== orig ) return true ;
7072 }
7173
7274 if ( selectedCategories . includes ( "subject" ) ) {
73- const currCode = ( categoryValues [ " courseCode" ] || "" ) . trim ( ) ;
74- const origCode = ( originalCategoryValues [ " courseCode" ] || "" ) . trim ( ) ;
75+ const currCode = ( categoryValues . courseCode ?? "" ) . trim ( ) ;
76+ const origCode = ( originalCategoryValues . courseCode ?? "" ) . trim ( ) ;
7577 if ( currCode !== origCode ) return true ;
7678 }
7779
@@ -83,7 +85,8 @@ const ReportTagModal = ({
8385 for ( const c of selectedCategories ) {
8486 if ( categoryValues [ c ] ) continue ;
8587 if ( c === "subject" && subject ) {
86- const m = subject . match ( / ^ ( .* ) \s * \[ ( [ ^ \] ] + ) \] \s * $ / ) ;
88+ const subjectRegex = / ^ ( .* ) \s * \[ ( [ ^ \] ] + ) \] \s * $ / ;
89+ const m = subjectRegex . exec ( subject ) ;
8790 if ( m ?. [ 1 ] && m ?. [ 2 ] ) {
8891 const name = m [ 1 ] . trim ( ) ;
8992 const code = m [ 2 ] . trim ( ) ;
@@ -104,17 +107,18 @@ const ReportTagModal = ({
104107 if ( open ) {
105108 const base : Record < string , string > = { } ;
106109 if ( subject ) {
107- const m = subject . match ( / ^ ( .* ) \s * \[ ( [ ^ \] ] + ) \] \s * $ / ) ;
110+ const subjectRegex = / ^ ( .* ) \s * \[ ( [ ^ \] ] + ) \] \s * $ / ;
111+ const m = subjectRegex . exec ( subject ) ;
108112 if ( m ?. [ 1 ] && m ?. [ 2 ] ) {
109- base [ " subject" ] = m [ 1 ] . trim ( ) ;
110- base [ " courseCode" ] = m [ 2 ] . trim ( ) ;
113+ base . subject = m [ 1 ] . trim ( ) ;
114+ base . courseCode = m [ 2 ] . trim ( ) ;
111115 } else {
112- base [ " subject" ] = subject ;
116+ base . subject = subject ;
113117 }
114118 }
115- if ( exam ) base [ " exam" ] = exam ;
116- if ( slot ) base [ " slot" ] = slot ;
117- if ( year ) base [ " year" ] = year ;
119+ if ( exam ) base . exam = exam ;
120+ if ( slot ) base . slot = slot ;
121+ if ( year ) base . year = year ;
118122 setOriginalCategoryValues ( base ) ;
119123 setOriginalEmail ( "" ) ;
120124 } else {
@@ -134,23 +138,23 @@ const ReportTagModal = ({
134138 }
135139
136140 if ( selectedCategories . includes ( "subject" ) ) {
137- const sub = ( categoryValues . subject || "" ) . trim ( ) ;
141+ const sub = ( categoryValues . subject ?? "" ) . trim ( ) ;
138142 if ( ! sub ) {
139143 toast . error ( "Subject name cannot be empty." ) ;
140144 return ;
141145 }
142146}
143147
144148 if ( selectedCategories . includes ( "slot" ) ) {
145- const v = ( categoryValues . slot || "" ) . trim ( ) ;
149+ const v = ( categoryValues . slot ?? "" ) . trim ( ) ;
146150 const slotRegex = / ^ [ A - G ] [ 1 - 2 ] $ / ;
147151 if ( ! slotRegex . test ( v ) ) {
148152 toast . error ( "Slot must be from A1 to G2 (e.g., D1, B2)." ) ;
149153 return ;
150154 }
151155 }
152156 if ( selectedCategories . includes ( "year" ) ) {
153- const y = ( categoryValues . year || "" ) . trim ( ) ;
157+ const y = ( categoryValues . year ?? "" ) . trim ( ) ;
154158 const yearRegex = / ^ \d { 4 } ( - \d { 4 } ) ? $ / ;
155159 if ( ! yearRegex . test ( y ) ) {
156160 toast . error ( "Year must be a valid format (e.g., 2024 or 2024-2025)." ) ;
@@ -180,15 +184,15 @@ const ReportTagModal = ({
180184
181185 for ( const c of selectedCategories ) {
182186 if ( c === "subject" ) {
183- const newSub = ( categoryValues . subject || "" ) . trim ( ) ;
184- const oldSub = ( originalCategoryValues . subject || "" ) . trim ( ) ;
187+ const newSub = ( categoryValues . subject ?? "" ) . trim ( ) ;
188+ const oldSub = ( originalCategoryValues . subject ?? "" ) . trim ( ) ;
185189
186190 if ( newSub !== oldSub ) {
187191 reportedFields . push ( { field : "subject" , value : newSub } ) ;
188192 }
189193
190- const newCode = ( categoryValues . courseCode || "" ) . trim ( ) ;
191- const oldCode = ( originalCategoryValues . courseCode || "" ) . trim ( ) ;
194+ const newCode = ( categoryValues . courseCode ?? "" ) . trim ( ) ;
195+ const oldCode = ( originalCategoryValues . courseCode ?? "" ) . trim ( ) ;
192196
193197 if ( newCode !== oldCode ) {
194198 reportedFields . push ( { field : "courseCode" , value : newCode } ) ;
@@ -197,8 +201,8 @@ const ReportTagModal = ({
197201 continue ;
198202 }
199203
200- const newVal = ( categoryValues [ c ] || "" ) . trim ( ) ;
201- const oldVal = ( originalCategoryValues [ c ] || "" ) . trim ( ) ;
204+ const newVal = ( categoryValues [ c ] ?? "" ) . trim ( ) ;
205+ const oldVal = ( originalCategoryValues [ c ] ?? "" ) . trim ( ) ;
202206
203207 if ( newVal !== oldVal ) {
204208 reportedFields . push ( { field : c , value : newVal } ) ;
@@ -213,20 +217,23 @@ if (reportedFields.length === 0 && comment.trim().length === 0) {
213217 paperId,
214218 reportedFields,
215219 comment,
216- reporterEmail : email || undefined ,
220+ reporterEmail : email ?? undefined ,
217221 } ;
218222 setLoading ( true ) ;
219223 await toast . promise (
220- axios . post ( "/api/report-tag" , payload ) ,
224+ axios . post < ReportResponse > ( "/api/report-tag" , payload ) ,
221225 {
222226 loading : "Submitting your report..." ,
223227 success : "Reported successfully. Thank you, We will work on that" ,
224- error : ( err ) => {
225- return (
226- err ?. response ?. data ?. error ||
227- err ?. message ||
228- "Failed to submit report."
229- )
228+ error : ( err : unknown ) => {
229+ if ( axios . isAxiosError < ReportResponse > ( err ) ) {
230+ return (
231+ err . response ?. data ?. error ??
232+ err . message ??
233+ "Failed to submit report."
234+ ) ;
235+ }
236+ return err instanceof Error ? err . message : "Failed to submit report." ;
230237 } ,
231238 }
232239 )
0 commit comments