{"version":3,"file":"index.4e9fcaad.js","sources":["../../vite/modulepreload-polyfill","../../src/assets/home-header.webp","../../src/assets/home-curve-transition.svg","../../src/assets/star.svg","../../src/assets/worst-of-worst-background.webp","../../src/App.config.ts","../../src/services/auth.service.ts","../../src/services/http.service.ts","../../src/services/logger.service.ts","../../src/services/review.service.ts","../../src/utils/getRandomSubarray.ts","../../src/utils/intersectingDbEntities.ts","../../src/services/teacher.service.ts","../../src/services/index.ts","../../src/components/Backdrop.tsx","../../src/constants/departments.ts","../../src/hooks/useAuth.ts","../../src/hooks/useObservable.ts","../../src/hooks/useProtectedRoute.ts","../../src/hooks/useService.ts","../../src/hooks/useQuery.ts","../../src/hooks/useWindowSize.ts","../../src/hooks/useTailwindBreakpoints.ts","../../src/components/EvaluateTeacherForm.tsx","../../src/components/SearchBar.tsx","../../src/assets/Logo.png","../../src/components/Navbar.tsx","../../src/components/NewTeacherForm.tsx","../../src/components/TeacherCard.tsx","../../src/components/MinMaxSlider.tsx","../../src/components/Filters.tsx","../../src/components/ClassSection.tsx","../../src/components/AutoComplete.tsx","../../src/pages/Home.tsx","../../src/pages/Login.tsx","../../src/pages/NewTeacher.tsx","../../src/pages/Search.tsx","../../src/pages/TeacherPage.tsx","../../src/pages/About.tsx","../../src/App.tsx","../../src/main.tsx"],"sourcesContent":["const p = function polyfill() {\n const relList = document.createElement('link').relList;\n if (relList && relList.supports && relList.supports('modulepreload')) {\n return;\n }\n for (const link of document.querySelectorAll('link[rel=\"modulepreload\"]')) {\n processPreload(link);\n }\n new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== 'childList') {\n continue;\n }\n for (const node of mutation.addedNodes) {\n if (node.tagName === 'LINK' && node.rel === 'modulepreload')\n processPreload(node);\n }\n }\n }).observe(document, { childList: true, subtree: true });\n function getFetchOpts(script) {\n const fetchOpts = {};\n if (script.integrity)\n fetchOpts.integrity = script.integrity;\n if (script.referrerpolicy)\n fetchOpts.referrerPolicy = script.referrerpolicy;\n if (script.crossorigin === 'use-credentials')\n fetchOpts.credentials = 'include';\n else if (script.crossorigin === 'anonymous')\n fetchOpts.credentials = 'omit';\n else\n fetchOpts.credentials = 'same-origin';\n return fetchOpts;\n }\n function processPreload(link) {\n if (link.ep)\n // ep marker = processed\n return;\n link.ep = true;\n // prepopulate the load record\n const fetchOpts = getFetchOpts(link);\n fetch(link.href, fetchOpts);\n }\n};__VITE_IS_MODERN__&&p();","export default \"__VITE_ASSET__6c92036b__\"","export default \"__VITE_ASSET__e73e0055__\"","export default \"__VITE_ASSET__7e79bd14__\"","export default \"__VITE_ASSET__3f2466ee__\"","interface AppConfiguration {\n remoteUrl: string;\n base: string;\n}\n\nconst devConfig: AppConfiguration = {\n remoteUrl: 'https:///sunder.polyratings.dev',\n base: '/',\n};\n\nconst prodConfig: AppConfiguration = {\n remoteUrl: 'https://api-beta.polyratings.dev',\n base: '/',\n};\n\nconst githubPagesConfig: AppConfiguration = {\n remoteUrl: 'https://api-beta.polyratings.dev',\n base: '/polyratings-revamp/',\n};\n\nconst regularConfig = process.env.NODE_ENV === 'development' ? devConfig : prodConfig;\n\nexport const config = window.location.href.includes('github.io')\n ? githubPagesConfig\n : regularConfig;\n","import { BehaviorSubject } from 'rxjs';\nimport { JwtAuthResponse, UserToken } from '@polyratings/shared';\nimport jwtDecode from 'jwt-decode';\nimport { config } from '@/App.config';\n\nconst USER_LOCAL_STORAGE_KEY = 'user';\n\nexport class AuthService {\n private jwtToken: string | null = null;\n\n public isAuthenticatedSubject = new BehaviorSubject(null);\n\n constructor(private storage: Storage, private fetch: typeof window.fetch) {\n const jwt = storage.getItem(USER_LOCAL_STORAGE_KEY) as string | null;\n if (jwt) {\n this.setAuthState(jwt);\n }\n }\n\n public getJwt(): string | null {\n return this.jwtToken;\n }\n\n public getUser(): UserToken | null {\n return this.jwtToken ? jwtDecode(this.jwtToken) : null;\n }\n\n public async login(username: string, password: string): Promise {\n const loginRes = await this.fetch(`${config.remoteUrl}/login`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ username, password }),\n });\n \n if(loginRes.status >= 300) {\n const errorPayload = await loginRes.json()\n throw errorPayload.message\n }\n\n const loginBody = (await loginRes.json()) as JwtAuthResponse;\n const jwt = loginBody.accessToken;\n\n // We know that this is a valid user since we just got a jwt\n return this.setAuthState(jwt) as UserToken;\n }\n\n public signOut() {\n this.setAuthState(null);\n }\n\n private setAuthState(jwtToken: string | null): UserToken | null {\n this.jwtToken = jwtToken;\n const user = this.getUser();\n this.isAuthenticatedSubject.next(user);\n if (jwtToken) {\n this.storage.setItem(USER_LOCAL_STORAGE_KEY, jwtToken);\n } else {\n this.storage.removeItem(USER_LOCAL_STORAGE_KEY);\n }\n return user;\n }\n}\n","import { AuthService } from './auth.service';\n\nexport class HttpService {\n constructor(private authService: AuthService, private globalFetch: typeof window.fetch) {}\n\n async fetch(input: string, init: RequestInit = {}): Promise {\n init.headers = init.headers || {};\n const jwt = this.authService.getJwt();\n if (jwt) {\n // @ts-expect-error error since can't normally index header object. The way that its going to be used will be fine though\n init.headers.Authorization = `Bearer ${jwt}`;\n }\n const res = await this.globalFetch(input, init);\n if (res.status === 401) {\n // TODO: Find a way to do this cleaner\n this.authService.signOut();\n const LOGIN_ROUTE = '/login';\n if (window.location.pathname !== LOGIN_ROUTE) {\n window.location.replace(LOGIN_ROUTE);\n }\n }\n if (res.status >= 300) {\n const errorPayload = await res.json()\n throw errorPayload.message\n }\n return res;\n }\n}\n","/* eslint-disable no-console */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable class-methods-use-this */\n\nexport class Logger {\n info(...args: any[]) {\n console.log(...args);\n }\n\n warn(...args: any[]) {\n console.warn(...args);\n }\n\n error(...args: any[]) {\n console.error(...args);\n }\n\n debug(...args: any[]) {\n console.debug(...args);\n }\n}\n","import { Teacher, AddReview } from '@polyratings/shared';\nimport { config } from '@/App.config';\nimport { HttpService } from './http.service';\n\nexport class ReviewService {\n constructor(private httpService: HttpService) {}\n\n async uploadReview(newReview: AddReview): Promise {\n const res = await this.httpService.fetch(`${config.remoteUrl}/review`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(newReview),\n });\n return res.json();\n }\n}\n","/* eslint-disable no-plusplus */\nexport function getRandomSubarray(arr: T[], size: number): T[] {\n const shuffled = arr.slice(0);\n let i = arr.length;\n const min = i - size;\n let temp;\n let index;\n while (i-- > min) {\n index = Math.floor((i + 1) * Math.random());\n temp = shuffled[index];\n shuffled[index] = shuffled[i];\n shuffled[i] = temp;\n }\n return shuffled.slice(min);\n}\n","import { DbEntryProperties } from '@polyratings/shared';\n\nexport function intersectingDbEntities(\n arrays: T[][],\n): { intersect: T[]; nonIntersect: T[] } {\n if (arrays.length === 1) {\n return {\n intersect: arrays[0],\n nonIntersect: [],\n };\n }\n const idToEntity = arrays.flat().reduce((acc: { [id: string]: T }, curr) => {\n acc[curr.id] = curr;\n return acc;\n }, {});\n const idArrays = arrays.map((arr) => arr.map((x) => x.id));\n let intersectionSet = new Set(idArrays[0]);\n idArrays.slice(1).forEach((array) => {\n const compareSet = new Set(array);\n intersectionSet = new Set([...intersectionSet].filter((x) => compareSet.has(x)));\n });\n\n const nonIntersect = arrays.flat().filter((x) => !intersectionSet.has(x.id));\n\n return {\n intersect: Array.from(intersectionSet).map((id) => idToEntity[id]),\n nonIntersect,\n };\n}\n","import { Teacher, TeacherIdResponse } from '@polyratings/shared';\nimport { config } from '@/App.config';\nimport { getRandomSubarray, intersectingDbEntities } from '@/utils';\nimport { HttpService } from './http.service';\n\nexport const TEACHER_CACHE_TIME = 1000 * 60 * 10;\nconst ALL_TEACHER_CACHE_KEY = 'ALL_TEACHERS';\nconst INDIVIDUAL_TEACHER_CACHE_KEY = 'TEACHERS';\n\ninterface TeacherCacheEntry {\n exp: number;\n teacher: Teacher;\n}\n\nexport type TeacherSearchType = 'name' | 'department' | 'class';\n\nexport class TeacherService {\n private allTeachers: Promise;\n\n private teacherCache: { [id: string]: TeacherCacheEntry };\n\n constructor(private httpService: HttpService, private storage: Storage) {\n const individualTeacherCacheStr = this.storage.getItem(INDIVIDUAL_TEACHER_CACHE_KEY);\n this.teacherCache = individualTeacherCacheStr ? JSON.parse(individualTeacherCacheStr) : {};\n\n const cachedAllTeacherCacheStr = this.storage.getItem(ALL_TEACHER_CACHE_KEY);\n if (cachedAllTeacherCacheStr) {\n const allTeacherCache: { exp: number; data: Teacher[] } =\n JSON.parse(cachedAllTeacherCacheStr);\n if (Date.now() < allTeacherCache.exp) {\n // List has not expired\n this.allTeachers = Promise.resolve(allTeacherCache.data);\n // Return early no need to fetch the teacher list\n return;\n }\n }\n\n this.allTeachers = (async () => {\n const res = await this.httpService.fetch(`${config.remoteUrl}/professors`);\n const data = await res.json();\n this.storage.setItem(\n ALL_TEACHER_CACHE_KEY,\n JSON.stringify({\n exp: Date.now() + TEACHER_CACHE_TIME,\n data,\n }),\n );\n return data;\n })();\n }\n\n public async getRandomBestTeacher(): Promise {\n const allTeachers = await this.allTeachers;\n const rankedTeachers = allTeachers\n .filter((t) => t.numEvals > 10)\n .sort((a, b) => b.overallRating - a.overallRating);\n return getRandomSubarray(rankedTeachers.slice(0, 30), 1)[0];\n }\n\n public async getRandomWorstTeachers(): Promise {\n const allTeachers = await this.allTeachers;\n const rankedTeachers = allTeachers\n .filter((t) => t.numEvals > 10)\n .sort((a, b) => a.overallRating - b.overallRating);\n return getRandomSubarray(rankedTeachers.slice(0, 30), 6);\n }\n\n public async getTeacher(id: string): Promise {\n if (this.teacherCache[id]) {\n if (Date.now() < this.teacherCache[id].exp) {\n return this.teacherCache[id].teacher;\n }\n this.removeTeacherFromCache(id);\n }\n\n const res = await this.httpService.fetch(`${config.remoteUrl}/professors/${id}`);\n\n const teacher: Teacher = await res.json();\n // Make sure reviews are in dated order\n Object.values(teacher.reviews ?? []).forEach((reviewArr) =>\n reviewArr.sort((a, b) => Date.parse(b.postDate.toString()) - Date.parse(a.postDate.toString())),\n );\n this.addTeacherToCache(teacher);\n return teacher;\n }\n\n public async searchForTeacher(type: TeacherSearchType, value: string): Promise {\n const allTeachers = await this.allTeachers;\n\n switch (type) {\n case 'name': {\n const tokens = value.toLowerCase().split(' ');\n const tokenMatches = tokens.map((token) =>\n allTeachers.filter((teacher) =>\n `${teacher.lastName}, ${teacher.firstName}`.toLowerCase().includes(token),\n ),\n );\n const { intersect, nonIntersect } = intersectingDbEntities(tokenMatches);\n return [...intersect, ...nonIntersect];\n }\n case 'class': {\n const courseName = value.toUpperCase();\n // use includes to possibly be more lenient\n return allTeachers.filter((teacher) =>\n teacher.courses.find((course) => course.includes(courseName)),\n );\n }\n case 'department': {\n const department = value.toUpperCase();\n // Use starts with since most times with department you are looking for an exact match\n return allTeachers.filter((teacher) => teacher.department.startsWith(department));\n }\n default:\n throw new Error(`Invalid Search Type: ${type}`);\n }\n }\n\n public async getAllTeachers(): Promise {\n return this.allTeachers;\n }\n\n public async addNewTeacher(newTeacher: Teacher): Promise {\n const res = await this.httpService.fetch(`${config.remoteUrl}/teacher`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(newTeacher),\n });\n const teacherIdResponse = (await res.json()) as TeacherIdResponse;\n return teacherIdResponse.teacherId;\n }\n\n private addTeacherToCache(teacher: Teacher) {\n this.teacherCache[teacher.id] = {\n teacher,\n exp: Date.now() + TEACHER_CACHE_TIME,\n };\n this.storage.setItem(INDIVIDUAL_TEACHER_CACHE_KEY, JSON.stringify(this.teacherCache));\n }\n\n private removeTeacherFromCache(id: string) {\n delete this.teacherCache[id];\n this.storage.setItem(INDIVIDUAL_TEACHER_CACHE_KEY, JSON.stringify(this.teacherCache));\n }\n}\n","import { DependencyInjector, InjectionToken, makeInjector } from '@mindspace-io/react';\nimport { AuthService } from './auth.service';\nimport { HttpService } from './http.service';\nimport { Logger } from './logger.service';\nimport { ReviewService } from './review.service';\nimport { TeacherService } from './teacher.service';\n\nexport const LOCAL_STORAGE = new InjectionToken('local-storage');\nexport const FETCH = new InjectionToken('fetch');\n\nexport const injector: DependencyInjector = injectorFactory();\n\nexport function injectorFactory() {\n return makeInjector([\n { provide: LOCAL_STORAGE, useValue: window.localStorage },\n { provide: FETCH, useFactory: () => window.fetch.bind(window) },\n { provide: AuthService, useClass: AuthService, deps: [LOCAL_STORAGE, FETCH] },\n { provide: HttpService, useClass: HttpService, deps: [AuthService, FETCH] },\n { provide: TeacherService, useClass: TeacherService, deps: [HttpService, LOCAL_STORAGE] },\n { provide: ReviewService, useClass: ReviewService, deps: [HttpService] },\n { provide: Logger, useClass: Logger },\n ]);\n}\n\nexport { AuthService, HttpService, TeacherService, ReviewService, Logger };\n","import { ReactNode, useEffect } from 'react';\n\nexport function Backdrop({ children }: { children: ReactNode }) {\n window.scrollTo(0, 0);\n\n // Fix scroll position\n useEffect(() => {\n document.body.style.height = '100vh';\n document.body.style.overflowY = 'hidden';\n return () => {\n document.body.style.height = 'auto';\n document.body.style.overflowY = 'auto';\n };\n }, []);\n\n return (\n \n {children}\n \n );\n}\n","export const departments = [\n 'AERO',\n 'BIO',\n 'IT',\n 'CHEM',\n 'MATH',\n 'ARCH',\n 'ENGL',\n 'PHIL',\n 'AGB',\n 'ASCI',\n 'ART',\n 'EE',\n 'BUS',\n 'PHYS',\n 'SOC',\n 'CSC',\n 'JOUR',\n 'FSN',\n 'IME',\n 'DSCI',\n 'POLS',\n 'SS',\n 'CE',\n 'TH',\n 'ARCE',\n 'LA',\n 'MU',\n 'ME',\n 'COMS',\n 'NRM',\n 'STAT',\n 'ECON',\n 'EDUC',\n 'HIST',\n 'CPE',\n 'CRSC',\n 'PSY',\n 'WGS',\n 'CRP',\n 'ES',\n 'MSC',\n 'EHS',\n 'AGED',\n 'HUM',\n 'BRAE',\n 'GRC',\n 'MATE',\n 'LS',\n 'CM',\n 'PE',\n 'ENGR',\n 'BMED',\n 'SCI',\n 'GBA',\n 'WS',\n].sort();\n","import { AuthService } from '@/services';\nimport { useService, useObservable } from '.';\n\nexport function useAuth() {\n const authService = useService(AuthService);\n const isAuthenticated = useObservable(authService.isAuthenticatedSubject, authService.getUser());\n return isAuthenticated;\n}\n","import { useEffect, useState } from 'react';\nimport { Observable } from 'rxjs';\n\nexport function useObservable(observable: Observable, initial: T) {\n const [value, setValue] = useState(initial);\n useEffect(() => {\n const sub = observable.subscribe(setValue);\n return () => {\n sub.unsubscribe();\n };\n }, []);\n return value;\n}\n","import { useEffect } from 'react';\nimport { useHistory } from 'react-router-dom';\nimport { toast } from 'react-toastify';\nimport { UserToken } from '@polyratings/shared';\nimport { useAuth } from './useAuth';\n\nexport function useProtectedRoute(\n authenticated: B,\n redirect: string,\n toastMessage?: (user: B extends false ? UserToken : null) => string,\n) {\n // Redirect to home if logged in\n const user = useAuth();\n const history = useHistory();\n useEffect(() => {\n if (authenticated === !user) {\n if (toastMessage) {\n // Typescript can not properly deduce that the type has to be User\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n toast.info(toastMessage(user as any));\n }\n history.replace(redirect);\n }\n }, [user]);\n}\n","import { useInjectorHook } from '@mindspace-io/react';\nimport { injector } from '@/services';\n\ntype Constructs = new (...args: never[]) => T;\n\nexport function useService(token: Constructs): T {\n const [service] = useInjectorHook(token, injector);\n return service;\n}\n","import { useMemo } from 'react';\nimport { useLocation } from 'react-router-dom';\n\n// From: https://v5.reactrouter.com/web/example/query-parameters\n// A custom hook that builds on useLocation to parse\n// the query string for you.\nexport function useQuery() {\n const { search } = useLocation();\n\n return useMemo(() => new URLSearchParams(search), [search]);\n}\n","import { useState, useEffect } from 'react';\n\nexport interface Size {\n width: number;\n height: number;\n}\n\n// From https://usehooks.com/useWindowSize/ used to bind to the window size\nexport function useWindowSize(): Size {\n const [windowSize, setWindowSize] = useState({\n width: 0,\n height: 0,\n });\n\n useEffect(() => {\n function handleResize() {\n setWindowSize({\n width: window.innerWidth,\n height: window.innerHeight,\n });\n }\n\n // Add event listener\n window.addEventListener('resize', handleResize);\n\n // Call handler right away so state gets updated with initial window size\n handleResize();\n\n // Remove event listener on cleanup\n return () => window.removeEventListener('resize', handleResize);\n }, []);\n\n return windowSize;\n}\n","import { useEffect, useState } from 'react';\nimport { useWindowSize } from './useWindowSize';\n\nexport interface TailwindBreakpoints {\n sm?: T;\n md?: T;\n lg?: T;\n xl?: T;\n '2xl'?: T;\n}\n\nconst breakpointRanges: TailwindBreakpoints<[number, number]> = {\n sm: [640, 768],\n md: [768, 1024],\n lg: [1024, 1280],\n xl: [1280, 1536],\n '2xl': [1536, Infinity],\n};\n\ntype breakpointRangeEntry = [keyof typeof breakpointRanges, [number, number]];\n\nexport function useTailwindBreakpoint(breakpoints: TailwindBreakpoints, defaultValue: T): T {\n const windowSize = useWindowSize();\n const [outputValue, setOutputValue] = useState(defaultValue);\n const internalValues: TailwindBreakpoints = {};\n internalValues.sm = breakpoints.sm ?? defaultValue;\n internalValues.md = breakpoints.md ?? internalValues.sm;\n internalValues.lg = breakpoints.lg ?? internalValues.md;\n internalValues.xl = breakpoints.xl ?? internalValues.lg;\n internalValues['2xl'] = breakpoints['2xl'] ?? internalValues.xl;\n\n useEffect(() => {\n const windowWidth = window.innerWidth;\n const entry = (Object.entries(breakpointRanges) as breakpointRangeEntry[]).find(\n ([, [lower, upper]]) => windowWidth >= lower && windowWidth < upper,\n );\n\n if (entry) {\n // If we have a key T will be defined due to the internalValues setup above\n const [key] = entry;\n setOutputValue(internalValues[key] as T);\n } else {\n setOutputValue(defaultValue);\n }\n }, [windowSize.width]);\n\n return outputValue;\n}\n","import { RefObject, useState } from 'react';\nimport { useForm, SubmitHandler } from 'react-hook-form';\nimport { ErrorMessage } from '@hookform/error-message';\nimport { toast } from 'react-toastify';\nimport ClipLoader from 'react-spinners/ClipLoader';\nimport { CourseType, Grade, GradeLevel, Teacher, AddReview } from '@polyratings/shared';\nimport { ReviewService } from '@/services';\nimport { departments } from '@/constants';\nimport { useService } from '@/hooks';\n\ninterface EvaluateTeacherFormInputs {\n knownClass: string;\n overallRating: number;\n recognizesStudentDifficulties: number;\n presentsMaterialClearly: number;\n reviewText: string;\n unknownClassDepartment: string;\n unknownClassNumber: number;\n year: string;\n grade: string;\n reasonForTaking: string;\n}\n\ninterface EvaluateTeacherFormProps {\n teacher: Teacher | null;\n setTeacher: (teacher: Teacher) => void;\n closeForm: () => void;\n overrideSubmitHandler?: (review: AddReview) => void | Promise;\n innerRef?: RefObject;\n}\nexport function EvaluateTeacherForm({\n teacher,\n setTeacher,\n closeForm,\n overrideSubmitHandler,\n innerRef,\n}: EvaluateTeacherFormProps) {\n const {\n register,\n handleSubmit,\n watch,\n formState: { errors },\n } = useForm({\n defaultValues: {\n knownClass: Object.keys(teacher?.reviews || {})[0],\n },\n });\n const knownClassValue = watch('knownClass');\n const reviewService = useService(ReviewService);\n const [networkErrorText, setNetworkErrorText] = useState('');\n const [loading, setLoading] = useState(false);\n\n const onSubmit: SubmitHandler = async (formResult) => {\n setLoading(true);\n const newReview: AddReview = {\n overallRating: formResult.overallRating,\n recognizesStudentDifficulties: formResult.recognizesStudentDifficulties,\n presentsMaterialClearly: formResult.presentsMaterialClearly,\n // Purposely set null as any due to use case where when teacher == null teacherId is not used\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n teacherId: teacher?.id || (null as any),\n classIdOrName:\n formResult.knownClass ||\n `${formResult.unknownClassDepartment} ${formResult.unknownClassNumber}`,\n review: {\n gradeLevel: formResult.year as GradeLevel,\n grade: formResult.grade as Grade,\n courseType: formResult.reasonForTaking as CourseType,\n rating: formResult.reviewText,\n postDate: new Date()\n },\n };\n if (overrideSubmitHandler) {\n overrideSubmitHandler(newReview);\n } else {\n try {\n const newTeacherData = await reviewService.uploadReview(newReview);\n setTeacher(newTeacherData);\n toast.success('Thank you for your review');\n closeForm();\n } catch (e) {\n setNetworkErrorText(e as string);\n }\n }\n setLoading(false);\n };\n\n const numericalRatings: { label: string; inputName: keyof EvaluateTeacherFormInputs }[] = [\n { label: 'Overall Rating', inputName: 'overallRating' },\n { label: 'Recognizes Student Difficulties', inputName: 'recognizesStudentDifficulties' },\n { label: 'Presents Material Clearly', inputName: 'presentsMaterialClearly' },\n ];\n\n const classInformation: {\n label: string;\n inputName: keyof EvaluateTeacherFormInputs;\n options: string[];\n }[] = [\n {\n label: 'Year',\n inputName: 'year',\n options: ['Freshman', 'Sophomore', 'Junior', 'Senior', 'Grad'],\n },\n {\n label: 'Grade Achieved',\n inputName: 'grade',\n options: ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F', 'CR', 'NC'],\n },\n {\n label: 'Reason For Taking',\n inputName: 'reasonForTaking',\n options: ['Required (Major)', 'Required (Support)', 'Elective'],\n },\n ];\n\n return (\n
\n {teacher && (\n