{"version":3,"file":"index.134ede61.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","../../../shared/dist/esm/interfaces/Review.js","../../../shared/dist/esm/constants.js","../../../shared/dist/esm/decorators.js","../../../shared/dist/esm/utils/chunkArray.js","../../../shared/dist/esm/utils/math.js","../../../shared/dist/esm/dtos/public/BaseDTO.js","../../../shared/dist/esm/dtos/public/AddReview.js","../../../shared/dist/esm/dtos/public/LoginRequest.js","../../../shared/dist/esm/dtos/public/AuthResponse.js","../../../shared/dist/esm/dtos/public/ProcessingReview.js","../../../shared/dist/esm/dtos/public/AddProfessor.js","../../../shared/dist/esm/dtos/public/Bulk.js","../../../shared/dist/esm/dtos/public/ReportReview.js","../../../shared/dist/esm/dtos/public/MergeProfessor.js","../../../shared/dist/esm/dtos/public/ChangeProfessor.js","../../../shared/dist/esm/dtos/internal/Reviews.js","../../../shared/dist/esm/dtos/internal/Professors.js","../../../shared/dist/esm/dtos/internal/RatingReport.js","../../../shared/dist/esm/dtos/internal/User.js","../../src/App.config.ts","../../src/services/admin.service.ts","../../src/services/auth.service.ts","../../src/services/http.service.ts","../../src/services/storage.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/injector.ts","../../src/components/Backdrop.tsx","../../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/assets/Discord-Logo-White.svg","../../src/assets/github.svg","../../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","../../vite/preload-helper","../../src/pages/Admin.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__\"","export const GradeLevelOptions = [\n \"Freshman\",\n \"Sophomore\",\n \"Junior\",\n \"Senior\",\n \"5th/6th Year\",\n \"Grad Student\",\n];\nexport const GradeOptions = [\"N/A\", \"A\", \"B\", \"C\", \"D\", \"F\", \"CR\", \"NC\", \"W\"];\nexport const CourseTypeOptions = [\n \"Elective\",\n \"General Ed\",\n \"Major (Support)\",\n \"Major (Required)\",\n];\n//# sourceMappingURL=Review.js.map","export const DEPARTMENT_LIST = [\n \"AEPS\",\n \"AERO\",\n \"AG\",\n \"AGB\",\n \"AGC\",\n \"AGED\",\n \"ANT\",\n \"AP\",\n \"ARCE\",\n \"ARCH\",\n \"ART\",\n \"ASCI\",\n \"ASTR\",\n \"BIO\",\n \"BMED\",\n \"BOT\",\n \"BRAE\",\n \"BUS\",\n \"CD\",\n \"CE\",\n \"CHEM\",\n \"CHIN\",\n \"CM\",\n \"CMAT\",\n \"COMS\",\n \"CPE\",\n \"CRP\",\n \"CSC\",\n \"CSUC\",\n \"CSUV\",\n \"DANC\",\n \"DATA\",\n \"DE\",\n \"DEV10\",\n \"DEV11\",\n \"DSCI\",\n \"ECON\",\n \"EDES\",\n \"EDUC\",\n \"EE\",\n \"ENGL\",\n \"ENGR\",\n \"ENVE\",\n \"ERSC\",\n \"ES\",\n \"ESCI\",\n \"EXSS\",\n \"FPE\",\n \"FR\",\n \"FSN\",\n \"GEOG\",\n \"GEOL\",\n \"GER\",\n \"GRC\",\n \"GS\",\n \"GSA\",\n \"GSB\",\n \"GSE\",\n \"GSP\",\n \"HIST\",\n \"HLTH\",\n \"HNRC\",\n \"HNRS\",\n \"IME\",\n \"IP\",\n \"ISLA\",\n \"ITAL\",\n \"ITP\",\n \"JOUR\",\n \"JPNS\",\n \"KINE\",\n \"LA\",\n \"LAES\",\n \"LS\",\n \"MATE\",\n \"MATH\",\n \"MCRO\",\n \"ME\",\n \"MSCI\",\n \"MSL\",\n \"MU\",\n \"NE\",\n \"NR\",\n \"PEM\",\n \"PEW\",\n \"PHIL\",\n \"PHYS\",\n \"POLS\",\n \"PSC\",\n \"PSY\",\n \"RELS\",\n \"RPTA\",\n \"SCM\",\n \"SOC\",\n \"SPAN\",\n \"SPED\",\n \"SS\",\n \"STAT\",\n \"TH\",\n \"UNIV\",\n \"WGS\",\n \"WLC\",\n \"WVIT\",\n].sort();\n//# sourceMappingURL=constants.js.map","import { Exclude, Expose, Transform } from \"class-transformer\";\nexport const ExcludeFrontend = () => (target, propertyKey) => {\n Exclude({ toPlainOnly: true })(target, propertyKey);\n Expose({ toClassOnly: true })(target, propertyKey);\n};\nexport const ExposeFrontend = () => Expose();\nexport const Default = (fn) => Transform(({ value }) => value !== null && value !== void 0 ? value : fn());\n//# sourceMappingURL=decorators.js.map","export function chunkArray(arr, size) {\n const arrShallowClone = [...arr];\n const chunked = [];\n while (arrShallowClone.length) {\n chunked.push(arrShallowClone.splice(0, size));\n }\n return chunked;\n}\n//# sourceMappingURL=chunkArray.js.map","export function roundToPrecision(roundingTarget, precision) {\n return Math.round((roundingTarget + Number.EPSILON) * 10 ** precision) / 10 ** precision;\n}\n//# sourceMappingURL=math.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { Allow } from \"class-validator\";\nexport class BaseDTO {\n}\nBaseDTO.__base_dto_marker__ = true;\n__decorate([\n Allow()\n], BaseDTO, \"__base_dto_marker__\", void 0);\n//# sourceMappingURL=BaseDTO.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { IsBoolean, IsDefined, IsIn, IsInt, IsOptional, IsString, IsUUID, Max, Min, MinLength, } from \"class-validator\";\nimport { plainToInstance } from \"class-transformer\";\nimport { DEPARTMENT_LIST } from \"../../constants\";\nimport { BaseDTO } from \"./BaseDTO\";\nimport { ExposeFrontend } from \"../../decorators\";\nexport class NewReviewBase {\n}\n__decorate([\n IsDefined()\n], NewReviewBase.prototype, \"gradeLevel\", void 0);\n__decorate([\n IsDefined()\n], NewReviewBase.prototype, \"grade\", void 0);\n__decorate([\n IsDefined()\n], NewReviewBase.prototype, \"courseType\", void 0);\n__decorate([\n IsInt(),\n Min(100),\n Max(599)\n], NewReviewBase.prototype, \"courseNum\", void 0);\n__decorate([\n IsIn(DEPARTMENT_LIST)\n], NewReviewBase.prototype, \"department\", void 0);\n__decorate([\n IsInt(),\n Min(0),\n Max(4)\n], NewReviewBase.prototype, \"overallRating\", void 0);\n__decorate([\n IsInt(),\n Min(0),\n Max(4)\n], NewReviewBase.prototype, \"presentsMaterialClearly\", void 0);\n__decorate([\n IsInt(),\n Min(0),\n Max(4)\n], NewReviewBase.prototype, \"recognizesStudentDifficulties\", void 0);\n__decorate([\n MinLength(20)\n], NewReviewBase.prototype, \"rating\", void 0);\nexport class AddReviewRequest extends NewReviewBase {\n}\n__decorate([\n IsUUID()\n], AddReviewRequest.prototype, \"professor\", void 0);\nexport class AddReviewResponse extends BaseDTO {\n static new(success, statusMessage, newReviewId) {\n return plainToInstance(AddReviewResponse, { success, statusMessage, newReviewId });\n }\n}\n__decorate([\n ExposeFrontend(),\n IsBoolean()\n], AddReviewResponse.prototype, \"success\", void 0);\n__decorate([\n ExposeFrontend(),\n IsString()\n], AddReviewResponse.prototype, \"statusMessage\", void 0);\n__decorate([\n IsOptional(),\n IsString(),\n ExposeFrontend()\n], AddReviewResponse.prototype, \"newReviewId\", void 0);\n//# sourceMappingURL=AddReview.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { IsString } from \"class-validator\";\nimport { BaseDTO } from \".\";\nexport class LoginRequest extends BaseDTO {\n}\n__decorate([\n IsString()\n], LoginRequest.prototype, \"username\", void 0);\n__decorate([\n IsString()\n], LoginRequest.prototype, \"password\", void 0);\n//# sourceMappingURL=LoginRequest.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { plainToInstance } from \"class-transformer\";\nimport { IsString } from \"class-validator\";\nimport { ExposeFrontend } from \"../../decorators\";\nimport { BaseDTO } from \"./BaseDTO\";\nexport class AuthResponse extends BaseDTO {\n static new(accessToken) {\n return plainToInstance(AuthResponse, { accessToken });\n }\n}\n__decorate([\n ExposeFrontend(),\n IsString()\n], AuthResponse.prototype, \"accessToken\", void 0);\n//# sourceMappingURL=AuthResponse.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { plainToInstance } from \"class-transformer\";\nimport { Allow, IsBoolean, IsOptional, IsString } from \"class-validator\";\nimport { ExposeFrontend } from \"../../decorators\";\nimport { BaseDTO } from \"./BaseDTO\";\nexport class ProcessingReviewResponse extends BaseDTO {\n static new(success, message, updatedProfessor) {\n return plainToInstance(ProcessingReviewResponse, { success, message, updatedProfessor });\n }\n}\n__decorate([\n IsBoolean(),\n ExposeFrontend()\n], ProcessingReviewResponse.prototype, \"success\", void 0);\n__decorate([\n IsOptional(),\n IsString(),\n ExposeFrontend()\n], ProcessingReviewResponse.prototype, \"message\", void 0);\n__decorate([\n IsOptional(),\n Allow(),\n ExposeFrontend()\n], ProcessingReviewResponse.prototype, \"updatedProfessor\", void 0);\n//# sourceMappingURL=ProcessingReview.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { Type } from \"class-transformer\";\nimport { Equals, IsIn, IsNotEmpty, ValidateNested } from \"class-validator\";\nimport { DEPARTMENT_LIST } from \"../../constants\";\nimport { NewReviewBase } from \"./AddReview\";\nexport class AddProfessorRequest {\n}\n__decorate([\n IsIn(DEPARTMENT_LIST)\n], AddProfessorRequest.prototype, \"department\", void 0);\n__decorate([\n IsNotEmpty()\n], AddProfessorRequest.prototype, \"firstName\", void 0);\n__decorate([\n IsNotEmpty()\n], AddProfessorRequest.prototype, \"lastName\", void 0);\n__decorate([\n Equals(1)\n], AddProfessorRequest.prototype, \"numEvals\", void 0);\n__decorate([\n ValidateNested(),\n Type(() => NewReviewBase)\n], AddProfessorRequest.prototype, \"review\", void 0);\n//# sourceMappingURL=AddProfessor.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { IsArray } from \"class-validator\";\nimport { Default } from \"../../decorators\";\nimport { BaseDTO } from \"./BaseDTO\";\nexport const bulkKeys = [\n \"professors\",\n \"rating-queue\",\n \"professor-queue\",\n \"reports\",\n \"users\",\n];\nexport class BulkValueRequest extends BaseDTO {\n}\n__decorate([\n Default(() => []),\n IsArray()\n], BulkValueRequest.prototype, \"keys\", void 0);\n//# sourceMappingURL=Bulk.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { IsEmail, IsString, IsUUID, ValidateIf } from \"class-validator\";\nimport { BaseDTO } from \"./BaseDTO\";\nexport class ReportReviewRequest extends BaseDTO {\n}\n__decorate([\n IsUUID()\n], ReportReviewRequest.prototype, \"ratingId\", void 0);\n__decorate([\n IsUUID()\n], ReportReviewRequest.prototype, \"professorId\", void 0);\n__decorate([\n ValidateIf((r) => r.email !== \"\"),\n IsEmail()\n], ReportReviewRequest.prototype, \"email\", void 0);\n__decorate([\n IsString()\n], ReportReviewRequest.prototype, \"reason\", void 0);\n//# sourceMappingURL=ReportReview.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { IsUUID } from \"class-validator\";\nimport { BaseDTO } from \"./BaseDTO\";\nexport class MergeProfessorRequest extends BaseDTO {\n}\n__decorate([\n IsUUID()\n], MergeProfessorRequest.prototype, \"sourceId\", void 0);\n__decorate([\n IsUUID()\n], MergeProfessorRequest.prototype, \"destId\", void 0);\n//# sourceMappingURL=MergeProfessor.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { IsIn, IsUUID } from \"class-validator\";\nimport { DEPARTMENT_LIST } from \"../../constants\";\nimport { BaseDTO } from \"./BaseDTO\";\nexport class ChangeDepartmentRequest extends BaseDTO {\n}\n__decorate([\n IsUUID()\n], ChangeDepartmentRequest.prototype, \"professorId\", void 0);\n__decorate([\n IsIn(DEPARTMENT_LIST)\n], ChangeDepartmentRequest.prototype, \"department\", void 0);\n//# sourceMappingURL=ChangeProfessor.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { Allow, IsDate, IsDefined, IsIn, IsInt, IsUUID, Max, Min, MinLength, } from \"class-validator\";\nimport { plainToInstance, Type } from \"class-transformer\";\nimport { Default, ExcludeFrontend, ExposeFrontend } from \"../../decorators\";\nimport { DEPARTMENT_LIST } from \"../../constants\";\nimport { BaseDTO } from \"../public\";\nexport class ReviewDTO extends BaseDTO {\n constructor() {\n super(...arguments);\n this.postDate = new Date();\n }\n}\n__decorate([\n IsUUID(),\n ExposeFrontend(),\n Default(() => crypto.randomUUID())\n], ReviewDTO.prototype, \"id\", void 0);\n__decorate([\n IsUUID(),\n ExcludeFrontend()\n], ReviewDTO.prototype, \"professor\", void 0);\n__decorate([\n IsDefined(),\n ExposeFrontend()\n], ReviewDTO.prototype, \"grade\", void 0);\n__decorate([\n IsDefined(),\n ExposeFrontend()\n], ReviewDTO.prototype, \"gradeLevel\", void 0);\n__decorate([\n IsDefined(),\n ExposeFrontend()\n], ReviewDTO.prototype, \"courseType\", void 0);\n__decorate([\n IsDate(),\n Type(() => Date),\n Default(() => new Date()),\n ExposeFrontend()\n], ReviewDTO.prototype, \"postDate\", void 0);\n__decorate([\n IsInt(),\n Min(0),\n Max(4),\n ExcludeFrontend()\n], ReviewDTO.prototype, \"overallRating\", void 0);\n__decorate([\n IsInt(),\n Min(0),\n Max(4),\n ExcludeFrontend()\n], ReviewDTO.prototype, \"presentsMaterialClearly\", void 0);\n__decorate([\n IsInt(),\n Min(0),\n Max(4),\n ExcludeFrontend()\n], ReviewDTO.prototype, \"recognizesStudentDifficulties\", void 0);\n__decorate([\n MinLength(20),\n ExposeFrontend()\n], ReviewDTO.prototype, \"rating\", void 0);\nexport class PendingReviewDTO extends ReviewDTO {\n static fromAddReviewRequest(request) {\n return plainToInstance(PendingReviewDTO, request);\n }\n toReviewDTO() {\n return plainToInstance(ReviewDTO, this, {\n excludeExtraneousValues: true,\n });\n }\n}\n__decorate([\n Allow(),\n ExcludeFrontend(),\n Default(() => \"Queued\")\n], PendingReviewDTO.prototype, \"status\", void 0);\n__decorate([\n Allow(),\n ExposeFrontend()\n], PendingReviewDTO.prototype, \"error\", void 0);\n__decorate([\n Allow(),\n ExposeFrontend()\n], PendingReviewDTO.prototype, \"sentimentResponse\", void 0);\n__decorate([\n IsInt(),\n Min(100),\n Max(599),\n ExposeFrontend()\n], PendingReviewDTO.prototype, \"courseNum\", void 0);\n__decorate([\n IsIn(DEPARTMENT_LIST),\n ExposeFrontend()\n], PendingReviewDTO.prototype, \"department\", void 0);\n//# sourceMappingURL=Reviews.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { Allow, IsIn, IsInt, IsNotEmpty, IsUUID, Max, Min } from \"class-validator\";\nimport { plainToInstance, Transform } from \"class-transformer\";\nimport { roundToPrecision } from \"../../utils\";\nimport { BaseDTO } from \"../public\";\nimport { ExposeFrontend } from \"../../decorators\";\nimport { ReviewDTO } from \"./Reviews\";\nimport { DEPARTMENT_LIST } from \"../../constants\";\nexport class TruncatedProfessorDTO extends BaseDTO {\n}\n__decorate([\n IsUUID(),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"id\", void 0);\n__decorate([\n IsIn(DEPARTMENT_LIST),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"department\", void 0);\n__decorate([\n IsNotEmpty(),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"firstName\", void 0);\n__decorate([\n IsNotEmpty(),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"lastName\", void 0);\n__decorate([\n IsInt(),\n Min(0),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"numEvals\", void 0);\n__decorate([\n Min(0),\n Max(4),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"overallRating\", void 0);\n__decorate([\n Min(0),\n Max(4),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"materialClear\", void 0);\n__decorate([\n Min(0),\n Max(4),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"studentDifficulties\", void 0);\n__decorate([\n Allow(),\n ExposeFrontend()\n], TruncatedProfessorDTO.prototype, \"courses\", void 0);\nexport class ProfessorDTO extends TruncatedProfessorDTO {\n addReview(review, courseName) {\n review.professor = this.id;\n if (!this.courses.includes(courseName)) {\n this.courses.push(courseName);\n }\n const reviews = this.reviews[courseName];\n if (!reviews) {\n this.reviews[courseName] = [review];\n }\n else {\n reviews.push(review);\n }\n const newMaterial = (this.materialClear * this.numEvals + review.presentsMaterialClearly) /\n (this.numEvals + 1);\n const newStudentDiff = (this.studentDifficulties * this.numEvals + review.recognizesStudentDifficulties) /\n (this.numEvals + 1);\n const newOverall = (this.overallRating * this.numEvals + review.overallRating) / (this.numEvals + 1);\n this.numEvals += 1;\n this.materialClear = roundToPrecision(newMaterial, 2);\n this.studentDifficulties = roundToPrecision(newStudentDiff, 2);\n this.overallRating = roundToPrecision(newOverall, 2);\n }\n removeReview(reviewId) {\n const targetCourse = Object.entries(this.reviews).find(([, courseReviews]) => courseReviews.find((review) => review.id === reviewId));\n if (!targetCourse) {\n throw new Error(\"Review Does not exist\");\n }\n const [courseName, reviews] = targetCourse;\n let removedReview;\n if (reviews.length === 1) {\n [removedReview] = this.reviews[courseName];\n delete this.reviews[courseName];\n const coursesIndex = this.courses.indexOf(courseName);\n if (coursesIndex === -1) {\n throw new Error(\"Course to be removed is missing from professorDTO courses\");\n }\n this.courses.splice(coursesIndex, 1);\n }\n else {\n const reviewIndex = reviews.findIndex((review) => review.id === reviewId);\n removedReview = this.reviews[courseName][reviewIndex];\n this.reviews[courseName].splice(reviewIndex, 1);\n }\n if (this.numEvals === 1) {\n this.materialClear = 0;\n this.studentDifficulties = 0;\n this.overallRating = 0;\n this.numEvals = 0;\n }\n else {\n const newMaterial = (this.materialClear * this.numEvals - removedReview.presentsMaterialClearly) /\n (this.numEvals - 1);\n const newStudentDiff = (this.studentDifficulties * this.numEvals -\n removedReview.recognizesStudentDifficulties) /\n (this.numEvals - 1);\n const newOverall = (this.overallRating * this.numEvals - removedReview.overallRating) /\n (this.numEvals - 1);\n this.numEvals -= 1;\n this.materialClear = roundToPrecision(newMaterial, 2);\n this.studentDifficulties = roundToPrecision(newStudentDiff, 2);\n this.overallRating = roundToPrecision(newOverall, 2);\n }\n }\n toTruncatedProfessorDTO() {\n return plainToInstance(TruncatedProfessorDTO, this, { excludeExtraneousValues: true });\n }\n static fromAddProfessorRequest(addProfessorRequest) {\n const newProfessorId = crypto.randomUUID();\n const plainReview = Object.assign({ professor: newProfessorId }, addProfessorRequest.review);\n const review = plainToInstance(ReviewDTO, plainReview, {\n excludeExtraneousValues: true,\n });\n const courseName = `${addProfessorRequest.review.department} ${addProfessorRequest.review.courseNum}`;\n const plain = Object.assign({ id: newProfessorId, overallRating: review.overallRating, studentDifficulties: review.recognizesStudentDifficulties, materialClear: review.presentsMaterialClearly, courses: [courseName], reviews: {\n [courseName]: [review],\n } }, addProfessorRequest);\n return plainToInstance(ProfessorDTO, plain, {\n excludeExtraneousValues: true,\n });\n }\n}\n__decorate([\n Allow(),\n Transform(({ value, options }) => {\n Object.entries(value).forEach(([course, reviews]) => {\n value[course] = plainToInstance(ReviewDTO, reviews, options);\n });\n return value;\n }, { toClassOnly: true }),\n ExposeFrontend()\n], ProfessorDTO.prototype, \"reviews\", void 0);\n//# sourceMappingURL=Professors.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { plainToInstance, Type } from \"class-transformer\";\nimport { IsEmail, IsString, IsUUID, ValidateIf, ValidateNested } from \"class-validator\";\nimport { BaseDTO } from \"../public\";\nimport { ExposeFrontend } from \"../../decorators\";\nexport class RatingReport extends BaseDTO {\n static fromReportReviewRequest(reportReviewRequest) {\n const plain = Object.assign({ reports: [{ reason: reportReviewRequest.reason, email: reportReviewRequest.email }] }, reportReviewRequest);\n return plainToInstance(RatingReport, plain, { excludeExtraneousValues: true });\n }\n}\n__decorate([\n IsUUID(),\n ExposeFrontend()\n], RatingReport.prototype, \"ratingId\", void 0);\n__decorate([\n Type(() => Report),\n ValidateNested(),\n ExposeFrontend()\n], RatingReport.prototype, \"reports\", void 0);\n__decorate([\n IsUUID(),\n ExposeFrontend()\n], RatingReport.prototype, \"professorId\", void 0);\nexport class Report {\n}\n__decorate([\n ValidateIf((r) => r.email !== \"\"),\n IsEmail(),\n ExposeFrontend()\n], Report.prototype, \"email\", void 0);\n__decorate([\n IsString(),\n ExposeFrontend()\n], Report.prototype, \"reason\", void 0);\n//# sourceMappingURL=RatingReport.js.map","var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nimport { IsNotEmpty } from \"class-validator\";\nexport class User {\n constructor(username, hashedPassword) {\n this.username = username;\n this.password = hashedPassword;\n }\n}\n__decorate([\n IsNotEmpty()\n], User.prototype, \"username\", void 0);\n__decorate([\n IsNotEmpty()\n], User.prototype, \"password\", void 0);\n//# sourceMappingURL=User.js.map","interface AppConfiguration {\n remoteUrl: string;\n base: string;\n}\n\nconst devConfig: AppConfiguration = {\n remoteUrl: \"https://api-dev.polyratings.dev\",\n base: \"/\",\n};\n\nconst prodConfig: AppConfiguration = {\n remoteUrl: \"https://api-prod.polyratings.dev\",\n base: \"/\",\n};\n\nconst betaConfig: AppConfiguration = {\n remoteUrl: \"https://api-beta.polyratings.dev\",\n base: \"/\",\n};\n\nconst liveConfig = window.location.href.includes(\"beta.\") ? betaConfig : prodConfig;\n\nexport const config = process.env.NODE_ENV === \"development\" ? devConfig : liveConfig;\n","import { BulkKey, chunkArray, RatingReport, Review, Teacher, Internal } from \"@polyratings/client\";\nimport { config } from \"@/App.config\";\nimport { AuthService } from \"./auth.service\";\nimport { HttpService } from \"./http.service\";\nimport { CacheEntry, StorageService } from \".\";\n\nconst WORKER_RETRIEVAL_CHUNK_SIZE = 1000;\nconst PROFESSOR_KV_DUMP_CACHE_KEY = \"PROFESSOR_KV_DUMP\";\nconst TWO_HOURS = 1000 * 60 * 60 * 2;\n\ntype ProfessorKvDumpCacheEntry = CacheEntry>;\nexport interface JoinedRatingReport extends RatingReport {\n professor: Teacher;\n review: Review;\n courseName: string;\n}\nexport type PendingReview = Internal.PendingReviewDTOPlain & { scores: Record };\nexport type BackendProfessor = Internal.PlainProfessorDTO;\n\nexport type ConnectedReview = Internal.ReviewDTO & { professorId: string; professorName: string };\nexport class AdminService {\n private professorKvDump: Promise = new Promise(() => {});\n\n constructor(\n private httpService: HttpService,\n authService: AuthService,\n private storageService: StorageService,\n ) {\n authService.isAuthenticatedSubject.subscribe((user) => {\n if (user) {\n this.professorKvDump = this.storageService\n .getItem>(PROFESSOR_KV_DUMP_CACHE_KEY)\n .then((result) => result ?? this.fetchProfessorKvDump());\n }\n });\n }\n\n private async fetchProfessorKvDump(): Promise {\n const professors = await this.bulkRead(\"professors\");\n const professorIdMap = professors.reduce((acc, curr) => {\n acc[curr.id] = curr;\n return acc;\n }, {} as Record);\n\n const professorKvDump: ProfessorKvDumpCacheEntry = {\n data: professorIdMap,\n exp: Date.now() + TWO_HOURS,\n cachedAt: Date.now(),\n };\n\n this.storageService.setItem(PROFESSOR_KV_DUMP_CACHE_KEY, professorKvDump.data, TWO_HOURS);\n return professorKvDump;\n }\n\n private async bulkRead(bulkKey: BulkKey): Promise {\n const keyRequest = await this.httpService.fetch(\n `${config.remoteUrl}/admin/bulk/${bulkKey}`,\n );\n const allKeys = (await keyRequest.json()) as string[];\n const chunkedKeys = chunkArray(allKeys, WORKER_RETRIEVAL_CHUNK_SIZE);\n const results = await Promise.all(\n chunkedKeys.map((chunk) =>\n this.httpService.fetch(`${config.remoteUrl}/admin/bulk/${bulkKey}`, {\n method: \"POST\",\n body: JSON.stringify({\n keys: chunk,\n }),\n }),\n ),\n );\n\n const bodies2d = await Promise.all(results.map((res) => res.json()));\n return bodies2d.flat() as T[];\n }\n\n public async recentReviews(): Promise {\n const allProfessors = (await this.professorKvDump).data;\n\n const allReviews: ConnectedReview[] = Object.values(allProfessors).flatMap((professor) =>\n Object.values(professor.reviews ?? [])\n .flat()\n .map((review) => ({\n professorId: professor.id,\n professorName: `${professor.lastName}, ${professor.firstName}`,\n ...review,\n })),\n );\n\n allReviews.sort(\n (reviewA, reviewB) =>\n Date.parse(reviewB.postDate.toString()) - Date.parse(reviewA.postDate.toString()),\n );\n\n return allReviews;\n }\n\n public async professorKvDumpUpdatedAt(): Promise {\n const professorKvDump = await this.professorKvDump;\n return new Date(professorKvDump.cachedAt).toLocaleTimeString(\"US\");\n }\n\n public async pendingProfessors(): Promise {\n const pendingProfessorsRes = await this.httpService.fetch(\n `${config.remoteUrl}/admin/professors/pending`,\n );\n return pendingProfessorsRes.json();\n }\n\n public async approvePendingProfessor(professorId: string): Promise {\n await this.httpService.fetch(`${config.remoteUrl}/admin/pending/${professorId}`, {\n method: \"POST\",\n });\n const pendingProfessors = await this.pendingProfessors();\n // Sometimes the kv store does not update fast enough to be queried immediately\n return pendingProfessors.filter((professor) => professor.id !== professorId);\n }\n\n public async removePendingProfessor(professorId: string): Promise {\n await this.httpService.fetch(`${config.remoteUrl}/admin/pending/${professorId}`, {\n method: \"DELETE\",\n });\n const pendingProfessors = await this.pendingProfessors();\n // Sometimes the kv store does not update fast enough to be queried immediately\n return pendingProfessors.filter((professor) => professor.id !== professorId);\n }\n\n public async removeReview(professorId: string, reviewId: string): Promise {\n await this.httpService.fetch(\n `${config.remoteUrl}/admin/rating/${professorId}/${reviewId}`,\n {\n method: \"DELETE\",\n },\n );\n // Remove review from local cache\n const professorKvDump = (await this.professorKvDump).data;\n const targetProfessor = professorKvDump[professorId];\n\n // Use non null assertion to make code cleaner\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const [key, arr] = Object.entries(targetProfessor.reviews ?? {}).find(([, reviews]) =>\n (reviews as ConnectedReview[]).find((review) => review.id === reviewId),\n )!;\n\n if (arr.length === 1) {\n // It is the last review\n\n // We know it is defined from before\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n delete targetProfessor.reviews![key];\n } else {\n const reviewIndex = arr.findIndex(\n (review) => (review as ConnectedReview).id === reviewId,\n );\n arr.splice(reviewIndex, 1);\n }\n\n return this.recentReviews();\n }\n\n public async getReports(): Promise {\n const alreadyRemovedReview: Review = {\n id: \"N/A\",\n grade: \"N/A\",\n courseType: \"N/A\" as never,\n rating: \"N/A\",\n postDate: new Date(),\n gradeLevel: \"N/A\" as never,\n };\n\n const reports = await this.bulkRead(\"reports\");\n const allProfessors = await (await this.professorKvDump).data;\n return reports.map((report) => {\n const professor = { ...allProfessors[report.professorId] } as Teacher;\n const [courseName, reviewArr] = Object.entries(professor.reviews ?? {}).find(\n ([, reviews]) => reviews.find((r) => r.id === report.ratingId),\n ) ?? [\"N/A\", []];\n const review = reviewArr.find((review) => review.id === report.ratingId);\n\n // Remove reviews since there is no need to waste memory\n delete professor.reviews;\n\n return { ...report, professor, review: review ?? alreadyRemovedReview, courseName };\n });\n }\n\n public async actOnReport(ratingId: string): Promise {\n await this.httpService.fetch(`${config.remoteUrl}/admin/reports/${ratingId}`, {\n method: \"POST\",\n });\n const reports = await this.getReports();\n // Sometimes the kv store does not update fast enough to be queried immediately\n return reports.filter((report) => report.ratingId !== ratingId);\n }\n\n public async removeReport(ratingId: string): Promise {\n await this.httpService.fetch(`${config.remoteUrl}/admin/reports/${ratingId}`, {\n method: \"DELETE\",\n });\n const reports = await this.getReports();\n // Sometimes the kv store does not update fast enough to be queried immediately\n return reports.filter((report) => report.ratingId !== ratingId);\n }\n\n // TODO: Find a better way to handle normally private types on the frontend and in other places\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n public async getProcessedReviews(): Promise {\n const reviews = await this.bulkRead(\"rating-queue\");\n return reviews.map((review) => {\n const scores = Object.entries(review.sentimentResponse ?? {}).reduce(\n (acc, [key, scoresObj]) => {\n acc[key] = scoresObj.summaryScore.value;\n return acc;\n },\n {} as Record,\n );\n return { scores, ...review };\n });\n }\n}\n","import { BehaviorSubject } from \"rxjs\";\nimport { AuthResponse, UserToken } from \"@polyratings/client\";\nimport jwtDecode from \"jwt-decode\";\nimport { config } from \"@/App.config\";\nimport { StorageService } from \"./storage.service\";\n\nexport const USER_TOKEN_CACHE_KEY = \"user\";\n\n// 2h token expiry\nconst USER_TOKEN_EXPIRY_TIME = 1000 * 60 * 60 * 2;\n\nexport class AuthService {\n private jwtToken: string | null = null;\n\n public isAuthenticatedSubject = new BehaviorSubject(null);\n\n constructor(private storageService: StorageService, private fetch: typeof window.fetch) {\n storageService.getItem(USER_TOKEN_CACHE_KEY).then((jwtCacheEntry) => {\n if (jwtCacheEntry) {\n this.setAuthState(jwtCacheEntry.data);\n }\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 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 AuthResponse;\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.storageService.setItem(USER_TOKEN_CACHE_KEY, jwtToken, USER_TOKEN_EXPIRY_TIME);\n } else {\n this.storageService.removeItem(USER_TOKEN_CACHE_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 new Error(JSON.stringify(errorPayload.message));\n }\n return res;\n }\n}\n","const POLYRATINGS_INDEXED_DB_NAME = \"POLYRATINGS\";\nconst POLYRATINGS_OBJECT_STORE = \"POLYRATINGS\";\nconst POLYRATINGS_INDEXED_DB_VERSION = 1;\n\nexport interface CacheEntry {\n exp: number;\n cachedAt: number;\n data: T;\n}\n\nexport class StorageService {\n private databaseConnection: Promise = new Promise((resolve, reject) => {\n const dbOpenRequest = indexedDB.open(\n POLYRATINGS_INDEXED_DB_NAME,\n POLYRATINGS_INDEXED_DB_VERSION,\n );\n\n dbOpenRequest.onupgradeneeded = (): void => {\n const db = dbOpenRequest.result;\n // Wipes out all auto-save data on upgrade\n if (db.objectStoreNames.contains(POLYRATINGS_OBJECT_STORE)) {\n db.deleteObjectStore(POLYRATINGS_OBJECT_STORE);\n }\n\n db.createObjectStore(POLYRATINGS_OBJECT_STORE);\n };\n\n dbOpenRequest.onerror = (): void => {\n // eslint-disable-next-line no-console\n console.error(\"Polyratings IndexedDb error:\", dbOpenRequest.error);\n reject(dbOpenRequest.error);\n };\n\n dbOpenRequest.onsuccess = (): void => {\n resolve(dbOpenRequest.result);\n };\n });\n\n public async getItem(cacheKey: string): Promise | undefined> {\n const db = await this.databaseConnection;\n const transaction = db.transaction(POLYRATINGS_OBJECT_STORE, \"readonly\");\n const request = transaction.objectStore(POLYRATINGS_OBJECT_STORE).get(cacheKey);\n\n return new Promise((resolve) => {\n request.onsuccess = (): void => {\n const cacheEntry: CacheEntry = request.result;\n // Check if cache entry has expired\n if (cacheEntry && Date.now() < cacheEntry.exp) {\n resolve(cacheEntry);\n } else {\n this.removeItem(cacheKey);\n resolve(undefined);\n }\n };\n });\n }\n\n public async setItem(cacheKey: string, data: T, timeUntilExpire: number) {\n const db = await this.databaseConnection;\n const cacheEntry: CacheEntry = {\n data,\n exp: Date.now() + timeUntilExpire,\n cachedAt: Date.now(),\n };\n const transaction = db.transaction(POLYRATINGS_OBJECT_STORE, \"readwrite\");\n await transaction.objectStore(POLYRATINGS_OBJECT_STORE).put(cacheEntry, cacheKey);\n }\n\n public async removeItem(cacheKey: string) {\n const db = await this.databaseConnection;\n const transaction = db.transaction(POLYRATINGS_OBJECT_STORE, \"readwrite\");\n transaction.objectStore(POLYRATINGS_OBJECT_STORE).delete(cacheKey);\n }\n\n public async clearAllStorage() {\n const db = await this.databaseConnection;\n const transaction = db.transaction(POLYRATINGS_OBJECT_STORE, \"readwrite\");\n const request = transaction.objectStore(POLYRATINGS_OBJECT_STORE).clear();\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve();\n request.onerror = () => reject();\n });\n }\n}\n","/* eslint-disable no-console */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable class-methods-use-this */\nimport * as Sentry from \"@sentry/react\";\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 args.forEach((arg) => {\n if (arg instanceof Error) {\n Sentry.captureException(arg);\n }\n });\n console.error(...args);\n }\n\n debug(...args: any[]) {\n console.debug(...args);\n }\n}\n","import {\n AddReviewRequest,\n AddReviewResponse,\n ProcessingReviewResponse,\n ReportReviewRequest,\n Teacher,\n} from \"@polyratings/client\";\nimport { config } from \"@/App.config\";\nimport { HttpService } from \"./http.service\";\nimport { TeacherService } from \".\";\n\nexport class ReviewService {\n constructor(private httpService: HttpService, private teacherService: TeacherService) {}\n\n async uploadReview(addReviewRequest: AddReviewRequest): Promise {\n const addReviewRes = await this.httpService.fetch(\n `${config.remoteUrl}/professors/${addReviewRequest.professor}/ratings`,\n {\n method: \"POST\",\n body: JSON.stringify(addReviewRequest),\n },\n );\n\n const addReviewResponse = (await addReviewRes.json()) as AddReviewResponse;\n\n if (!addReviewResponse.newReviewId) {\n throw new Error(addReviewResponse.statusMessage);\n }\n const processingReviewRes = await this.httpService.fetch(\n `${config.remoteUrl}/ratings/${addReviewResponse.newReviewId}`,\n );\n const processingResponse = (await processingReviewRes.json()) as ProcessingReviewResponse;\n\n if (!processingResponse.updatedProfessor) {\n throw new Error(processingResponse.message);\n }\n\n this.teacherService.overrideCacheEntry(processingResponse.updatedProfessor);\n return processingResponse.updatedProfessor;\n }\n\n async reportReview(report: ReportReviewRequest) {\n await this.httpService.fetch(`${config.remoteUrl}/rating/report`, {\n method: \"POST\",\n body: JSON.stringify(report),\n });\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","interface HasId {\n id: string;\n}\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 { AddProfessorRequest, Teacher } from \"@polyratings/client\";\nimport { config } from \"@/App.config\";\nimport { getRandomSubarray, intersectingDbEntities } from \"@/utils\";\nimport { HttpService } from \"./http.service\";\nimport { StorageService } from \".\";\n\nexport const TEACHER_CACHE_TIME = 1000 * 60 * 10;\nconst ALL_TEACHER_CACHE_KEY = \"ALL_TEACHERS\";\n\nexport type TeacherSearchType = \"name\" | \"department\" | \"class\";\n\nexport class TeacherService {\n private allTeachers: Promise;\n\n constructor(private httpService: HttpService, private storageService: StorageService) {\n this.allTeachers = storageService\n .getItem(ALL_TEACHER_CACHE_KEY)\n .then(async (result) => {\n if (result) {\n return result.data;\n }\n const res = await this.httpService.fetch(`${config.remoteUrl}/professors`);\n const data = await res.json();\n await storageService.setItem(ALL_TEACHER_CACHE_KEY, data, TEACHER_CACHE_TIME);\n return data;\n });\n }\n\n public async getBestTeachers(): 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, 100), 6);\n }\n\n public async getTeacher(id: string): Promise {\n const localTeacherCacheEntry = await this.storageService.getItem(id);\n if (localTeacherCacheEntry) {\n return localTeacherCacheEntry.data;\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(\n (a, b) => Date.parse(b.postDate.toString()) - Date.parse(a.postDate.toString()),\n ),\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: AddProfessorRequest): Promise {\n await this.httpService.fetch(`${config.remoteUrl}/professors`, {\n method: \"POST\",\n body: JSON.stringify(newTeacher),\n });\n }\n\n private addTeacherToCache(teacher: Teacher) {\n this.storageService.setItem(teacher.id, teacher, TEACHER_CACHE_TIME);\n }\n\n public overrideCacheEntry(teacher: Teacher) {\n this.addTeacherToCache(teacher);\n }\n}\n","import { DependencyInjector, InjectionToken, makeInjector } from \"@mindspace-io/react\";\nimport { StorageService } from \"./storage.service\";\nimport { AdminService } from \"./admin.service\";\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 FETCH = new InjectionToken(\"fetch\");\n\nexport const injector: DependencyInjector = injectorFactory();\n\nexport function injectorFactory() {\n return makeInjector([\n { provide: FETCH, useFactory: () => window.fetch.bind(window) },\n {\n provide: AuthService,\n useClass: AuthService,\n deps: [StorageService, FETCH, StorageService],\n },\n { provide: HttpService, useClass: HttpService, deps: [AuthService, FETCH] },\n { provide: TeacherService, useClass: TeacherService, deps: [HttpService, StorageService] },\n { provide: ReviewService, useClass: ReviewService, deps: [HttpService, TeacherService] },\n {\n provide: AdminService,\n useClass: AdminService,\n deps: [HttpService, AuthService, StorageService],\n },\n { provide: Logger, useClass: Logger },\n { provide: StorageService, useClass: StorageService },\n ]);\n}\n","import { ReactNode, useEffect } from \"react\";\n\nexport function Backdrop({ children }: { children: ReactNode }) {\n // Fix scroll position\n useEffect(() => {\n const initialY = window.scrollY;\n window.scrollTo(0, 0);\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 window.scrollTo(0, initialY);\n };\n }, []);\n\n return (\n
\n {children}\n
\n );\n}\n","import { AuthService } from \"@/services\";\nimport { useService, useObservable } from \".\";\n\nexport function useAuth() {\n const authService = useService(AuthService);\n const isAuthenticated = useObservable(\n authService.isAuthenticatedSubject,\n authService.getUser(),\n );\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/client\";\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 React, { 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 {\n CourseType,\n Grade,\n GradeLevel,\n Teacher,\n AddReviewRequest,\n NewReviewBase,\n GradeOptions,\n GradeLevelOptions,\n CourseTypeOptions,\n DEPARTMENT_LIST,\n} from \"@polyratings/client\";\nimport { ReviewService } from \"@/services\";\nimport { useService } from \"@/hooks\";\n\ninterface EvaluateTeacherFormInputs {\n knownClass: string | undefined;\n overallRating: string;\n recognizesStudentDifficulties: string;\n presentsMaterialClearly: string;\n reviewText: string;\n unknownClassDepartment: string;\n unknownClassNumber: string;\n year: GradeLevel;\n grade: Grade;\n reasonForTaking: CourseType;\n}\n\ninterface EvaluateTeacherFormProps {\n teacher?: Teacher | null;\n setTeacher?: (teacher: Teacher) => void;\n closeForm?: () => void;\n overrideSubmitHandler?: (review: NewReviewBase) => 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\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 courseNum =\n formResult.knownClass && formResult.knownClass !== \"other\"\n ? parseInt(formResult.knownClass.split(\" \")[1], 10)\n : parseInt(formResult.unknownClassNumber, 10);\n const department =\n formResult.knownClass && formResult.knownClass !== \"other\"\n ? formResult.knownClass.split(\" \")[0]\n : formResult.unknownClassDepartment;\n\n const reviewBase: NewReviewBase = {\n gradeLevel: formResult.year,\n grade: formResult.grade,\n courseType: formResult.reasonForTaking,\n courseNum,\n department,\n overallRating: parseFloat(formResult.overallRating),\n presentsMaterialClearly: parseFloat(formResult.presentsMaterialClearly),\n recognizesStudentDifficulties: parseFloat(formResult.recognizesStudentDifficulties),\n rating: formResult.reviewText,\n };\n\n if (overrideSubmitHandler) {\n overrideSubmitHandler(reviewBase);\n } else {\n try {\n // The non-null assertion is safe since the override is for when there is no teacher\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const body: AddReviewRequest = { professor: teacher!.id, ...reviewBase };\n const updatedProfessor = await reviewService.uploadReview(body);\n if (setTeacher) {\n setTeacher(updatedProfessor);\n }\n toast.success(\"Thank you for your review\");\n if (closeForm) {\n closeForm();\n }\n } catch (e) {\n setNetworkErrorText((e as Error).toString());\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: Readonly;\n }[] = [\n {\n label: \"Year\",\n inputName: \"year\",\n options: GradeLevelOptions,\n },\n {\n label: \"Grade Achieved\",\n inputName: \"grade\",\n options: GradeOptions,\n },\n {\n label: \"Reason For Taking\",\n inputName: \"reasonForTaking\",\n options: CourseTypeOptions,\n },\n ];\n\n return (\n
\n {teacher && (\n