From 0d315ade3189532a719ec3f0c0875d8a7e33c369 Mon Sep 17 00:00:00 2001 From: quntaoz Date: Sat, 11 Oct 2025 22:58:53 -0400 Subject: [PATCH 1/2] Removed the old listing schema --- server/src/models/Listing.ts | 47 ----------------- server/src/models/index.ts | 1 - server/src/routes/index.ts | 2 - server/src/routes/listings.ts | 96 ----------------------------------- 4 files changed, 146 deletions(-) delete mode 100644 server/src/models/Listing.ts delete mode 100644 server/src/routes/listings.ts diff --git a/server/src/models/Listing.ts b/server/src/models/Listing.ts deleted file mode 100644 index c70ebbe..0000000 --- a/server/src/models/Listing.ts +++ /dev/null @@ -1,47 +0,0 @@ -import mongoose from 'mongoose'; - -const listingSchema = new mongoose.Schema( - { - _id: { - type: String, - required: true, - }, - departments: { - type: [String], - required: true, - }, - email: { - type: String, - required: true, - }, - website: { - type: String, - required: false, - }, - description: { - type: String, - required: false, - }, - keywords: { - type: String, - required: false, - }, - last_updated: { - type: String, - required: true, - }, - lname: { - type: String, - required: true, - }, - fname: { - type: String, - required: true, - } - }, - { - timestamps: true, - } -); - -export const Listing = mongoose.model('listings', listingSchema); \ No newline at end of file diff --git a/server/src/models/index.ts b/server/src/models/index.ts index 610304d..380435e 100644 --- a/server/src/models/index.ts +++ b/server/src/models/index.ts @@ -1,4 +1,3 @@ -export * from "./Listing"; export * from "./User"; export * from "./UserBackup"; export * from "./NewListing"; diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts index 23c3326..2c5dcdd 100644 --- a/server/src/routes/index.ts +++ b/server/src/routes/index.ts @@ -1,5 +1,4 @@ import { Router } from "express"; -import ListingsRoutes from "./listings"; import UsersRoutes from "./users"; import UserBackupsRoutes from "./userBackups"; import NewListingsRoutes from "./newListings"; @@ -7,7 +6,6 @@ import NewListingsRoutes from "./newListings"; const router = Router(); router.use("/newListings", NewListingsRoutes); -router.use("/listings", ListingsRoutes); router.use("/users", UsersRoutes); router.use("/userBackups", UserBackupsRoutes); diff --git a/server/src/routes/listings.ts b/server/src/routes/listings.ts deleted file mode 100644 index b8ce30c..0000000 --- a/server/src/routes/listings.ts +++ /dev/null @@ -1,96 +0,0 @@ -import express from 'express'; -import { Listing } from '../models'; -import { Request, Response, Router } from "express"; -import { isAuthenticated } from '../utils/permissions'; - -const router = Router(); - -/* -//Create new listing - -//Hidden for security reasons - -/*router.post("/", async (request: Request, response: Response) => { - try { - const listing = new Listing(request.body); - await listing.save(); - response.status(201).json({ listing: listing.toObject(), success: true }); - } catch (error) { - console.log(error.message); - response.status(400).json({ error: error.message, success: false }); - } -}); - -// Route for getting listing by id: for testing -router.get('/byId/:id', async (request: Request, response: Response) => { - try { - const { id } = request.params; - - const listing = await Listing.findById(id); - - return response.status(200).json(listing); - } catch (error) { - console.log(error.message); - response.status(500).send({ message: error.message }); - } -}); -*/ - -/* Route for getting relevant listings based on the queries fname, lname, and dept (all optional, at least one of the 3 must be provided) -fname: fname must be a substring of prof's first name for the corresponding listing to be included -lname: lname must be a substring of prof's last name for the corresponding listing to be included -dept: dept must contain a department mentioned in the listing for the corresponding listing to be included -*/ -router.get('/', isAuthenticated, async (request: Request, response: Response) => { - try { - const fname = request.query.fname === undefined ? '' : request.query.fname; - const lname = request.query.lname === undefined ? '' : request.query.lname; - const keywords = request.query.keywords === undefined || request.query.keywords === '' ? [] : (request.query.keywords as String).split(','); - const dept = request.query.dept === undefined || request.query.dept === '' ? [] : (request.query.dept as String).split(','); - - /*if(fname === '' && lname === '' && dept.length == 0 && keywords.length == 0){ - throw new Error('At least 1 query must be provided'); - }*/ - - let conditions = []; - - if (keywords.length > 0) { - const textCondition = { "$text" : { "$search": keywords.join(" "), "$caseSensitive": false } }; - - conditions.push(textCondition); - } - - if (dept.length > 0) { - conditions.push({ "departments": { "$elemMatch": { "$in": dept } } }); - } - - if(typeof fname === "string" && fname.trim()) { - conditions.push({"fname": { "$regex": fname.trim(), "$options": "i" }}) - } - - if(typeof lname === "string" && lname.trim()) { - conditions.push({"lname": { "$regex": lname.trim(), "$options": "i" }}) - } - - const query = conditions.length ? { $and: conditions } : {}; - - const listings = await Listing.find(query); - return response.status(200).json(listings); - - } catch (error) { - console.log(error.message); - response.status(500).send({ message: error.message }); - } -}); - -router.get('/all', isAuthenticated, async (request: Request, response: Response) => { - try { - const listings = await Listing.find(); - return response.status(200).json(listings); - } catch (error) { - console.log(error.message); - response.status(500).send({ message: error.message }); - } -}); - -export default router; \ No newline at end of file From 14fa08c47525adc2860b9bf50a8ca52b6d9b6a73 Mon Sep 17 00:00:00 2001 From: quntaoz Date: Sat, 18 Oct 2025 21:39:09 -0400 Subject: [PATCH 2/2] Refactored backend and use listing model --- client/dist/assets/index-CbSqCWf5.js | 6 +- .../src/components/accounts/ListingCard.tsx | 34 +- .../components/accounts/ListingForm/index.tsx | 46 +- .../src/components/accounts/ListingModal.tsx | 6 +- client/src/components/home/ListingCard.tsx | 8 +- client/src/components/home/ListingModal.tsx | 4 +- .../src/components/home/ListingsCardList.tsx | 6 +- client/src/components/home/SearchHub.tsx | 12 +- client/src/pages/account.tsx | 59 +-- client/src/pages/home.tsx | 16 +- client/src/types/types.tsx | 12 +- server/src/controllers/listingController.ts | 178 ++++++++ server/src/controllers/userController.ts | 333 ++++++++++++++ server/src/index.ts | 1 + server/src/middleware/auth.ts | 92 ++++ server/src/middleware/errorHandler.ts | 78 ++++ server/src/middleware/index.ts | 27 ++ server/src/middleware/validation.ts | 108 +++++ server/src/models/ListingBackup.ts | 39 -- server/src/models/UserBackup.ts | 50 -- server/src/models/index.ts | 6 +- .../src/models/{NewListing.ts => listing.ts} | 4 +- server/src/routes/index.ts | 6 +- server/src/routes/listings.ts | 34 ++ server/src/routes/newListings.ts | 339 -------------- server/src/routes/userBackups.ts | 75 --- server/src/routes/users.ts | 426 ++---------------- server/src/services/listingBackupServices.ts | 75 --- ...ewListingsService.ts => listingService.ts} | 30 +- server/src/services/userBackupService.ts | 92 ---- server/src/services/userService.ts | 40 +- server/src/utils/permissions.ts | 18 - 32 files changed, 1004 insertions(+), 1256 deletions(-) create mode 100644 server/src/controllers/listingController.ts create mode 100644 server/src/controllers/userController.ts create mode 100644 server/src/middleware/auth.ts create mode 100644 server/src/middleware/errorHandler.ts create mode 100644 server/src/middleware/index.ts create mode 100644 server/src/middleware/validation.ts delete mode 100644 server/src/models/ListingBackup.ts delete mode 100644 server/src/models/UserBackup.ts rename server/src/models/{NewListing.ts => listing.ts} (92%) create mode 100644 server/src/routes/listings.ts delete mode 100644 server/src/routes/newListings.ts delete mode 100644 server/src/routes/userBackups.ts delete mode 100644 server/src/services/listingBackupServices.ts rename server/src/services/{newListingsService.ts => listingService.ts} (87%) delete mode 100644 server/src/services/userBackupService.ts delete mode 100644 server/src/utils/permissions.ts diff --git a/client/dist/assets/index-CbSqCWf5.js b/client/dist/assets/index-CbSqCWf5.js index d30871b..c169212 100644 --- a/client/dist/assets/index-CbSqCWf5.js +++ b/client/dist/assets/index-CbSqCWf5.js @@ -73,7 +73,7 @@ Error generating stack: `+a.message+` * * @license MIT */function If(){return If=Object.assign?Object.assign.bind():function(n){for(var r=1;r=0)&&(o[s]=n[s]);return o}function ES(n){return!!(n.metaKey||n.altKey||n.ctrlKey||n.shiftKey)}function CS(n,r){return n.button===0&&(!r||r==="_self")&&!ES(n)}const RS=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],TS="6";try{window.__reactRouterVersion=TS}catch{}const jS="startTransition",B0=Kf[jS];function OS(n){let{basename:r,children:o,future:l,window:s}=n,c=R.useRef();c.current==null&&(c.current=A1({window:s,v5Compat:!0}));let f=c.current,[p,h]=R.useState({action:f.action,location:f.location}),{v7_startTransition:g}=l||{},y=R.useCallback(v=>{g&&B0?B0(()=>h(v)):h(v)},[h,g]);return R.useLayoutEffect(()=>f.listen(y),[f,y]),R.useEffect(()=>bS(l),[l]),R.createElement(xS,{basename:r,children:o,location:p.location,navigationType:p.action,navigator:f,future:l})}const NS=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",_S=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,Rs=R.forwardRef(function(r,o){let{onClick:l,relative:s,reloadDocument:c,replace:f,state:p,target:h,to:g,preventScrollReset:y,viewTransition:v}=r,S=wS(r,RS),{basename:j}=R.useContext(za),w,x=!1;if(typeof g=="string"&&_S.test(g)&&(w=g,NS))try{let N=new URL(window.location.href),M=g.startsWith("//")?new URL(N.protocol+g):new URL(g),O=hd(M.pathname,j);M.origin===N.origin&&O!=null?g=O+M.search+M.hash:x=!0}catch{}let _=rS(g,{relative:s}),T=AS(g,{replace:f,state:p,target:h,preventScrollReset:y,relative:s,viewTransition:v});function C(N){l&&l(N),N.defaultPrevented||T(N)}return R.createElement("a",If({},S,{href:w||_,onClick:x||c?l:C,ref:o,target:h}))});var U0;(function(n){n.UseScrollRestoration="useScrollRestoration",n.UseSubmit="useSubmit",n.UseSubmitFetcher="useSubmitFetcher",n.UseFetcher="useFetcher",n.useViewTransitionState="useViewTransitionState"})(U0||(U0={}));var $0;(function(n){n.UseFetcher="useFetcher",n.UseFetchers="useFetchers",n.UseScrollRestoration="useScrollRestoration"})($0||($0={}));function AS(n,r){let{target:o,replace:l,state:s,preventScrollReset:c,relative:f,viewTransition:p}=r===void 0?{}:r,h=Vv(),g=pi(),y=Xv(n,{relative:f});return R.useCallback(v=>{if(CS(v,o)){v.preventDefault();let S=l!==void 0?l:ms(g)===ms(y);h(n,{replace:S,state:s,preventScrollReset:c,relative:f,viewTransition:p})}},[g,h,y,l,s,o,n,c,f,p])}const MS={isLoading:!0,isAuthenticated:!1,checkContext:()=>{}},mn=R.createContext(MS),ul=({Component:n,unknownBlocked:r,knownBlocked:o})=>{const{user:l,isLoading:s,isAuthenticated:c}=R.useContext(mn);if(s)return null;if(!c)return m.jsx(ml,{to:"/login"});if(l){if(r&&l.userType==="unknown")return m.jsx(ml,{to:"/unknown"});if(o&&l.userType!=="unknown")return m.jsx(ml,{to:"/"})}return m.jsx(n,{})},kS=({Component:n})=>{const{user:r}=R.useContext(mn);return r?m.jsx(ml,{to:"/"}):m.jsx(n,{})},H0=["African American Studies","African Studies","American Studies","Anesthesiology","Anthropology","Applied Mathematics","Applied Physics","Archaeological Studies","Architecture","Art","Astronomy","Biological and Biomedical Sciences","Biomedical Engineering","Biomedical Informatics and Data Science","Biostatistics","Cell Biology","Cellular and Molecular Physiology","Chemical and Environmental Engineering","Chemistry","Child Study Center","Chronic Disease Epidemiology","Classics","Cognitive Science","Comparative Literature","Comparative Medicine","Computational Biology and Bioinformatics","Computer Science","Dermatology","Digital Ethics Center","Early Modern Studies","Earth and Planetary Sciences","East Asian Languages and Literatures","East Asian Studies","Ecology and Evolutionary Biology","Economics","Electrical Engineering","Emergency Medicine","Engineering and Applied Science","English","Environmental Health Sciences","Environmental Studies","Epidemiology of Microbial Diseases","Ethics, Politics and Economics","Ethnicity, Race and Migration","European and Russian Studies","Experimental Pathology","Film and Media Studies","Forestry and Environmental Studies","French","Genetics","Geology and Geophysics","German","Global Affairs","Health Care Management","Health Policy and Management","Hellenic Studies","History","History of Art","History of Medicine","History of Science and Medicine","Humanities","Immunobiology","Internal Medicine","International and Development Economics","Investigative Medicine","Italian","Judaic Studies","Laboratory Medicine","Latin American Studies","Law","Linguistics","MCDB","Management","Mathematics","Mechanical Engineering and Materials Science","Medicine","Medieval Studies","Microbial Pathogenesis","Microbiology","Modern Middle East Studies","Molecular Biophysics and Biochemistry","Molecular, Cellular and Developmental Biology","Music","Near Eastern Langauges and Civilizations","Neurology","Neuroscience","Neurosurgery","Nursing","Obstetrics, Gynecology and Reproductive Sciences","Ophthalmology and Visual Science","Orthopaedics and Rehabilitation","Pathology","Pediatrics","Pharmacology","Philosophy","Physics","Political Science","Psychiatry","Psychology","Public Health","Radiology and Biomedical Imaging","Religious Studies","Slavic Languages and Literatures","Sociology","South Asian Studies","Spanish and Portuguese","Statistics","Surgery","Theater Studies","Therapeutic Radiology","Urology","Women’s, Gender, and Sexuality Studies"],It={"African American Studies":0,"American Studies":0,"Archaeological Studies":0,Architecture:0,Art:0,Classics:0,"Comparative Literature":0,"Early Modern Studies":0,English:0,"Ethics, Politics and Economics":0,"Digital Ethics Center":0,"Film and Media Studies":0,History:0,"History of Art":0,"History of Medicine":0,"History of Science and Medicine":0,Humanities:0,"Judaic Studies":0,"Medieval Studies":0,Music:0,Philosophy:0,"Religious Studies":0,"Theater Studies":0,"African Studies":1,Anthropology:1,"Cognitive Science":1,Economics:1,"Ethnicity, Race and Migration":1,"European and Russian Studies":1,"Global Affairs":1,"International and Development Economics":1,"Political Science":1,Psychology:1,Sociology:1,"South Asian Studies":1,"Women's, Gender, and Sexuality Studies":1,"Applied Mathematics":2,Astronomy:2,Chemistry:2,"Earth and Planetary Sciences":2,"Geology and Geophysics":2,Mathematics:2,Physics:2,Statistics:2,"Biological and Biomedical Sciences":3,"Cell Biology":3,"Cellular and Molecular Physiology":3,"Computational Biology and Bioinformatics":3,"Ecology and Evolutionary Biology":3,"Environmental Studies":3,"Forestry and Environmental Studies":3,Genetics:3,Immunobiology:3,MCDB:3,Microbiology:3,"Molecular Biophysics and Biochemistry":3,"Molecular, Cellular and Developmental Biology":3,Neuroscience:3,"Applied Physics":4,"Biomedical Engineering":4,"Biomedical Informatics and Data Science":4,"Chemical and Environmental Engineering":4,"Computer Science":4,"Electrical Engineering":4,"Engineering and Applied Science":4,"Mechanical Engineering and Materials Science":4,Anesthesiology:5,Biostatistics:5,"Child Study Center":5,"Chronic Disease Epidemiology":5,"Comparative Medicine":5,Dermatology:5,"Emergency Medicine":5,"Environmental Health Sciences":5,"Epidemiology of Microbial Diseases":5,"Experimental Pathology":5,"Health Care Management":5,"Health Policy and Management":5,"Internal Medicine":5,"Investigative Medicine":5,"Laboratory Medicine":5,Medicine:5,"Microbial Pathogenesis":5,Neurology:5,Neurosurgery:5,Nursing:5,"Obstetrics, Gynecology and Reproductive Sciences":5,"Ophthalmology and Visual Science":5,"Orthopaedics and Rehabilitation":5,Pathology:5,Pediatrics:5,Pharmacology:5,Psychiatry:5,"Public Health":5,"Radiology and Biomedical Imaging":5,Surgery:5,"Therapeutic Radiology":5,Urology:5,"East Asian Languages and Literatures":6,"East Asian Studies":6,French:6,German:6,Italian:6,Linguistics:6,"Near Eastern Langauges and Civilizations":6,"Slavic Languages and Literatures":6,"Spanish and Portuguese":6,"Latin American Studies":6,"Modern Middle East Studies":6,"Hellenic Studies":6,Law:7,Management:7},DS=({isOpen:n,onClose:r,listing:o,favListingsIds:l,updateFavorite:s})=>{const[c,f]=R.useState(o.id==="create"),[p,h]=R.useState(l.includes(o.id)),[g,y]=R.useState(!0),{user:v}=R.useContext(mn),S=["bg-blue-200","bg-green-200","bg-yellow-200","bg-red-200","bg-purple-200","bg-pink-200","bg-teal-200","bg-orange-200"],j=()=>o.hiringStatus<0?"bg-red-500":o.hiringStatus===0?"bg-yellow-500":"bg-green-500",w=()=>o.hiringStatus<0?"Lab not seeking applicants":o.hiringStatus===0?"Lab open to applicants":"Lab seeking applicants",x=C=>{C.target===C.currentTarget&&r()};R.useEffect(()=>{l&&h(l.includes(o.id))},[l]),R.useEffect(()=>{v&&v.userConfirmed&&["admin","professor","faculty"].includes(v.userType)&&y(!1)},[]),R.useEffect(()=>(n&&(document.body.style.overflow="hidden"),()=>{document.body.style.overflow="auto"}),[n]);const _=C=>{C.stopPropagation(),o.favorites=p?o.favorites-1:o.favorites+1,o.favorites<0&&(o.favorites=0),s(o.id,!p)},T=C=>C?C.startsWith("http://")||C.startsWith("https://")?C:`https://${C}`:"";return!n||!o?null:m.jsx("div",{className:"fixed inset-0 bg-black/65 z-50 flex items-center justify-center overflow-y-auto p-4 pt-24",onClick:x,children:m.jsxs("div",{className:"bg-white rounded-lg shadow-xl w-full max-w-4xl max-h-[80vh] overflow-y-auto",onClick:C=>C.stopPropagation(),children:[m.jsx("div",{className:`${j()} h-2 w-full rounded-t-lg`}),m.jsxs("div",{className:"p-6 relative",children:[m.jsxs("div",{className:"absolute top-4 right-4",children:[!c&&m.jsx("a",{onClick:_,className:"inline-block",children:m.jsx("button",{className:"p-1 hover:bg-gray-100 rounded-full mr-2","aria-label":p?"Remove from favorites":"Add to favorites",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",className:"transition-colors h-6 w-6",fill:p?"#FFDA7B":"none",stroke:p?"#F0C04A":"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:m.jsx("path",{d:"M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z"})})})}),m.jsx("button",{onClick:r,className:"p-1 rounded-full hover:bg-gray-100","aria-label":"Close",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-6 w-6",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),m.jsx("div",{className:"mb-6 pr-20",children:m.jsxs("div",{className:"flex flex-col md:flex-row md:items-center gap-2",children:[m.jsx("h2",{className:"text-2xl font-bold md:max-w-[400px] lg:max-w-[600px]",children:o.title}),m.jsx("span",{className:`${j()} mt-2 md:mt-0 md:ml-2 text-white text-xs px-2 py-1 rounded-full inline-block w-fit`,children:w()})]})}),m.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-6",children:[m.jsxs("div",{className:"col-span-1",children:[m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Professors"}),m.jsx("div",{className:"space-y-2",children:[`${o.ownerFirstName} ${o.ownerLastName}`,...o.professorNames].map((C,N)=>m.jsxs("div",{className:"flex items-center",children:[m.jsx("div",{className:"w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center mr-2",children:C.charAt(0).toUpperCase()}),m.jsx("span",{children:C})]},N))})]}),m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Departments"}),m.jsx("div",{className:"flex flex-wrap gap-2",children:o.departments.map(C=>m.jsx("span",{className:`${Object.keys(It).includes(C)?S[It[C]]:"bg-gray-200"} text-gray-900 text-xs rounded px-2 py-1`,children:C},C))})]}),m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Contact Information"}),m.jsxs("div",{className:"mb-4",children:[m.jsx("h4",{className:"text-md font-medium",children:"Emails"}),m.jsx("ul",{className:"mt-1 space-y-1",children:[o.ownerEmail,...o.emails].map((C,N)=>m.jsx("li",{children:m.jsx("a",{href:`mailto:${C}`,className:"text-blue-600 hover:underline",children:C})},N))})]}),o.websites&&o.websites.length>0&&m.jsxs("div",{children:[m.jsx("h4",{className:"text-md font-medium",children:"Websites"}),m.jsx("ul",{className:"mt-1 space-y-1",children:o.websites.map((C,N)=>m.jsx("li",{className:"truncate",children:m.jsx("a",{href:T(C),target:"_blank",rel:"noopener noreferrer",className:"text-blue-600 hover:underline",children:C})},N))})]})]}),m.jsxs("section",{children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Stats"}),m.jsxs("div",{className:"space-y-2 text-sm",children:[!g&&m.jsxs(m.Fragment,{children:[m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Views:"}),m.jsx("span",{className:"font-medium",children:o.views})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Favorites:"}),m.jsx("span",{className:"font-medium",children:o.favorites})]})]}),o.established&&m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Lab Established:"}),m.jsx("span",{className:"font-medium",children:o.established})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Listing Created:"}),m.jsx("span",{className:"font-medium",children:new Date(o.createdAt).toLocaleDateString()})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Listing Updated:"}),m.jsx("span",{className:"font-medium",children:new Date(o.updatedAt).toLocaleDateString()})]})]})]})]}),m.jsxs("div",{className:"col-span-1 md:col-span-2",children:[m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"About"}),m.jsx("div",{className:"whitespace-pre-wrap",children:o.description})]}),o.archived&&m.jsxs("div",{className:"mt-6 p-3 bg-red-100 text-red-700 rounded-lg",children:[m.jsx("div",{className:"font-semibold",children:"This listing is archived"}),m.jsx("div",{className:"text-sm",children:"Archived listings are not visible in search results or as favorites."})]})]})]})]})]})})};var as={exports:{}},lf,q0;function Zv(){return q0||(q0=1,lf=function(r,o){return function(){for(var s=new Array(arguments.length),c=0;c"u"}function s(E){return E!==null&&!l(E)&&E.constructor!==null&&!l(E.constructor)&&typeof E.constructor.isBuffer=="function"&&E.constructor.isBuffer(E)}function c(E){return r.call(E)==="[object ArrayBuffer]"}function f(E){return r.call(E)==="[object FormData]"}function p(E){var z;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?z=ArrayBuffer.isView(E):z=E&&E.buffer&&c(E.buffer),z}function h(E){return typeof E=="string"}function g(E){return typeof E=="number"}function y(E){return E!==null&&typeof E=="object"}function v(E){if(r.call(E)!=="[object Object]")return!1;var z=Object.getPrototypeOf(E);return z===null||z===Object.prototype}function S(E){return r.call(E)==="[object Date]"}function j(E){return r.call(E)==="[object File]"}function w(E){return r.call(E)==="[object Blob]"}function x(E){return r.call(E)==="[object Function]"}function _(E){return y(E)&&x(E.pipe)}function T(E){return r.call(E)==="[object URLSearchParams]"}function C(E){return E.trim?E.trim():E.replace(/^\s+|\s+$/g,"")}function N(){return typeof navigator<"u"&&(navigator.product==="ReactNative"||navigator.product==="NativeScript"||navigator.product==="NS")?!1:typeof window<"u"&&typeof document<"u"}function M(E,z){if(!(E===null||typeof E>"u"))if(typeof E!="object"&&(E=[E]),o(E))for(var D=0,B=E.length;D"u"||(n.isArray(y)?v=v+"[]":y=[y],n.forEach(y,function(j){n.isDate(j)?j=j.toISOString():n.isObject(j)&&(j=JSON.stringify(j)),p.push(r(v)+"="+r(j))}))}),f=p.join("&")}if(f){var h=l.indexOf("#");h!==-1&&(l=l.slice(0,h)),l+=(l.indexOf("?")===-1?"?":"&")+f}return l},sf}var uf,G0;function zS(){if(G0)return uf;G0=1;var n=Gt();function r(){this.handlers=[]}return r.prototype.use=function(l,s,c){return this.handlers.push({fulfilled:l,rejected:s,synchronous:c?c.synchronous:!1,runWhen:c?c.runWhen:null}),this.handlers.length-1},r.prototype.eject=function(l){this.handlers[l]&&(this.handlers[l]=null)},r.prototype.forEach=function(l){n.forEach(this.handlers,function(c){c!==null&&l(c)})},uf=r,uf}var cf,V0;function LS(){if(V0)return cf;V0=1;var n=Gt();return cf=function(o,l){n.forEach(o,function(c,f){f!==l&&f.toUpperCase()===l.toUpperCase()&&(o[l]=c,delete o[f])})},cf}var ff,X0;function Wv(){return X0||(X0=1,ff=function(r,o,l,s,c){return r.config=o,l&&(r.code=l),r.request=s,r.response=c,r.isAxiosError=!0,r.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code,status:this.response&&this.response.status?this.response.status:null}},r}),ff}var df,F0;function Jv(){return F0||(F0=1,df={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1}),df}var pf,K0;function ey(){if(K0)return pf;K0=1;var n=Wv();return pf=function(o,l,s,c,f){var p=new Error(o);return n(p,l,s,c,f)},pf}var hf,Q0;function BS(){if(Q0)return hf;Q0=1;var n=ey();return hf=function(o,l,s){var c=s.config.validateStatus;!s.status||!c||c(s.status)?o(s):l(n("Request failed with status code "+s.status,s.config,null,s.request,s))},hf}var mf,Z0;function US(){if(Z0)return mf;Z0=1;var n=Gt();return mf=n.isStandardBrowserEnv()?function(){return{write:function(l,s,c,f,p,h){var g=[];g.push(l+"="+encodeURIComponent(s)),n.isNumber(c)&&g.push("expires="+new Date(c).toGMTString()),n.isString(f)&&g.push("path="+f),n.isString(p)&&g.push("domain="+p),h===!0&&g.push("secure"),document.cookie=g.join("; ")},read:function(l){var s=document.cookie.match(new RegExp("(^|;\\s*)("+l+")=([^;]*)"));return s?decodeURIComponent(s[3]):null},remove:function(l){this.write(l,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}(),mf}var gf,I0;function $S(){return I0||(I0=1,gf=function(r){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(r)}),gf}var vf,W0;function HS(){return W0||(W0=1,vf=function(r,o){return o?r.replace(/\/+$/,"")+"/"+o.replace(/^\/+/,""):r}),vf}var yf,J0;function qS(){if(J0)return yf;J0=1;var n=$S(),r=HS();return yf=function(l,s){return l&&!n(s)?r(l,s):s},yf}var bf,eg;function PS(){if(eg)return bf;eg=1;var n=Gt(),r=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];return bf=function(l){var s={},c,f,p;return l&&n.forEach(l.split(` -`),function(g){if(p=g.indexOf(":"),c=n.trim(g.substr(0,p)).toLowerCase(),f=n.trim(g.substr(p+1)),c){if(s[c]&&r.indexOf(c)>=0)return;c==="set-cookie"?s[c]=(s[c]?s[c]:[]).concat([f]):s[c]=s[c]?s[c]+", "+f:f}}),s},bf}var xf,tg;function YS(){if(tg)return xf;tg=1;var n=Gt();return xf=n.isStandardBrowserEnv()?function(){var o=/(msie|trident)/i.test(navigator.userAgent),l=document.createElement("a"),s;function c(f){var p=f;return o&&(l.setAttribute("href",p),p=l.href),l.setAttribute("href",p),{href:l.href,protocol:l.protocol?l.protocol.replace(/:$/,""):"",host:l.host,search:l.search?l.search.replace(/^\?/,""):"",hash:l.hash?l.hash.replace(/^#/,""):"",hostname:l.hostname,port:l.port,pathname:l.pathname.charAt(0)==="/"?l.pathname:"/"+l.pathname}}return s=c(window.location.href),function(p){var h=n.isString(p)?c(p):p;return h.protocol===s.protocol&&h.host===s.host}}():function(){return function(){return!0}}(),xf}var Sf,ng;function Ts(){if(ng)return Sf;ng=1;function n(r){this.message=r}return n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,Sf=n,Sf}var wf,ag;function rg(){if(ag)return wf;ag=1;var n=Gt(),r=BS(),o=US(),l=Iv(),s=qS(),c=PS(),f=YS(),p=ey(),h=Jv(),g=Ts();return wf=function(v){return new Promise(function(j,w){var x=v.data,_=v.headers,T=v.responseType,C;function N(){v.cancelToken&&v.cancelToken.unsubscribe(C),v.signal&&v.signal.removeEventListener("abort",C)}n.isFormData(x)&&delete _["Content-Type"];var M=new XMLHttpRequest;if(v.auth){var O=v.auth.username||"",G=v.auth.password?unescape(encodeURIComponent(v.auth.password)):"";_.Authorization="Basic "+btoa(O+":"+G)}var A=s(v.baseURL,v.url);M.open(v.method.toUpperCase(),l(A,v.params,v.paramsSerializer),!0),M.timeout=v.timeout;function E(){if(M){var D="getAllResponseHeaders"in M?c(M.getAllResponseHeaders()):null,B=!T||T==="text"||T==="json"?M.responseText:M.response,q={data:B,status:M.status,statusText:M.statusText,headers:D,config:v,request:M};r(function(ne){j(ne),N()},function(ne){w(ne),N()},q),M=null}}if("onloadend"in M?M.onloadend=E:M.onreadystatechange=function(){!M||M.readyState!==4||M.status===0&&!(M.responseURL&&M.responseURL.indexOf("file:")===0)||setTimeout(E)},M.onabort=function(){M&&(w(p("Request aborted",v,"ECONNABORTED",M)),M=null)},M.onerror=function(){w(p("Network Error",v,null,M)),M=null},M.ontimeout=function(){var B=v.timeout?"timeout of "+v.timeout+"ms exceeded":"timeout exceeded",q=v.transitional||h;v.timeoutErrorMessage&&(B=v.timeoutErrorMessage),w(p(B,v,q.clarifyTimeoutError?"ETIMEDOUT":"ECONNABORTED",M)),M=null},n.isStandardBrowserEnv()){var z=(v.withCredentials||f(A))&&v.xsrfCookieName?o.read(v.xsrfCookieName):void 0;z&&(_[v.xsrfHeaderName]=z)}"setRequestHeader"in M&&n.forEach(_,function(B,q){typeof x>"u"&&q.toLowerCase()==="content-type"?delete _[q]:M.setRequestHeader(q,B)}),n.isUndefined(v.withCredentials)||(M.withCredentials=!!v.withCredentials),T&&T!=="json"&&(M.responseType=v.responseType),typeof v.onDownloadProgress=="function"&&M.addEventListener("progress",v.onDownloadProgress),typeof v.onUploadProgress=="function"&&M.upload&&M.upload.addEventListener("progress",v.onUploadProgress),(v.cancelToken||v.signal)&&(C=function(D){M&&(w(!D||D&&D.type?new g("canceled"):D),M.abort(),M=null)},v.cancelToken&&v.cancelToken.subscribe(C),v.signal&&(v.signal.aborted?C():v.signal.addEventListener("abort",C))),x||(x=null),M.send(x)})},wf}var Ef,ig;function yd(){if(ig)return Ef;ig=1;var n=Gt(),r=LS(),o=Wv(),l=Jv(),s={"Content-Type":"application/x-www-form-urlencoded"};function c(g,y){!n.isUndefined(g)&&n.isUndefined(g["Content-Type"])&&(g["Content-Type"]=y)}function f(){var g;return(typeof XMLHttpRequest<"u"||typeof process<"u"&&Object.prototype.toString.call(process)==="[object process]")&&(g=rg()),g}function p(g,y,v){if(n.isString(g))try{return(y||JSON.parse)(g),n.trim(g)}catch(S){if(S.name!=="SyntaxError")throw S}return(v||JSON.stringify)(g)}var h={transitional:l,adapter:f(),transformRequest:[function(y,v){return r(v,"Accept"),r(v,"Content-Type"),n.isFormData(y)||n.isArrayBuffer(y)||n.isBuffer(y)||n.isStream(y)||n.isFile(y)||n.isBlob(y)?y:n.isArrayBufferView(y)?y.buffer:n.isURLSearchParams(y)?(c(v,"application/x-www-form-urlencoded;charset=utf-8"),y.toString()):n.isObject(y)||v&&v["Content-Type"]==="application/json"?(c(v,"application/json"),p(y)):y}],transformResponse:[function(y){var v=this.transitional||h.transitional,S=v&&v.silentJSONParsing,j=v&&v.forcedJSONParsing,w=!S&&this.responseType==="json";if(w||j&&n.isString(y)&&y.length)try{return JSON.parse(y)}catch(x){if(w)throw x.name==="SyntaxError"?o(x,this,"E_JSON_PARSE"):x}return y}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,validateStatus:function(y){return y>=200&&y<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};return n.forEach(["delete","get","head"],function(y){h.headers[y]={}}),n.forEach(["post","put","patch"],function(y){h.headers[y]=n.merge(s)}),Ef=h,Ef}var Cf,lg;function GS(){if(lg)return Cf;lg=1;var n=Gt(),r=yd();return Cf=function(l,s,c){var f=this||r;return n.forEach(c,function(h){l=h.call(f,l,s)}),l},Cf}var Rf,og;function ty(){return og||(og=1,Rf=function(r){return!!(r&&r.__CANCEL__)}),Rf}var Tf,sg;function VS(){if(sg)return Tf;sg=1;var n=Gt(),r=GS(),o=ty(),l=yd(),s=Ts();function c(f){if(f.cancelToken&&f.cancelToken.throwIfRequested(),f.signal&&f.signal.aborted)throw new s("canceled")}return Tf=function(p){c(p),p.headers=p.headers||{},p.data=r.call(p,p.data,p.headers,p.transformRequest),p.headers=n.merge(p.headers.common||{},p.headers[p.method]||{},p.headers),n.forEach(["delete","get","head","post","put","patch","common"],function(y){delete p.headers[y]});var h=p.adapter||l.adapter;return h(p).then(function(y){return c(p),y.data=r.call(p,y.data,y.headers,p.transformResponse),y},function(y){return o(y)||(c(p),y&&y.response&&(y.response.data=r.call(p,y.response.data,y.response.headers,p.transformResponse))),Promise.reject(y)})},Tf}var jf,ug;function ny(){if(ug)return jf;ug=1;var n=Gt();return jf=function(o,l){l=l||{};var s={};function c(v,S){return n.isPlainObject(v)&&n.isPlainObject(S)?n.merge(v,S):n.isPlainObject(S)?n.merge({},S):n.isArray(S)?S.slice():S}function f(v){if(n.isUndefined(l[v])){if(!n.isUndefined(o[v]))return c(void 0,o[v])}else return c(o[v],l[v])}function p(v){if(!n.isUndefined(l[v]))return c(void 0,l[v])}function h(v){if(n.isUndefined(l[v])){if(!n.isUndefined(o[v]))return c(void 0,o[v])}else return c(void 0,l[v])}function g(v){if(v in l)return c(o[v],l[v]);if(v in o)return c(void 0,o[v])}var y={url:p,method:p,data:p,baseURL:h,transformRequest:h,transformResponse:h,paramsSerializer:h,timeout:h,timeoutMessage:h,withCredentials:h,adapter:h,responseType:h,xsrfCookieName:h,xsrfHeaderName:h,onUploadProgress:h,onDownloadProgress:h,decompress:h,maxContentLength:h,maxBodyLength:h,transport:h,httpAgent:h,httpsAgent:h,cancelToken:h,socketPath:h,responseEncoding:h,validateStatus:g};return n.forEach(Object.keys(o).concat(Object.keys(l)),function(S){var j=y[S]||f,w=j(S);n.isUndefined(w)&&j!==g||(s[S]=w)}),s},jf}var Of,cg;function ay(){return cg||(cg=1,Of={version:"0.26.1"}),Of}var Nf,fg;function XS(){if(fg)return Nf;fg=1;var n=ay().version,r={};["object","boolean","number","function","string","symbol"].forEach(function(s,c){r[s]=function(p){return typeof p===s||"a"+(c<1?"n ":" ")+s}});var o={};r.transitional=function(c,f,p){function h(g,y){return"[Axios v"+n+"] Transitional option '"+g+"'"+y+(p?". "+p:"")}return function(g,y,v){if(c===!1)throw new Error(h(y," has been removed"+(f?" in "+f:"")));return f&&!o[y]&&(o[y]=!0,console.warn(h(y," has been deprecated since v"+f+" and will be removed in the near future"))),c?c(g,y,v):!0}};function l(s,c,f){if(typeof s!="object")throw new TypeError("options must be an object");for(var p=Object.keys(s),h=p.length;h-- >0;){var g=p[h],y=c[g];if(y){var v=s[g],S=v===void 0||y(v,g,s);if(S!==!0)throw new TypeError("option "+g+" must be "+S);continue}if(f!==!0)throw Error("Unknown option "+g)}}return Nf={assertOptions:l,validators:r},Nf}var _f,dg;function FS(){if(dg)return _f;dg=1;var n=Gt(),r=Iv(),o=zS(),l=VS(),s=ny(),c=XS(),f=c.validators;function p(h){this.defaults=h,this.interceptors={request:new o,response:new o}}return p.prototype.request=function(g,y){typeof g=="string"?(y=y||{},y.url=g):y=g||{},y=s(this.defaults,y),y.method?y.method=y.method.toLowerCase():this.defaults.method?y.method=this.defaults.method.toLowerCase():y.method="get";var v=y.transitional;v!==void 0&&c.assertOptions(v,{silentJSONParsing:f.transitional(f.boolean),forcedJSONParsing:f.transitional(f.boolean),clarifyTimeoutError:f.transitional(f.boolean)},!1);var S=[],j=!0;this.interceptors.request.forEach(function(O){typeof O.runWhen=="function"&&O.runWhen(y)===!1||(j=j&&O.synchronous,S.unshift(O.fulfilled,O.rejected))});var w=[];this.interceptors.response.forEach(function(O){w.push(O.fulfilled,O.rejected)});var x;if(!j){var _=[l,void 0];for(Array.prototype.unshift.apply(_,S),_=_.concat(w),x=Promise.resolve(y);_.length;)x=x.then(_.shift(),_.shift());return x}for(var T=y;S.length;){var C=S.shift(),N=S.shift();try{T=C(T)}catch(M){N(M);break}}try{x=l(T)}catch(M){return Promise.reject(M)}for(;w.length;)x=x.then(w.shift(),w.shift());return x},p.prototype.getUri=function(g){return g=s(this.defaults,g),r(g.url,g.params,g.paramsSerializer).replace(/^\?/,"")},n.forEach(["delete","get","head","options"],function(g){p.prototype[g]=function(y,v){return this.request(s(v||{},{method:g,url:y,data:(v||{}).data}))}}),n.forEach(["post","put","patch"],function(g){p.prototype[g]=function(y,v,S){return this.request(s(S||{},{method:g,url:y,data:v}))}}),_f=p,_f}var Af,pg;function KS(){if(pg)return Af;pg=1;var n=Ts();function r(o){if(typeof o!="function")throw new TypeError("executor must be a function.");var l;this.promise=new Promise(function(f){l=f});var s=this;this.promise.then(function(c){if(s._listeners){var f,p=s._listeners.length;for(f=0;f{const[s,c]=R.useState([]),[f,p]=R.useState(0),[h,g]=R.useState(!1),[y,v]=R.useState(r.includes(n.id)),[S,j]=R.useState(!1),w=R.useRef(null),{user:x}=R.useContext(mn),_=["bg-blue-200","bg-green-200","bg-yellow-200","bg-red-200","bg-purple-200","bg-pink-200","bg-teal-200","bg-orange-200"],T=()=>n.hiringStatus<0?"bg-red-500":n.hiringStatus===0?"bg-yellow-500":"bg-green-500",C=()=>n.hiringStatus<0?"Lab not seeking applicants":n.hiringStatus===0?"Lab open to applicants":"Lab seeking applicants";R.useEffect(()=>{r&&v(r.includes(n.id))},[r]),R.useEffect(()=>{if(!w.current)return;const G=()=>{const A=w.current;if(!A)return;const E=A.clientWidth;let z=0;const D=[];p(0);const B=document.createElement("span");B.className="bg-blue-200 text-gray-900 text-xs rounded px-1 py-0.5 mt-2 mr-2",B.style.visibility="hidden",B.style.position="absolute",document.body.appendChild(B);for(let q=0;qE&&(D.pop(),p(n.departments.length-D.length))}document.body.removeChild(B),c(D)};return G(),window.addEventListener("resize",G),()=>window.removeEventListener("resize",G)},[n]);const N=G=>{G.stopPropagation(),n.favorites=y?n.favorites-1:n.favorites+1,n.favorites<0&&(n.favorites=0),o(n.id,!y)},M=()=>{S||(xt.put(`newListings/${n.id}/addView`,{withCredentials:!0}).catch(G=>{console.log("Could not add view for listing"),n.views=n.views-1}),n.views=n.views+1,j(!0)),l(n)},O=G=>G?G.startsWith("http://")||G.startsWith("https://")?G:`https://${G}`:"";return n?m.jsx("div",{className:"mb-4 relative",children:m.jsxs("div",{className:"flex relative z-10 rounded-md shadow",children:[m.jsx("div",{className:`${T()} cursor-pointer rounded-l flex-shrink-0 relative`,style:{width:"6px"},onMouseEnter:()=>g(!0),onMouseLeave:()=>g(!1),children:h&&m.jsx("div",{className:`${T()} absolute top-1/2 left-4 -translate-y-1/2 text-white text-xs rounded-full py-1 px-2 z-10 whitespace-nowrap shadow`,children:C()})}),m.jsxs("div",{className:"p-4 flex-grow grid grid-cols-3 md:grid-cols-12 cursor-pointer bg-white hover:bg-gray-100 border border-gray-300 rounded-r",onClick:M,children:[m.jsxs("div",{className:"col-span-2 md:col-span-4",children:[m.jsx("p",{className:"text-lg font-semibold mb-3",style:{lineHeight:"1.2rem",height:"1.2rem",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:n.title}),m.jsxs("p",{className:"text-sm text-gray-700",style:{overflow:"hidden",display:"-webkit-box",WebkitLineClamp:1,WebkitBoxOrient:"vertical"},children:["Professors: ",[`${n.ownerFirstName} ${n.ownerLastName}`,...n.professorNames].join(", ")]}),m.jsx("div",{ref:w,className:"flex overflow-hidden",style:{whiteSpace:"nowrap"},children:s.length>0?m.jsxs(m.Fragment,{children:[s.map(G=>m.jsx("span",{className:`${Object.keys(It).includes(G)?_[It[G]]:"bg-gray-200"} text-gray-900 text-xs rounded px-1 py-0.5 mt-3 mr-2`,style:{display:"inline-block",whiteSpace:"nowrap"},children:G},G)),f>0&&m.jsxs("span",{className:"bg-gray-200 text-gray-900 text-xs rounded px-1 py-0.5 mt-3",style:{display:"inline-block",whiteSpace:"nowrap"},children:["+",f," more"]})]}):m.jsx("div",{className:"mt-3 flex",children:m.jsx("span",{className:"invisible bg-gray-200 text-gray-900 text-xs rounded px-1 py-0.5 mr-2",style:{display:"inline-block"},children:"placeholder"})})})]}),m.jsxs("div",{className:"col-span-6 hidden md:flex align-middle",children:[m.jsx("div",{className:"flex-shrink-0 border-l border-gray-300 mx-4"}),m.jsx("p",{className:"flex-grow text-gray-800 text-sm overflow-hidden overflow-ellipsis",style:{display:"-webkit-box",WebkitLineClamp:4,WebkitBoxOrient:"vertical"},children:n.description})]}),m.jsxs("div",{className:"flex flex-col col-span-1 md:col-span-2 items-end",children:[m.jsxs("div",{children:[n.websites&&n.websites.length>0&&m.jsx("a",{href:O(n.websites[0]),className:"mr-1",onClick:G=>G.stopPropagation(),target:"_blank",rel:"noopener noreferrer",children:m.jsx("button",{className:"p-1 rounded-full hover:bg-gray-200",children:m.jsx("img",{src:"/assets/icons/new-link.png",alt:"Lab Website",className:"w-5 h-5"})})}),m.jsx("a",{onClick:N,className:"inline-block",children:m.jsx("button",{className:"p-1 hover:bg-gray-200 rounded-full","aria-label":y?"Remove from favorites":"Add to favorites",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",className:"transition-colors",fill:y?"#FFDA7B":"none",stroke:y?"#F0C04A":"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:m.jsx("path",{d:"M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z"})})})})]}),m.jsx("div",{className:"flex-grow"}),m.jsx("p",{className:"text-[8px] mb-0.5 text-gray-700",style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:"Last Update"}),m.jsx("p",{className:"text-sm text-gray-700",style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:new Date(n.updatedAt).toLocaleDateString()})]})]})]},n.id)}):null},iy=({sortBy:n,setSortBy:r,sortOptions:o,searchHub:l})=>{var S;const[s,c]=R.useState(!1),[f,p]=R.useState(-1),h=R.useRef(null),g=R.useRef(null),y=j=>{r(j),c(!1),g.current&&g.current.blur()},v=j=>{switch(j.key){case"ArrowDown":j.preventDefault(),p(w=>ww>0?w-1:0);break;case"Enter":j.preventDefault(),f>=0&&fj.value===n))==null?void 0:S.label)||"",onClick:()=>{c(!0)},onKeyDown:v,onFocus:()=>c(!0),onBlur:()=>{setTimeout(()=>{var j;(j=h.current)!=null&&j.contains(document.activeElement)||c(!1)},100)},className:`appearance-none border rounded w-full ${l?"h-full":"py-2"} px-3 pr-10 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer`}),m.jsx("div",{className:"absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 cursor-pointer",onClick:()=>{c(!s),!s&&g.current&&g.current.focus()},children:m.jsx("svg",{className:"fill-current h-4 w-4",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})})]}),s&&m.jsx("div",{className:"absolute left-0 right-0 bg-white rounded-lg z-30 shadow-lg border overflow-hidden mt-1 max-h-[350px] border-gray-300",tabIndex:-1,children:m.jsx("ul",{className:"max-h-[350px] overflow-y-auto",tabIndex:-1,children:o.map((j,w)=>m.jsxs("li",{onClick:()=>y(j.value),className:`p-2 cursor-pointer flex items-center justify-between ${f===w?"bg-blue-100":"hover:bg-gray-100"}`,tabIndex:-1,onMouseDown:x=>x.preventDefault(),children:[m.jsx("span",{children:j.label}),n===j.value&&m.jsx("svg",{className:"h-4 w-4 text-blue-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M5 13l4 4L19 7"})})]},w))})})]})})};var Pt={};function nw(n){if(n.sheet)return n.sheet;for(var r=0;r0?Rt(hi,--Yt):0,si--,ht===10&&(si=1,Os--),ht}function Zt(){return ht=Yt2||El(ht)>3?"":" "}function mw(n,r){for(;--r&&Zt()&&!(ht<48||ht>102||ht>57&&ht<65||ht>70&&ht<97););return Al(n,cs()+(r<6&&Bn()==32&&Zt()==32))}function Jf(n){for(;Zt();)switch(ht){case n:return Yt;case 34:case 39:n!==34&&n!==39&&Jf(ht);break;case 40:n===41&&Jf(n);break;case 92:Zt();break}return Yt}function gw(n,r){for(;Zt()&&n+ht!==57;)if(n+ht===84&&Bn()===47)break;return"/*"+Al(r,Yt-1)+"*"+js(n===47?n:Zt())}function vw(n){for(;!El(Bn());)Zt();return Al(n,Yt)}function yw(n){return fy(ds("",null,null,null,[""],n=cy(n),0,[0],n))}function ds(n,r,o,l,s,c,f,p,h){for(var g=0,y=0,v=f,S=0,j=0,w=0,x=1,_=1,T=1,C=0,N="",M=s,O=c,G=l,A=N;_;)switch(w=C,C=Zt()){case 40:if(w!=108&&Rt(A,v-1)==58){Wf(A+=Ue(fs(C),"&","&\f"),"&\f")!=-1&&(T=-1);break}case 34:case 39:case 91:A+=fs(C);break;case 9:case 10:case 13:case 32:A+=hw(w);break;case 92:A+=mw(cs()-1,7);continue;case 47:switch(Bn()){case 42:case 47:rs(bw(gw(Zt(),cs()),r,o),h);break;default:A+="/"}break;case 123*x:p[g++]=zn(A)*T;case 125*x:case 59:case 0:switch(C){case 0:case 125:_=0;case 59+y:T==-1&&(A=Ue(A,/\f/g,"")),j>0&&zn(A)-v&&rs(j>32?bg(A+";",l,o,v-1):bg(Ue(A," ","")+";",l,o,v-2),h);break;case 59:A+=";";default:if(rs(G=yg(A,r,o,g,y,s,p,N,M=[],O=[],v),c),C===123)if(y===0)ds(A,r,G,G,M,c,v,p,O);else switch(S===99&&Rt(A,3)===110?100:S){case 100:case 108:case 109:case 115:ds(n,G,G,l&&rs(yg(n,G,G,0,0,s,p,N,s,M=[],v),O),s,O,v,p,l?M:O);break;default:ds(A,G,G,G,[""],O,0,p,O)}}g=y=j=0,x=T=1,N=A="",v=f;break;case 58:v=1+zn(A),j=w;default:if(x<1){if(C==123)--x;else if(C==125&&x++==0&&pw()==125)continue}switch(A+=js(C),C*x){case 38:T=y>0?1:(A+="\f",-1);break;case 44:p[g++]=(zn(A)-1)*T,T=1;break;case 64:Bn()===45&&(A+=fs(Zt())),S=Bn(),y=v=zn(N=A+=vw(cs())),C++;break;case 45:w===45&&zn(A)==2&&(x=0)}}return c}function yg(n,r,o,l,s,c,f,p,h,g,y){for(var v=s-1,S=s===0?c:[""],j=Sd(S),w=0,x=0,_=0;w0?S[T]+" "+C:Ue(C,/&\f/g,S[T])))&&(h[_++]=N);return Ns(n,r,o,s===0?bd:p,h,g,y)}function bw(n,r,o){return Ns(n,r,o,ly,js(dw()),wl(n,2,-2),0)}function bg(n,r,o,l){return Ns(n,r,o,xd,wl(n,0,l),wl(n,l+1,-1),l)}function li(n,r){for(var o="",l=Sd(n),s=0;s6)switch(Rt(n,r+1)){case 109:if(Rt(n,r+4)!==45)break;case 102:return Ue(n,/(.+:)(.+)-([^]+)/,"$1"+Be+"$2-$3$1"+gs+(Rt(n,r+3)==108?"$3":"$2-$3"))+n;case 115:return~Wf(n,"stretch")?py(Ue(n,"stretch","fill-available"),r)+n:n}break;case 4949:if(Rt(n,r+1)!==115)break;case 6444:switch(Rt(n,zn(n)-3-(~Wf(n,"!important")&&10))){case 107:return Ue(n,":",":"+Be)+n;case 101:return Ue(n,/(.+:)([^;!]+)(;|!.+)?/,"$1"+Be+(Rt(n,14)===45?"inline-":"")+"box$3$1"+Be+"$2$3$1"+_t+"$2box$3")+n}break;case 5936:switch(Rt(n,r+11)){case 114:return Be+n+_t+Ue(n,/[svh]\w+-[tblr]{2}/,"tb")+n;case 108:return Be+n+_t+Ue(n,/[svh]\w+-[tblr]{2}/,"tb-rl")+n;case 45:return Be+n+_t+Ue(n,/[svh]\w+-[tblr]{2}/,"lr")+n}return Be+n+_t+n+n}return n}var Ow=function(r,o,l,s){if(r.length>-1&&!r.return)switch(r.type){case xd:r.return=py(r.value,r.length);break;case oy:return li([cl(r,{value:Ue(r.value,"@","@"+Be)})],s);case bd:if(r.length)return fw(r.props,function(c){switch(cw(c,/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":return li([cl(r,{props:[Ue(c,/:(read-\w+)/,":"+gs+"$1")]})],s);case"::placeholder":return li([cl(r,{props:[Ue(c,/:(plac\w+)/,":"+Be+"input-$1")]}),cl(r,{props:[Ue(c,/:(plac\w+)/,":"+gs+"$1")]}),cl(r,{props:[Ue(c,/:(plac\w+)/,_t+"input-$1")]})],s)}return""})}},Nw=[Ow],hy=function(r){var o=r.key;if(o==="css"){var l=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(l,function(x){var _=x.getAttribute("data-emotion");_.indexOf(" ")!==-1&&(document.head.appendChild(x),x.setAttribute("data-s",""))})}var s=r.stylisPlugins||Nw,c={},f,p=[];f=r.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+o+' "]'),function(x){for(var _=x.getAttribute("data-emotion").split(" "),T=1;T<_.length;T++)c[_[T]]=!0;p.push(x)});var h,g=[Tw,jw];{var y,v=[xw,ww(function(x){y.insert(x)})],S=Sw(g.concat(s,v)),j=function(_){return li(yw(_),S)};h=function(_,T,C,N){y=C,j(_?_+"{"+T.styles+"}":T.styles),N&&(w.inserted[T.name]=!0)}}var w={key:o,sheet:new rw({key:o,container:f,nonce:r.nonce,speedy:r.speedy,prepend:r.prepend,insertionPoint:r.insertionPoint}),nonce:r.nonce,inserted:c,registered:{},insert:h};return w.sheet.hydrate(p),w};function ee(){return ee=Object.assign?Object.assign.bind():function(n){for(var r=1;r=0)return;c==="set-cookie"?s[c]=(s[c]?s[c]:[]).concat([f]):s[c]=s[c]?s[c]+", "+f:f}}),s},bf}var xf,tg;function YS(){if(tg)return xf;tg=1;var n=Gt();return xf=n.isStandardBrowserEnv()?function(){var o=/(msie|trident)/i.test(navigator.userAgent),l=document.createElement("a"),s;function c(f){var p=f;return o&&(l.setAttribute("href",p),p=l.href),l.setAttribute("href",p),{href:l.href,protocol:l.protocol?l.protocol.replace(/:$/,""):"",host:l.host,search:l.search?l.search.replace(/^\?/,""):"",hash:l.hash?l.hash.replace(/^#/,""):"",hostname:l.hostname,port:l.port,pathname:l.pathname.charAt(0)==="/"?l.pathname:"/"+l.pathname}}return s=c(window.location.href),function(p){var h=n.isString(p)?c(p):p;return h.protocol===s.protocol&&h.host===s.host}}():function(){return function(){return!0}}(),xf}var Sf,ng;function Ts(){if(ng)return Sf;ng=1;function n(r){this.message=r}return n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,Sf=n,Sf}var wf,ag;function rg(){if(ag)return wf;ag=1;var n=Gt(),r=BS(),o=US(),l=Iv(),s=qS(),c=PS(),f=YS(),p=ey(),h=Jv(),g=Ts();return wf=function(v){return new Promise(function(j,w){var x=v.data,_=v.headers,T=v.responseType,C;function N(){v.cancelToken&&v.cancelToken.unsubscribe(C),v.signal&&v.signal.removeEventListener("abort",C)}n.isFormData(x)&&delete _["Content-Type"];var M=new XMLHttpRequest;if(v.auth){var O=v.auth.username||"",G=v.auth.password?unescape(encodeURIComponent(v.auth.password)):"";_.Authorization="Basic "+btoa(O+":"+G)}var A=s(v.baseURL,v.url);M.open(v.method.toUpperCase(),l(A,v.params,v.paramsSerializer),!0),M.timeout=v.timeout;function E(){if(M){var D="getAllResponseHeaders"in M?c(M.getAllResponseHeaders()):null,B=!T||T==="text"||T==="json"?M.responseText:M.response,q={data:B,status:M.status,statusText:M.statusText,headers:D,config:v,request:M};r(function(ne){j(ne),N()},function(ne){w(ne),N()},q),M=null}}if("onloadend"in M?M.onloadend=E:M.onreadystatechange=function(){!M||M.readyState!==4||M.status===0&&!(M.responseURL&&M.responseURL.indexOf("file:")===0)||setTimeout(E)},M.onabort=function(){M&&(w(p("Request aborted",v,"ECONNABORTED",M)),M=null)},M.onerror=function(){w(p("Network Error",v,null,M)),M=null},M.ontimeout=function(){var B=v.timeout?"timeout of "+v.timeout+"ms exceeded":"timeout exceeded",q=v.transitional||h;v.timeoutErrorMessage&&(B=v.timeoutErrorMessage),w(p(B,v,q.clarifyTimeoutError?"ETIMEDOUT":"ECONNABORTED",M)),M=null},n.isStandardBrowserEnv()){var z=(v.withCredentials||f(A))&&v.xsrfCookieName?o.read(v.xsrfCookieName):void 0;z&&(_[v.xsrfHeaderName]=z)}"setRequestHeader"in M&&n.forEach(_,function(B,q){typeof x>"u"&&q.toLowerCase()==="content-type"?delete _[q]:M.setRequestHeader(q,B)}),n.isUndefined(v.withCredentials)||(M.withCredentials=!!v.withCredentials),T&&T!=="json"&&(M.responseType=v.responseType),typeof v.onDownloadProgress=="function"&&M.addEventListener("progress",v.onDownloadProgress),typeof v.onUploadProgress=="function"&&M.upload&&M.upload.addEventListener("progress",v.onUploadProgress),(v.cancelToken||v.signal)&&(C=function(D){M&&(w(!D||D&&D.type?new g("canceled"):D),M.abort(),M=null)},v.cancelToken&&v.cancelToken.subscribe(C),v.signal&&(v.signal.aborted?C():v.signal.addEventListener("abort",C))),x||(x=null),M.send(x)})},wf}var Ef,ig;function yd(){if(ig)return Ef;ig=1;var n=Gt(),r=LS(),o=Wv(),l=Jv(),s={"Content-Type":"application/x-www-form-urlencoded"};function c(g,y){!n.isUndefined(g)&&n.isUndefined(g["Content-Type"])&&(g["Content-Type"]=y)}function f(){var g;return(typeof XMLHttpRequest<"u"||typeof process<"u"&&Object.prototype.toString.call(process)==="[object process]")&&(g=rg()),g}function p(g,y,v){if(n.isString(g))try{return(y||JSON.parse)(g),n.trim(g)}catch(S){if(S.name!=="SyntaxError")throw S}return(v||JSON.stringify)(g)}var h={transitional:l,adapter:f(),transformRequest:[function(y,v){return r(v,"Accept"),r(v,"Content-Type"),n.isFormData(y)||n.isArrayBuffer(y)||n.isBuffer(y)||n.isStream(y)||n.isFile(y)||n.isBlob(y)?y:n.isArrayBufferView(y)?y.buffer:n.isURLSearchParams(y)?(c(v,"application/x-www-form-urlencoded;charset=utf-8"),y.toString()):n.isObject(y)||v&&v["Content-Type"]==="application/json"?(c(v,"application/json"),p(y)):y}],transformResponse:[function(y){var v=this.transitional||h.transitional,S=v&&v.silentJSONParsing,j=v&&v.forcedJSONParsing,w=!S&&this.responseType==="json";if(w||j&&n.isString(y)&&y.length)try{return JSON.parse(y)}catch(x){if(w)throw x.name==="SyntaxError"?o(x,this,"E_JSON_PARSE"):x}return y}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,validateStatus:function(y){return y>=200&&y<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};return n.forEach(["delete","get","head"],function(y){h.headers[y]={}}),n.forEach(["post","put","patch"],function(y){h.headers[y]=n.merge(s)}),Ef=h,Ef}var Cf,lg;function GS(){if(lg)return Cf;lg=1;var n=Gt(),r=yd();return Cf=function(l,s,c){var f=this||r;return n.forEach(c,function(h){l=h.call(f,l,s)}),l},Cf}var Rf,og;function ty(){return og||(og=1,Rf=function(r){return!!(r&&r.__CANCEL__)}),Rf}var Tf,sg;function VS(){if(sg)return Tf;sg=1;var n=Gt(),r=GS(),o=ty(),l=yd(),s=Ts();function c(f){if(f.cancelToken&&f.cancelToken.throwIfRequested(),f.signal&&f.signal.aborted)throw new s("canceled")}return Tf=function(p){c(p),p.headers=p.headers||{},p.data=r.call(p,p.data,p.headers,p.transformRequest),p.headers=n.merge(p.headers.common||{},p.headers[p.method]||{},p.headers),n.forEach(["delete","get","head","post","put","patch","common"],function(y){delete p.headers[y]});var h=p.adapter||l.adapter;return h(p).then(function(y){return c(p),y.data=r.call(p,y.data,y.headers,p.transformResponse),y},function(y){return o(y)||(c(p),y&&y.response&&(y.response.data=r.call(p,y.response.data,y.response.headers,p.transformResponse))),Promise.reject(y)})},Tf}var jf,ug;function ny(){if(ug)return jf;ug=1;var n=Gt();return jf=function(o,l){l=l||{};var s={};function c(v,S){return n.isPlainObject(v)&&n.isPlainObject(S)?n.merge(v,S):n.isPlainObject(S)?n.merge({},S):n.isArray(S)?S.slice():S}function f(v){if(n.isUndefined(l[v])){if(!n.isUndefined(o[v]))return c(void 0,o[v])}else return c(o[v],l[v])}function p(v){if(!n.isUndefined(l[v]))return c(void 0,l[v])}function h(v){if(n.isUndefined(l[v])){if(!n.isUndefined(o[v]))return c(void 0,o[v])}else return c(void 0,l[v])}function g(v){if(v in l)return c(o[v],l[v]);if(v in o)return c(void 0,o[v])}var y={url:p,method:p,data:p,baseURL:h,transformRequest:h,transformResponse:h,paramsSerializer:h,timeout:h,timeoutMessage:h,withCredentials:h,adapter:h,responseType:h,xsrfCookieName:h,xsrfHeaderName:h,onUploadProgress:h,onDownloadProgress:h,decompress:h,maxContentLength:h,maxBodyLength:h,transport:h,httpAgent:h,httpsAgent:h,cancelToken:h,socketPath:h,responseEncoding:h,validateStatus:g};return n.forEach(Object.keys(o).concat(Object.keys(l)),function(S){var j=y[S]||f,w=j(S);n.isUndefined(w)&&j!==g||(s[S]=w)}),s},jf}var Of,cg;function ay(){return cg||(cg=1,Of={version:"0.26.1"}),Of}var Nf,fg;function XS(){if(fg)return Nf;fg=1;var n=ay().version,r={};["object","boolean","number","function","string","symbol"].forEach(function(s,c){r[s]=function(p){return typeof p===s||"a"+(c<1?"n ":" ")+s}});var o={};r.transitional=function(c,f,p){function h(g,y){return"[Axios v"+n+"] Transitional option '"+g+"'"+y+(p?". "+p:"")}return function(g,y,v){if(c===!1)throw new Error(h(y," has been removed"+(f?" in "+f:"")));return f&&!o[y]&&(o[y]=!0,console.warn(h(y," has been deprecated since v"+f+" and will be removed in the near future"))),c?c(g,y,v):!0}};function l(s,c,f){if(typeof s!="object")throw new TypeError("options must be an object");for(var p=Object.keys(s),h=p.length;h-- >0;){var g=p[h],y=c[g];if(y){var v=s[g],S=v===void 0||y(v,g,s);if(S!==!0)throw new TypeError("option "+g+" must be "+S);continue}if(f!==!0)throw Error("Unknown option "+g)}}return Nf={assertOptions:l,validators:r},Nf}var _f,dg;function FS(){if(dg)return _f;dg=1;var n=Gt(),r=Iv(),o=zS(),l=VS(),s=ny(),c=XS(),f=c.validators;function p(h){this.defaults=h,this.interceptors={request:new o,response:new o}}return p.prototype.request=function(g,y){typeof g=="string"?(y=y||{},y.url=g):y=g||{},y=s(this.defaults,y),y.method?y.method=y.method.toLowerCase():this.defaults.method?y.method=this.defaults.method.toLowerCase():y.method="get";var v=y.transitional;v!==void 0&&c.assertOptions(v,{silentJSONParsing:f.transitional(f.boolean),forcedJSONParsing:f.transitional(f.boolean),clarifyTimeoutError:f.transitional(f.boolean)},!1);var S=[],j=!0;this.interceptors.request.forEach(function(O){typeof O.runWhen=="function"&&O.runWhen(y)===!1||(j=j&&O.synchronous,S.unshift(O.fulfilled,O.rejected))});var w=[];this.interceptors.response.forEach(function(O){w.push(O.fulfilled,O.rejected)});var x;if(!j){var _=[l,void 0];for(Array.prototype.unshift.apply(_,S),_=_.concat(w),x=Promise.resolve(y);_.length;)x=x.then(_.shift(),_.shift());return x}for(var T=y;S.length;){var C=S.shift(),N=S.shift();try{T=C(T)}catch(M){N(M);break}}try{x=l(T)}catch(M){return Promise.reject(M)}for(;w.length;)x=x.then(w.shift(),w.shift());return x},p.prototype.getUri=function(g){return g=s(this.defaults,g),r(g.url,g.params,g.paramsSerializer).replace(/^\?/,"")},n.forEach(["delete","get","head","options"],function(g){p.prototype[g]=function(y,v){return this.request(s(v||{},{method:g,url:y,data:(v||{}).data}))}}),n.forEach(["post","put","patch"],function(g){p.prototype[g]=function(y,v,S){return this.request(s(S||{},{method:g,url:y,data:v}))}}),_f=p,_f}var Af,pg;function KS(){if(pg)return Af;pg=1;var n=Ts();function r(o){if(typeof o!="function")throw new TypeError("executor must be a function.");var l;this.promise=new Promise(function(f){l=f});var s=this;this.promise.then(function(c){if(s._listeners){var f,p=s._listeners.length;for(f=0;f{const[s,c]=R.useState([]),[f,p]=R.useState(0),[h,g]=R.useState(!1),[y,v]=R.useState(r.includes(n.id)),[S,j]=R.useState(!1),w=R.useRef(null),{user:x}=R.useContext(mn),_=["bg-blue-200","bg-green-200","bg-yellow-200","bg-red-200","bg-purple-200","bg-pink-200","bg-teal-200","bg-orange-200"],T=()=>n.hiringStatus<0?"bg-red-500":n.hiringStatus===0?"bg-yellow-500":"bg-green-500",C=()=>n.hiringStatus<0?"Lab not seeking applicants":n.hiringStatus===0?"Lab open to applicants":"Lab seeking applicants";R.useEffect(()=>{r&&v(r.includes(n.id))},[r]),R.useEffect(()=>{if(!w.current)return;const G=()=>{const A=w.current;if(!A)return;const E=A.clientWidth;let z=0;const D=[];p(0);const B=document.createElement("span");B.className="bg-blue-200 text-gray-900 text-xs rounded px-1 py-0.5 mt-2 mr-2",B.style.visibility="hidden",B.style.position="absolute",document.body.appendChild(B);for(let q=0;qE&&(D.pop(),p(n.departments.length-D.length))}document.body.removeChild(B),c(D)};return G(),window.addEventListener("resize",G),()=>window.removeEventListener("resize",G)},[n]);const N=G=>{G.stopPropagation(),n.favorites=y?n.favorites-1:n.favorites+1,n.favorites<0&&(n.favorites=0),o(n.id,!y)},M=()=>{S||(xt.put(`listings/${n.id}/addView`,{withCredentials:!0}).catch(G=>{console.log("Could not add view for listing"),n.views=n.views-1}),n.views=n.views+1,j(!0)),l(n)},O=G=>G?G.startsWith("http://")||G.startsWith("https://")?G:`https://${G}`:"";return n?m.jsx("div",{className:"mb-4 relative",children:m.jsxs("div",{className:"flex relative z-10 rounded-md shadow",children:[m.jsx("div",{className:`${T()} cursor-pointer rounded-l flex-shrink-0 relative`,style:{width:"6px"},onMouseEnter:()=>g(!0),onMouseLeave:()=>g(!1),children:h&&m.jsx("div",{className:`${T()} absolute top-1/2 left-4 -translate-y-1/2 text-white text-xs rounded-full py-1 px-2 z-10 whitespace-nowrap shadow`,children:C()})}),m.jsxs("div",{className:"p-4 flex-grow grid grid-cols-3 md:grid-cols-12 cursor-pointer bg-white hover:bg-gray-100 border border-gray-300 rounded-r",onClick:M,children:[m.jsxs("div",{className:"col-span-2 md:col-span-4",children:[m.jsx("p",{className:"text-lg font-semibold mb-3",style:{lineHeight:"1.2rem",height:"1.2rem",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:n.title}),m.jsxs("p",{className:"text-sm text-gray-700",style:{overflow:"hidden",display:"-webkit-box",WebkitLineClamp:1,WebkitBoxOrient:"vertical"},children:["Professors: ",[`${n.ownerFirstName} ${n.ownerLastName}`,...n.professorNames].join(", ")]}),m.jsx("div",{ref:w,className:"flex overflow-hidden",style:{whiteSpace:"nowrap"},children:s.length>0?m.jsxs(m.Fragment,{children:[s.map(G=>m.jsx("span",{className:`${Object.keys(It).includes(G)?_[It[G]]:"bg-gray-200"} text-gray-900 text-xs rounded px-1 py-0.5 mt-3 mr-2`,style:{display:"inline-block",whiteSpace:"nowrap"},children:G},G)),f>0&&m.jsxs("span",{className:"bg-gray-200 text-gray-900 text-xs rounded px-1 py-0.5 mt-3",style:{display:"inline-block",whiteSpace:"nowrap"},children:["+",f," more"]})]}):m.jsx("div",{className:"mt-3 flex",children:m.jsx("span",{className:"invisible bg-gray-200 text-gray-900 text-xs rounded px-1 py-0.5 mr-2",style:{display:"inline-block"},children:"placeholder"})})})]}),m.jsxs("div",{className:"col-span-6 hidden md:flex align-middle",children:[m.jsx("div",{className:"flex-shrink-0 border-l border-gray-300 mx-4"}),m.jsx("p",{className:"flex-grow text-gray-800 text-sm overflow-hidden overflow-ellipsis",style:{display:"-webkit-box",WebkitLineClamp:4,WebkitBoxOrient:"vertical"},children:n.description})]}),m.jsxs("div",{className:"flex flex-col col-span-1 md:col-span-2 items-end",children:[m.jsxs("div",{children:[n.websites&&n.websites.length>0&&m.jsx("a",{href:O(n.websites[0]),className:"mr-1",onClick:G=>G.stopPropagation(),target:"_blank",rel:"noopener noreferrer",children:m.jsx("button",{className:"p-1 rounded-full hover:bg-gray-200",children:m.jsx("img",{src:"/assets/icons/new-link.png",alt:"Lab Website",className:"w-5 h-5"})})}),m.jsx("a",{onClick:N,className:"inline-block",children:m.jsx("button",{className:"p-1 hover:bg-gray-200 rounded-full","aria-label":y?"Remove from favorites":"Add to favorites",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",className:"transition-colors",fill:y?"#FFDA7B":"none",stroke:y?"#F0C04A":"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:m.jsx("path",{d:"M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z"})})})})]}),m.jsx("div",{className:"flex-grow"}),m.jsx("p",{className:"text-[8px] mb-0.5 text-gray-700",style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:"Last Update"}),m.jsx("p",{className:"text-sm text-gray-700",style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:new Date(n.updatedAt).toLocaleDateString()})]})]})]},n.id)}):null},iy=({sortBy:n,setSortBy:r,sortOptions:o,searchHub:l})=>{var S;const[s,c]=R.useState(!1),[f,p]=R.useState(-1),h=R.useRef(null),g=R.useRef(null),y=j=>{r(j),c(!1),g.current&&g.current.blur()},v=j=>{switch(j.key){case"ArrowDown":j.preventDefault(),p(w=>ww>0?w-1:0);break;case"Enter":j.preventDefault(),f>=0&&fj.value===n))==null?void 0:S.label)||"",onClick:()=>{c(!0)},onKeyDown:v,onFocus:()=>c(!0),onBlur:()=>{setTimeout(()=>{var j;(j=h.current)!=null&&j.contains(document.activeElement)||c(!1)},100)},className:`appearance-none border rounded w-full ${l?"h-full":"py-2"} px-3 pr-10 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer`}),m.jsx("div",{className:"absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 cursor-pointer",onClick:()=>{c(!s),!s&&g.current&&g.current.focus()},children:m.jsx("svg",{className:"fill-current h-4 w-4",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})})]}),s&&m.jsx("div",{className:"absolute left-0 right-0 bg-white rounded-lg z-30 shadow-lg border overflow-hidden mt-1 max-h-[350px] border-gray-300",tabIndex:-1,children:m.jsx("ul",{className:"max-h-[350px] overflow-y-auto",tabIndex:-1,children:o.map((j,w)=>m.jsxs("li",{onClick:()=>y(j.value),className:`p-2 cursor-pointer flex items-center justify-between ${f===w?"bg-blue-100":"hover:bg-gray-100"}`,tabIndex:-1,onMouseDown:x=>x.preventDefault(),children:[m.jsx("span",{children:j.label}),n===j.value&&m.jsx("svg",{className:"h-4 w-4 text-blue-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M5 13l4 4L19 7"})})]},w))})})]})})};var Pt={};function nw(n){if(n.sheet)return n.sheet;for(var r=0;r0?Rt(hi,--Yt):0,si--,ht===10&&(si=1,Os--),ht}function Zt(){return ht=Yt2||El(ht)>3?"":" "}function mw(n,r){for(;--r&&Zt()&&!(ht<48||ht>102||ht>57&&ht<65||ht>70&&ht<97););return Al(n,cs()+(r<6&&Bn()==32&&Zt()==32))}function Jf(n){for(;Zt();)switch(ht){case n:return Yt;case 34:case 39:n!==34&&n!==39&&Jf(ht);break;case 40:n===41&&Jf(n);break;case 92:Zt();break}return Yt}function gw(n,r){for(;Zt()&&n+ht!==57;)if(n+ht===84&&Bn()===47)break;return"/*"+Al(r,Yt-1)+"*"+js(n===47?n:Zt())}function vw(n){for(;!El(Bn());)Zt();return Al(n,Yt)}function yw(n){return fy(ds("",null,null,null,[""],n=cy(n),0,[0],n))}function ds(n,r,o,l,s,c,f,p,h){for(var g=0,y=0,v=f,S=0,j=0,w=0,x=1,_=1,T=1,C=0,N="",M=s,O=c,G=l,A=N;_;)switch(w=C,C=Zt()){case 40:if(w!=108&&Rt(A,v-1)==58){Wf(A+=Ue(fs(C),"&","&\f"),"&\f")!=-1&&(T=-1);break}case 34:case 39:case 91:A+=fs(C);break;case 9:case 10:case 13:case 32:A+=hw(w);break;case 92:A+=mw(cs()-1,7);continue;case 47:switch(Bn()){case 42:case 47:rs(bw(gw(Zt(),cs()),r,o),h);break;default:A+="/"}break;case 123*x:p[g++]=zn(A)*T;case 125*x:case 59:case 0:switch(C){case 0:case 125:_=0;case 59+y:T==-1&&(A=Ue(A,/\f/g,"")),j>0&&zn(A)-v&&rs(j>32?bg(A+";",l,o,v-1):bg(Ue(A," ","")+";",l,o,v-2),h);break;case 59:A+=";";default:if(rs(G=yg(A,r,o,g,y,s,p,N,M=[],O=[],v),c),C===123)if(y===0)ds(A,r,G,G,M,c,v,p,O);else switch(S===99&&Rt(A,3)===110?100:S){case 100:case 108:case 109:case 115:ds(n,G,G,l&&rs(yg(n,G,G,0,0,s,p,N,s,M=[],v),O),s,O,v,p,l?M:O);break;default:ds(A,G,G,G,[""],O,0,p,O)}}g=y=j=0,x=T=1,N=A="",v=f;break;case 58:v=1+zn(A),j=w;default:if(x<1){if(C==123)--x;else if(C==125&&x++==0&&pw()==125)continue}switch(A+=js(C),C*x){case 38:T=y>0?1:(A+="\f",-1);break;case 44:p[g++]=(zn(A)-1)*T,T=1;break;case 64:Bn()===45&&(A+=fs(Zt())),S=Bn(),y=v=zn(N=A+=vw(cs())),C++;break;case 45:w===45&&zn(A)==2&&(x=0)}}return c}function yg(n,r,o,l,s,c,f,p,h,g,y){for(var v=s-1,S=s===0?c:[""],j=Sd(S),w=0,x=0,_=0;w0?S[T]+" "+C:Ue(C,/&\f/g,S[T])))&&(h[_++]=N);return Ns(n,r,o,s===0?bd:p,h,g,y)}function bw(n,r,o){return Ns(n,r,o,ly,js(dw()),wl(n,2,-2),0)}function bg(n,r,o,l){return Ns(n,r,o,xd,wl(n,0,l),wl(n,l+1,-1),l)}function li(n,r){for(var o="",l=Sd(n),s=0;s6)switch(Rt(n,r+1)){case 109:if(Rt(n,r+4)!==45)break;case 102:return Ue(n,/(.+:)(.+)-([^]+)/,"$1"+Be+"$2-$3$1"+gs+(Rt(n,r+3)==108?"$3":"$2-$3"))+n;case 115:return~Wf(n,"stretch")?py(Ue(n,"stretch","fill-available"),r)+n:n}break;case 4949:if(Rt(n,r+1)!==115)break;case 6444:switch(Rt(n,zn(n)-3-(~Wf(n,"!important")&&10))){case 107:return Ue(n,":",":"+Be)+n;case 101:return Ue(n,/(.+:)([^;!]+)(;|!.+)?/,"$1"+Be+(Rt(n,14)===45?"inline-":"")+"box$3$1"+Be+"$2$3$1"+_t+"$2box$3")+n}break;case 5936:switch(Rt(n,r+11)){case 114:return Be+n+_t+Ue(n,/[svh]\w+-[tblr]{2}/,"tb")+n;case 108:return Be+n+_t+Ue(n,/[svh]\w+-[tblr]{2}/,"tb-rl")+n;case 45:return Be+n+_t+Ue(n,/[svh]\w+-[tblr]{2}/,"lr")+n}return Be+n+_t+n+n}return n}var Ow=function(r,o,l,s){if(r.length>-1&&!r.return)switch(r.type){case xd:r.return=py(r.value,r.length);break;case oy:return li([cl(r,{value:Ue(r.value,"@","@"+Be)})],s);case bd:if(r.length)return fw(r.props,function(c){switch(cw(c,/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":return li([cl(r,{props:[Ue(c,/:(read-\w+)/,":"+gs+"$1")]})],s);case"::placeholder":return li([cl(r,{props:[Ue(c,/:(plac\w+)/,":"+Be+"input-$1")]}),cl(r,{props:[Ue(c,/:(plac\w+)/,":"+gs+"$1")]}),cl(r,{props:[Ue(c,/:(plac\w+)/,_t+"input-$1")]})],s)}return""})}},Nw=[Ow],hy=function(r){var o=r.key;if(o==="css"){var l=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(l,function(x){var _=x.getAttribute("data-emotion");_.indexOf(" ")!==-1&&(document.head.appendChild(x),x.setAttribute("data-s",""))})}var s=r.stylisPlugins||Nw,c={},f,p=[];f=r.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+o+' "]'),function(x){for(var _=x.getAttribute("data-emotion").split(" "),T=1;T<_.length;T++)c[_[T]]=!0;p.push(x)});var h,g=[Tw,jw];{var y,v=[xw,ww(function(x){y.insert(x)})],S=Sw(g.concat(s,v)),j=function(_){return li(yw(_),S)};h=function(_,T,C,N){y=C,j(_?_+"{"+T.styles+"}":T.styles),N&&(w.inserted[T.name]=!0)}}var w={key:o,sheet:new rw({key:o,container:f,nonce:r.nonce,speedy:r.speedy,prepend:r.prepend,insertionPoint:r.insertionPoint}),nonce:r.nonce,inserted:c,registered:{},insert:h};return w.sheet.hydrate(p),w};function ee(){return ee=Object.assign?Object.assign.bind():function(n){for(var r=1;r `},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(4),f=s(2),p=s(0),h=p.default.ICON,g=p.default.ICON_CUSTOM,y=["error","warning","success","info"],v={error:f.errorIconMarkup(),warning:f.warningIconMarkup(),success:f.successIconMarkup()},S=function(x,_){var T=h+"--"+x;_.classList.add(T);var C=v[x];C&&(_.innerHTML=C)},j=function(x,_){_.classList.add(g);var T=document.createElement("img");T.src=x,_.appendChild(T)},w=function(x){if(x){var _=c.injectElIntoModal(f.iconMarkup);y.includes(x)?S(x,_):j(x,_)}};l.default=w},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(2),f=s(4),p=function(h){navigator.userAgent.includes("AppleWebKit")&&(h.style.display="none",h.offsetHeight,h.style.display="")};l.initTitle=function(h){if(h){var g=f.injectElIntoModal(c.titleMarkup);g.textContent=h,p(g)}},l.initText=function(h){if(h){var g=document.createDocumentFragment();h.split(` -`).forEach(function(v,S,j){g.appendChild(document.createTextNode(v)),S0}).forEach(function(B){E.classList.add(B)}),C&&_===y.CONFIRM_KEY&&E.classList.add(g),E.textContent=N;var D={};return D[_]=M,j.setActionValue(D),j.setActionOptionsFor(_,{closeModal:G}),E.addEventListener("click",function(){return S.onAction(_)}),A},x=function(_,T){var C=f.injectElIntoModal(v.footerMarkup);for(var N in _){var M=_[N],O=w(N,M,T);M.visible&&C.appendChild(O)}C.children.length===0&&C.remove()};l.default=x},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(3),f=s(4),p=s(2),h=s(5),g=s(6),y=s(0),v=y.default.CONTENT,S=function(x){x.addEventListener("input",function(_){var T=_.target,C=T.value;h.setActionValue(C)}),x.addEventListener("keyup",function(_){if(_.key==="Enter")return g.onAction(c.CONFIRM_KEY)}),setTimeout(function(){x.focus(),h.setActionValue("")},0)},j=function(x,_,T){var C=document.createElement(_),N=v+"__"+_;C.classList.add(N);for(var M in T){var O=T[M];C[M]=O}_==="input"&&S(C),x.appendChild(C)},w=function(x){if(x){var _=f.injectElIntoModal(p.contentMarkup),T=x.element,C=x.attributes;typeof T=="string"?j(_,T,C):_.appendChild(T)}};l.default=w},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(1),f=s(2),p=function(){var h=c.stringToNode(f.overlayMarkup);document.body.appendChild(h)};l.default=p},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(5),f=s(6),p=s(1),h=s(3),g=s(0),y=g.default.MODAL,v=g.default.BUTTON,S=g.default.OVERLAY,j=function(B){B.preventDefault(),C()},w=function(B){B.preventDefault(),N()},x=function(B){if(c.default.isOpen)switch(B.key){case"Escape":return f.onAction(h.CANCEL_KEY)}},_=function(B){if(c.default.isOpen)switch(B.key){case"Tab":return j(B)}},T=function(B){if(c.default.isOpen)return B.key==="Tab"&&B.shiftKey?w(B):void 0},C=function(){var B=p.getNode(v);B&&(B.tabIndex=0,B.focus())},N=function(){var B=p.getNode(y),q=B.querySelectorAll("."+v),X=q.length-1,ne=q[X];ne&&ne.focus()},M=function(B){B[B.length-1].addEventListener("keydown",_)},O=function(B){B[0].addEventListener("keydown",T)},G=function(){var B=p.getNode(y),q=B.querySelectorAll("."+v);q.length&&(M(q),O(q))},A=function(B){if(p.getNode(S)===B.target)return f.onAction(h.CANCEL_KEY)},E=function(B){var q=p.getNode(S);q.removeEventListener("click",A),B&&q.addEventListener("click",A)},z=function(B){c.default.timer&&clearTimeout(c.default.timer),B&&(c.default.timer=window.setTimeout(function(){return f.onAction(h.CANCEL_KEY)},B))},D=function(B){B.closeOnEsc?document.addEventListener("keyup",x):document.removeEventListener("keyup",x),B.dangerMode?C():N(),G(),E(B.closeOnClickOutside),z(B.timer)};l.default=D},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(1),f=s(3),p=s(37),h=s(38),g={title:null,text:null,icon:null,buttons:f.defaultButtonList,content:null,className:null,closeOnClickOutside:!0,closeOnEsc:!0,dangerMode:!1,timer:null},y=Object.assign({},g);l.setDefaults=function(T){y=Object.assign({},g,T)};var v=function(T){var C=T&&T.button,N=T&&T.buttons;return C!==void 0&&N!==void 0&&c.throwErr("Cannot set both 'button' and 'buttons' options!"),C!==void 0?{confirm:C}:N},S=function(T){return c.ordinalSuffixOf(T+1)},j=function(T,C){c.throwErr(S(C)+" argument ('"+T+"') is invalid")},w=function(T,C){var N=T+1,M=C[N];c.isPlainObject(M)||M===void 0||c.throwErr("Expected "+S(N)+" argument ('"+M+"') to be a plain object")},x=function(T,C){var N=T+1,M=C[N];M!==void 0&&c.throwErr("Unexpected "+S(N)+" argument ("+M+")")},_=function(T,C,N,M){var O=typeof C,G=O==="string",A=C instanceof Element;if(G){if(N===0)return{text:C};if(N===1)return{text:C,title:M[0]};if(N===2)return w(N,M),{icon:C};j(C,N)}else{if(A&&N===0)return w(N,M),{content:C};if(c.isPlainObject(C))return x(N,M),C;j(C,N)}};l.getOpts=function(){for(var T=[],C=0;C{const r=n._id==="create"?"* Your Lab's Name (or Professor's Name) *":"",o=n._id==="create"?"This is your new listing! Please edit the details and click save to post it. If you click cancel, this listing will be deleted.":"",l=new Date;return{id:n._id,ownerId:n.ownerId,ownerFirstName:n.ownerFirstName,ownerLastName:n.ownerLastName,ownerEmail:n.ownerEmail,professorIds:n.professorIds||[],professorNames:n.professorNames||[],title:n.title||r,departments:n.departments||[],emails:n.emails||[],websites:n.websites||[],description:n.description||o,keywords:n.keywords||[],established:n.established&&n.established.toString(),views:n.views||0,favorites:n.favorites||0,hiringStatus:n.hiringStatus||0,archived:n.archived||!1,updatedAt:n.updatedAt||l.toISOString(),createdAt:n.createdAt||l.toISOString(),confirmed:n.confirmed===void 0?!0:n.confirmed}},pE=({allDepartments:n,resetListings:r,addListings:o,setIsLoading:l,sortBy:s,sortOrder:c,setSortBy:f,setSortOrder:p,sortableKeys:h,page:g,setPage:y,pageSize:v,sortDirection:S,onToggleSortDirection:j})=>{const w=[{value:"default",label:"Sort by: Best Match"},{value:"updatedAt",label:"Sort by: Last Updated"},{value:"ownerLastName",label:"Sort by: Last Name"},{value:"ownerFirstName",label:"Sort by: First Name"},{value:"title",label:"Sort by: Lab Title"}],[x,_]=R.useState([]),[T,C]=R.useState(!1),[N,M]=R.useState(""),[O,G]=R.useState(""),[A,E]=R.useState(-1),z=R.useRef(null),D=R.useRef(null),B=R.useRef(null),q=R.useRef(null),[X,ne]=R.useState(!1),[oe,Z]=R.useState(!1);R.useEffect(()=>{const L=te=>{z.current&&!z.current.contains(te.target)&&(C(!1),M(""))};return document.addEventListener("mousedown",L),y(1),se(1),()=>{document.removeEventListener("mousedown",L)}},[]),R.useEffect(()=>{const L=setTimeout(()=>{X&&(y(1),se(1)),ne(!0)},500);return()=>{clearTimeout(L)}},[O]),R.useEffect(()=>{oe&&(y(1),se(1)),Z(!0)},[x,s,c]),R.useEffect(()=>{g>1&&se(g)},[g]);const le=L=>{var te,he,ge;switch(L.key){case"ArrowDown":L.preventDefault(),E(de=>dede>0?de-1:0);break;case"Enter":L.preventDefault(),A>=0&&A{var te;L.key==="Enter"&&(L.preventDefault(),(te=B.current)==null||te.blur(),W())},pe=L=>{_(te=>te.filter(he=>he!==L))},U=L=>{x.indexOf(L)<0?_(te=>[...te,L]):pe(L)},ie=L=>{M(L.target.value)},Q=n.filter(L=>L.toLowerCase().includes(N.toLowerCase())&&x.indexOf(L)<0),V=()=>{C(!T),M("")},W=()=>{C(!1),M("")},se=L=>{let te;const he=O.trim(),ge=x.join(","),de=window.location.host.includes("yalelabs.io")?"https://yalelabs.io":"http://localhost:4000";s==="default"?te=de+`/newListings/search?query=${he}&page=${L}&pageSize=${v}`:te=de+`/newListings/search?query=${he}&sortBy=${s}&sortOrder=${c}&page=${L}&pageSize=${v}`,ge&&(te+=`&departments=${ge}`),l(!0),ry.get(te,{withCredentials:!0}).then(Ee=>{const _e=Ee.data.results.map(function(we){return cr(we)});L==1?r(_e):o(_e),l(!1)}).catch(Ee=>{console.error("Error loading listings:",Ee),Fe({text:"Unable to load listings. Please try again later.",icon:"warning"}),l(!1)})},H=L=>{if(Object.keys(It).includes(L))switch(It[L]){case 0:return"bg-blue-200 text-gray-900";case 1:return"bg-green-200 text-gray-900";case 2:return"bg-yellow-200 text-gray-900";case 3:return"bg-red-200 text-gray-900";case 4:return"bg-purple-200 text-gray-900";case 5:return"bg-pink-200 text-gray-900";case 6:return"bg-teal-200 text-gray-900";case 7:return"bg-orange-200 text-gray-900";default:return"bg-gray-100 text-gray-900"}return"bg-gray-100 text-gray-900"},fe=()=>{_([])};return m.jsxs("div",{className:"relative",children:[m.jsxs("div",{className:"flex-col flex md:flex-row md:items-center gap-4",children:[m.jsx("div",{className:"md:flex-1",children:m.jsx("input",{ref:q,type:"text",value:O,onChange:L=>G(L.target.value),onKeyDown:L=>{var te;(L.key==="Enter"||L.key==="Escape")&&(L.preventDefault(),(te=q.current)==null||te.blur())},onFocus:W,placeholder:"Start your search...",className:"px-4 py-2 w-full border rounded text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-text h-11"})}),m.jsxs("div",{className:"relative w-full md:w-[35%]",ref:z,children:[m.jsxs("button",{ref:B,onClick:V,onKeyDown:re,className:"flex items-center justify-between w-full h-11 px-3 py-2 border rounded bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500",children:[m.jsx("span",{className:"truncate",children:"Filter by department"}),m.jsx("svg",{className:"fill-current h-4 w-4 ml-2",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})]}),T&&m.jsxs("div",{className:"absolute left-0 right-0 bg-white rounded-lg z-50 shadow-lg border overflow-hidden mt-1 max-h-[350px] border-gray-300",children:[m.jsx("div",{className:"p-2 border-b",children:m.jsx("input",{type:"text",value:N,onChange:ie,onKeyDown:le,placeholder:"Search departments...",className:"w-full px-3 py-2 border rounded text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500",autoFocus:!0})}),m.jsx("ul",{className:"max-h-[300px] p-1 overflow-y-auto",children:Q.length>0?Q.map((L,te)=>m.jsx("li",{onClick:()=>{U(L),M("")},className:`p-2 cursor-pointer ${A===te?"bg-blue-100":"hover:bg-gray-100"}`,onMouseDown:he=>he.preventDefault(),children:L},te)):m.jsx("li",{className:"p-2 text-gray-500",children:"No departments found"})})]})]}),m.jsxs("div",{className:"hidden md:flex items-center space-x-2",children:[m.jsx(iy,{sortBy:s,setSortBy:f,sortOptions:w,searchHub:!0}),s!=="default"&&m.jsx("button",{onClick:j,className:"flex items-center justify-center","aria-label":S==="asc"?"Sort ascending":"Sort descending",children:m.jsx("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:`transition-transform duration-300 ease-in-out transform ${S==="asc"?"rotate-0":"rotate-180"}`,children:m.jsx("path",{d:"M12 5l7 7-1.41 1.41L13 8.83V19h-2V8.83L6.41 13.41 5 12l7-7z",fill:"currentColor"})})})]})]}),x.length>0&&m.jsxs("div",{className:"flex flex-wrap gap-2 mt-4 w-full",children:[m.jsx("span",{className:"border text-gray-700 px-2 py-1 rounded text-sm flex items-center",children:"Filters:"}),x.map((L,te)=>m.jsxs("span",{className:`${H(L)} px-2 py-1 rounded text-sm flex items-center`,children:[m.jsx("span",{className:"whitespace-nowrap",children:L}),m.jsx("button",{type:"button",onClick:()=>pe(L),className:"ml-2 text-gray-500 hover:text-gray-700",children:"×"})]},te)),x.length>=2&&m.jsx("button",{onClick:fe,className:"bg-red-500 hover:bg-red-600 rounded px-2 py-1 rounded text-sm flex items-center transition-colors",children:m.jsx("span",{className:"whitespace-nowrap text-white",children:"Remove All"})})]})]})};var Sy=my();function hE(n){function r(Q,V,W,se,H){for(var fe=0,L=0,te=0,he=0,ge,de,Ee=0,_e=0,we,Ze=we=ge=0,je=0,et=0,vn=0,Ke=0,On=W.length,Hn=On-1,Ie,be="",De="",sa="",yn="",Jt;jege)&&(Ke=(be=be.replace(" ",":")).length),0se&&(se=(V=V.trim()).charCodeAt(0)),se){case 38:return V.replace(T,"$1"+Q.trim());case 58:return Q.trim()+V.replace(T,"$1"+Q.trim());default:if(0<1*W&&0L.charCodeAt(8))break;case 115:H=H.replace(L,"-webkit-"+L)+";"+H;break;case 207:case 102:H=H.replace(L,"-webkit-"+(102W.charCodeAt(0)&&(W=W.trim()),ie=W,W=[ie],01?r-1:0),l=1;l0?" Args: "+o.join(", "):""))}var yE=function(){function n(o){this.groupSizes=new Uint32Array(512),this.length=512,this.tag=o}var r=n.prototype;return r.indexOfGroup=function(o){for(var l=0,s=0;s=this.groupSizes.length){for(var s=this.groupSizes,c=s.length,f=c;o>=f;)(f<<=1)<0&&zl(16,""+o);this.groupSizes=new Uint32Array(f),this.groupSizes.set(s),this.length=f;for(var p=c;p=this.length||this.groupSizes[o]===0)return l;for(var s=this.groupSizes[o],c=this.indexOfGroup(o),f=c+s,p=c;p0}).forEach(function(B){E.classList.add(B)}),C&&_===y.CONFIRM_KEY&&E.classList.add(g),E.textContent=N;var D={};return D[_]=M,j.setActionValue(D),j.setActionOptionsFor(_,{closeModal:G}),E.addEventListener("click",function(){return S.onAction(_)}),A},x=function(_,T){var C=f.injectElIntoModal(v.footerMarkup);for(var N in _){var M=_[N],O=w(N,M,T);M.visible&&C.appendChild(O)}C.children.length===0&&C.remove()};l.default=x},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(3),f=s(4),p=s(2),h=s(5),g=s(6),y=s(0),v=y.default.CONTENT,S=function(x){x.addEventListener("input",function(_){var T=_.target,C=T.value;h.setActionValue(C)}),x.addEventListener("keyup",function(_){if(_.key==="Enter")return g.onAction(c.CONFIRM_KEY)}),setTimeout(function(){x.focus(),h.setActionValue("")},0)},j=function(x,_,T){var C=document.createElement(_),N=v+"__"+_;C.classList.add(N);for(var M in T){var O=T[M];C[M]=O}_==="input"&&S(C),x.appendChild(C)},w=function(x){if(x){var _=f.injectElIntoModal(p.contentMarkup),T=x.element,C=x.attributes;typeof T=="string"?j(_,T,C):_.appendChild(T)}};l.default=w},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(1),f=s(2),p=function(){var h=c.stringToNode(f.overlayMarkup);document.body.appendChild(h)};l.default=p},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(5),f=s(6),p=s(1),h=s(3),g=s(0),y=g.default.MODAL,v=g.default.BUTTON,S=g.default.OVERLAY,j=function(B){B.preventDefault(),C()},w=function(B){B.preventDefault(),N()},x=function(B){if(c.default.isOpen)switch(B.key){case"Escape":return f.onAction(h.CANCEL_KEY)}},_=function(B){if(c.default.isOpen)switch(B.key){case"Tab":return j(B)}},T=function(B){if(c.default.isOpen)return B.key==="Tab"&&B.shiftKey?w(B):void 0},C=function(){var B=p.getNode(v);B&&(B.tabIndex=0,B.focus())},N=function(){var B=p.getNode(y),q=B.querySelectorAll("."+v),X=q.length-1,ne=q[X];ne&&ne.focus()},M=function(B){B[B.length-1].addEventListener("keydown",_)},O=function(B){B[0].addEventListener("keydown",T)},G=function(){var B=p.getNode(y),q=B.querySelectorAll("."+v);q.length&&(M(q),O(q))},A=function(B){if(p.getNode(S)===B.target)return f.onAction(h.CANCEL_KEY)},E=function(B){var q=p.getNode(S);q.removeEventListener("click",A),B&&q.addEventListener("click",A)},z=function(B){c.default.timer&&clearTimeout(c.default.timer),B&&(c.default.timer=window.setTimeout(function(){return f.onAction(h.CANCEL_KEY)},B))},D=function(B){B.closeOnEsc?document.addEventListener("keyup",x):document.removeEventListener("keyup",x),B.dangerMode?C():N(),G(),E(B.closeOnClickOutside),z(B.timer)};l.default=D},function(o,l,s){Object.defineProperty(l,"__esModule",{value:!0});var c=s(1),f=s(3),p=s(37),h=s(38),g={title:null,text:null,icon:null,buttons:f.defaultButtonList,content:null,className:null,closeOnClickOutside:!0,closeOnEsc:!0,dangerMode:!1,timer:null},y=Object.assign({},g);l.setDefaults=function(T){y=Object.assign({},g,T)};var v=function(T){var C=T&&T.button,N=T&&T.buttons;return C!==void 0&&N!==void 0&&c.throwErr("Cannot set both 'button' and 'buttons' options!"),C!==void 0?{confirm:C}:N},S=function(T){return c.ordinalSuffixOf(T+1)},j=function(T,C){c.throwErr(S(C)+" argument ('"+T+"') is invalid")},w=function(T,C){var N=T+1,M=C[N];c.isPlainObject(M)||M===void 0||c.throwErr("Expected "+S(N)+" argument ('"+M+"') to be a plain object")},x=function(T,C){var N=T+1,M=C[N];M!==void 0&&c.throwErr("Unexpected "+S(N)+" argument ("+M+")")},_=function(T,C,N,M){var O=typeof C,G=O==="string",A=C instanceof Element;if(G){if(N===0)return{text:C};if(N===1)return{text:C,title:M[0]};if(N===2)return w(N,M),{icon:C};j(C,N)}else{if(A&&N===0)return w(N,M),{content:C};if(c.isPlainObject(C))return x(N,M),C;j(C,N)}};l.getOpts=function(){for(var T=[],C=0;C{const r=n._id==="create"?"* Your Lab's Name (or Professor's Name) *":"",o=n._id==="create"?"This is your new listing! Please edit the details and click save to post it. If you click cancel, this listing will be deleted.":"",l=new Date;return{id:n._id,ownerId:n.ownerId,ownerFirstName:n.ownerFirstName,ownerLastName:n.ownerLastName,ownerEmail:n.ownerEmail,professorIds:n.professorIds||[],professorNames:n.professorNames||[],title:n.title||r,departments:n.departments||[],emails:n.emails||[],websites:n.websites||[],description:n.description||o,keywords:n.keywords||[],established:n.established&&n.established.toString(),views:n.views||0,favorites:n.favorites||0,hiringStatus:n.hiringStatus||0,archived:n.archived||!1,updatedAt:n.updatedAt||l.toISOString(),createdAt:n.createdAt||l.toISOString(),confirmed:n.confirmed===void 0?!0:n.confirmed}},pE=({allDepartments:n,resetListings:r,addListings:o,setIsLoading:l,sortBy:s,sortOrder:c,setSortBy:f,setSortOrder:p,sortableKeys:h,page:g,setPage:y,pageSize:v,sortDirection:S,onToggleSortDirection:j})=>{const w=[{value:"default",label:"Sort by: Best Match"},{value:"updatedAt",label:"Sort by: Last Updated"},{value:"ownerLastName",label:"Sort by: Last Name"},{value:"ownerFirstName",label:"Sort by: First Name"},{value:"title",label:"Sort by: Lab Title"}],[x,_]=R.useState([]),[T,C]=R.useState(!1),[N,M]=R.useState(""),[O,G]=R.useState(""),[A,E]=R.useState(-1),z=R.useRef(null),D=R.useRef(null),B=R.useRef(null),q=R.useRef(null),[X,ne]=R.useState(!1),[oe,Z]=R.useState(!1);R.useEffect(()=>{const L=te=>{z.current&&!z.current.contains(te.target)&&(C(!1),M(""))};return document.addEventListener("mousedown",L),y(1),se(1),()=>{document.removeEventListener("mousedown",L)}},[]),R.useEffect(()=>{const L=setTimeout(()=>{X&&(y(1),se(1)),ne(!0)},500);return()=>{clearTimeout(L)}},[O]),R.useEffect(()=>{oe&&(y(1),se(1)),Z(!0)},[x,s,c]),R.useEffect(()=>{g>1&&se(g)},[g]);const le=L=>{var te,he,ge;switch(L.key){case"ArrowDown":L.preventDefault(),E(de=>dede>0?de-1:0);break;case"Enter":L.preventDefault(),A>=0&&A{var te;L.key==="Enter"&&(L.preventDefault(),(te=B.current)==null||te.blur(),W())},pe=L=>{_(te=>te.filter(he=>he!==L))},U=L=>{x.indexOf(L)<0?_(te=>[...te,L]):pe(L)},ie=L=>{M(L.target.value)},Q=n.filter(L=>L.toLowerCase().includes(N.toLowerCase())&&x.indexOf(L)<0),V=()=>{C(!T),M("")},W=()=>{C(!1),M("")},se=L=>{let te;const he=O.trim(),ge=x.join(","),de=window.location.host.includes("yalelabs.io")?"https://yalelabs.io":"http://localhost:4000";s==="default"?te=de+`/listings/search?query=${he}&page=${L}&pageSize=${v}`:te=de+`/listings/search?query=${he}&sortBy=${s}&sortOrder=${c}&page=${L}&pageSize=${v}`,ge&&(te+=`&departments=${ge}`),l(!0),ry.get(te,{withCredentials:!0}).then(Ee=>{const _e=Ee.data.results.map(function(we){return cr(we)});L==1?r(_e):o(_e),l(!1)}).catch(Ee=>{console.error("Error loading listings:",Ee),Fe({text:"Unable to load listings. Please try again later.",icon:"warning"}),l(!1)})},H=L=>{if(Object.keys(It).includes(L))switch(It[L]){case 0:return"bg-blue-200 text-gray-900";case 1:return"bg-green-200 text-gray-900";case 2:return"bg-yellow-200 text-gray-900";case 3:return"bg-red-200 text-gray-900";case 4:return"bg-purple-200 text-gray-900";case 5:return"bg-pink-200 text-gray-900";case 6:return"bg-teal-200 text-gray-900";case 7:return"bg-orange-200 text-gray-900";default:return"bg-gray-100 text-gray-900"}return"bg-gray-100 text-gray-900"},fe=()=>{_([])};return m.jsxs("div",{className:"relative",children:[m.jsxs("div",{className:"flex-col flex md:flex-row md:items-center gap-4",children:[m.jsx("div",{className:"md:flex-1",children:m.jsx("input",{ref:q,type:"text",value:O,onChange:L=>G(L.target.value),onKeyDown:L=>{var te;(L.key==="Enter"||L.key==="Escape")&&(L.preventDefault(),(te=q.current)==null||te.blur())},onFocus:W,placeholder:"Start your search...",className:"px-4 py-2 w-full border rounded text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-text h-11"})}),m.jsxs("div",{className:"relative w-full md:w-[35%]",ref:z,children:[m.jsxs("button",{ref:B,onClick:V,onKeyDown:re,className:"flex items-center justify-between w-full h-11 px-3 py-2 border rounded bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500",children:[m.jsx("span",{className:"truncate",children:"Filter by department"}),m.jsx("svg",{className:"fill-current h-4 w-4 ml-2",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})]}),T&&m.jsxs("div",{className:"absolute left-0 right-0 bg-white rounded-lg z-50 shadow-lg border overflow-hidden mt-1 max-h-[350px] border-gray-300",children:[m.jsx("div",{className:"p-2 border-b",children:m.jsx("input",{type:"text",value:N,onChange:ie,onKeyDown:le,placeholder:"Search departments...",className:"w-full px-3 py-2 border rounded text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500",autoFocus:!0})}),m.jsx("ul",{className:"max-h-[300px] p-1 overflow-y-auto",children:Q.length>0?Q.map((L,te)=>m.jsx("li",{onClick:()=>{U(L),M("")},className:`p-2 cursor-pointer ${A===te?"bg-blue-100":"hover:bg-gray-100"}`,onMouseDown:he=>he.preventDefault(),children:L},te)):m.jsx("li",{className:"p-2 text-gray-500",children:"No departments found"})})]})]}),m.jsxs("div",{className:"hidden md:flex items-center space-x-2",children:[m.jsx(iy,{sortBy:s,setSortBy:f,sortOptions:w,searchHub:!0}),s!=="default"&&m.jsx("button",{onClick:j,className:"flex items-center justify-center","aria-label":S==="asc"?"Sort ascending":"Sort descending",children:m.jsx("svg",{width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:`transition-transform duration-300 ease-in-out transform ${S==="asc"?"rotate-0":"rotate-180"}`,children:m.jsx("path",{d:"M12 5l7 7-1.41 1.41L13 8.83V19h-2V8.83L6.41 13.41 5 12l7-7z",fill:"currentColor"})})})]})]}),x.length>0&&m.jsxs("div",{className:"flex flex-wrap gap-2 mt-4 w-full",children:[m.jsx("span",{className:"border text-gray-700 px-2 py-1 rounded text-sm flex items-center",children:"Filters:"}),x.map((L,te)=>m.jsxs("span",{className:`${H(L)} px-2 py-1 rounded text-sm flex items-center`,children:[m.jsx("span",{className:"whitespace-nowrap",children:L}),m.jsx("button",{type:"button",onClick:()=>pe(L),className:"ml-2 text-gray-500 hover:text-gray-700",children:"×"})]},te)),x.length>=2&&m.jsx("button",{onClick:fe,className:"bg-red-500 hover:bg-red-600 rounded px-2 py-1 rounded text-sm flex items-center transition-colors",children:m.jsx("span",{className:"whitespace-nowrap text-white",children:"Remove All"})})]})]})};var Sy=my();function hE(n){function r(Q,V,W,se,H){for(var fe=0,L=0,te=0,he=0,ge,de,Ee=0,_e=0,we,Ze=we=ge=0,je=0,et=0,vn=0,Ke=0,On=W.length,Hn=On-1,Ie,be="",De="",sa="",yn="",Jt;jege)&&(Ke=(be=be.replace(" ",":")).length),0se&&(se=(V=V.trim()).charCodeAt(0)),se){case 38:return V.replace(T,"$1"+Q.trim());case 58:return Q.trim()+V.replace(T,"$1"+Q.trim());default:if(0<1*W&&0L.charCodeAt(8))break;case 115:H=H.replace(L,"-webkit-"+L)+";"+H;break;case 207:case 102:H=H.replace(L,"-webkit-"+(102W.charCodeAt(0)&&(W=W.trim()),ie=W,W=[ie],01?r-1:0),l=1;l0?" Args: "+o.join(", "):""))}var yE=function(){function n(o){this.groupSizes=new Uint32Array(512),this.length=512,this.tag=o}var r=n.prototype;return r.indexOfGroup=function(o){for(var l=0,s=0;s=this.groupSizes.length){for(var s=this.groupSizes,c=s.length,f=c;o>=f;)(f<<=1)<0&&zl(16,""+o);this.groupSizes=new Uint32Array(f),this.groupSizes.set(s),this.length=f;for(var p=c;p=this.length||this.groupSizes[o]===0)return l;for(var s=this.groupSizes[o],c=this.indexOfGroup(o),f=c+s,p=c;p=gl&&(gl=r+1),hs.set(n,r),bs.set(r,n)},SE="style["+ui+'][data-styled-version="5.3.11"]',wE=new RegExp("^"+ui+'\\.g(\\d+)\\[id="([\\w\\d-]+)"\\].*?"([^"]*)'),EE=function(n,r,o){for(var l,s=o.split(","),c=0,f=s.length;c=0;g--){var y=h[g];if(y&&y.nodeType===1&&y.hasAttribute(ui))return y}}(o),c=s!==void 0?s.nextSibling:null;l.setAttribute(ui,"active"),l.setAttribute("data-styled-version","5.3.11");var f=RE();return f&&l.setAttribute("nonce",f),o.insertBefore(l,c),l},TE=function(){function n(o){var l=this.element=wy(o);l.appendChild(document.createTextNode("")),this.sheet=function(s){if(s.sheet)return s.sheet;for(var c=document.styleSheets,f=0,p=c.length;f=0){var s=document.createTextNode(l),c=this.nodes[o];return this.element.insertBefore(s,c||null),this.length++,!0}return!1},r.deleteRule=function(o){this.element.removeChild(this.nodes[o]),this.length--},r.getRule=function(o){return o0&&(v+=S+",")}),c+=""+g+y+'{content:"'+v+`"}/*!sc*/ `}}}return c}(this)},n}(),_E=/(a)(d)/gi,Ug=function(n){return String.fromCharCode(n+(n>25?39:97))};function ad(n){var r,o="";for(r=Math.abs(n);r>52;r=r/52|0)o=Ug(r%52)+o;return(Ug(r%52)+o).replace(_E,"$1-$2")}var ri=function(n,r){for(var o=r.length;o;)n=33*n^r.charCodeAt(--o);return n},Cy=function(n){return ri(5381,n)};function AE(n){for(var r=0;r>>0);if(!o.hasNameForId(s,p)){var h=l(f,"."+p,void 0,s);o.insertRules(s,p,h)}c.push(p),this.staticRulesId=p}else{for(var g=this.rules.length,y=ri(this.baseHash,l.hash),v="",S=0;S>>0);if(!o.hasNameForId(s,_)){var T=l(v,"."+_,void 0,s);o.insertRules(s,_,T)}c.push(_)}}return c.join(" ")},n}(),DE=/^\s*\/\/.*$/gm,zE=[":","[",".","#"];function LE(n){var r,o,l,s,c=ka,f=c.options,p=f===void 0?ka:f,h=c.plugins,g=h===void 0?ys:h,y=new hE(p),v=[],S=function(x){function _(T){if(T)try{x(T+"}")}catch{}}return function(T,C,N,M,O,G,A,E,z,D){switch(T){case 1:if(z===0&&C.charCodeAt(0)===64)return x(C+";"),"";break;case 2:if(E===0)return C+"/*|*/";break;case 3:switch(E){case 102:case 112:return x(N[0]+C),"";default:return C+(D===0?"/*|*/":"")}case-2:C.split("/*|*/}").forEach(_)}}}(function(x){v.push(x)}),j=function(x,_,T){return _===0&&zE.indexOf(T[o.length])!==-1||T.match(s)?x:"."+r};function w(x,_,T,C){C===void 0&&(C="&");var N=x.replace(DE,""),M=_&&T?T+" "+_+" { "+N+" }":N;return r=C,o=_,l=new RegExp("\\"+o+"\\b","g"),s=new RegExp("(\\"+o+"\\b){2,}"),y(T||!_?"":_,M)}return y.use([].concat(g,[function(x,_,T){x===2&&T.length&&T[0].lastIndexOf(o)>0&&(T[0]=T[0].replace(l,j))},S,function(x){if(x===-2){var _=v;return v=[],_}}])),w.hash=g.length?g.reduce(function(x,_){return _.name||zl(15),ri(x,_.name)},5381).toString():"",w}var Ry=zt.createContext();Ry.Consumer;var Ty=zt.createContext(),BE=(Ty.Consumer,new Ey),rd=LE();function UE(){return R.useContext(Ry)||BE}function $E(){return R.useContext(Ty)||rd}var HE=function(){function n(r,o){var l=this;this.inject=function(s,c){c===void 0&&(c=rd);var f=l.name+c.hash;s.hasNameForId(l.id,f)||s.insertRules(l.id,f,c(l.rules,f,"@keyframes"))},this.toString=function(){return zl(12,String(l.name))},this.name=r,this.id="sc-keyframes-"+r,this.rules=o}return n.prototype.getName=function(r){return r===void 0&&(r=rd),this.name+r.hash},n}(),qE=/([A-Z])/,PE=/([A-Z])/g,YE=/^ms-/,GE=function(n){return"-"+n.toLowerCase()};function $g(n){return qE.test(n)?n.replace(PE,GE).replace(YE,"-ms-"):n}var Hg=function(n){return n==null||n===!1||n===""};function ci(n,r,o,l){if(Array.isArray(n)){for(var s,c=[],f=0,p=n.length;f1?r-1:0),l=1;l?@[\\\]^`{|}~-]+/g,KE=/(^-|-$)/g;function Uf(n){return n.replace(FE,"-").replace(KE,"")}var QE=function(n){return ad(Cy(n)>>>0)};function ls(n){return typeof n=="string"&&!0}var id=function(n){return typeof n=="function"||typeof n=="object"&&n!==null&&!Array.isArray(n)},ZE=function(n){return n!=="__proto__"&&n!=="constructor"&&n!=="prototype"};function IE(n,r,o){var l=n[o];id(r)&&id(l)?jy(l,r):n[o]=r}function jy(n){for(var r=arguments.length,o=new Array(r>1?r-1:0),l=1;l=0||(D[E]=G[E]);return D}(r,["componentId"]),O=N&&N+"-"+(ls(C)?C:Uf(Lg(C)));return Ny(C,aa({},M,{attrs:S,componentId:O}),o)},Object.defineProperty(w,"defaultProps",{get:function(){return this._foldedDefaultProps},set:function(C){this._foldedDefaultProps=l?jy({},n.defaultProps,C):C}}),Object.defineProperty(w,"toString",{value:function(){return"."+w.styledComponentId}}),s&&gy(w,n,{attrs:!0,componentStyle:!0,displayName:!0,foldedComponentIds:!0,shouldForwardProp:!0,styledComponentId:!0,target:!0,withComponent:!0}),w}var ra=function(n){return function r(o,l,s){if(s===void 0&&(s=ka),!Sy.isValidElementType(l))return zl(1,String(l));var c=function(){return o(l,s,VE.apply(void 0,arguments))};return c.withConfig=function(f){return r(o,l,aa({},s,{},f))},c.attrs=function(f){return r(o,l,aa({},s,{attrs:Array.prototype.concat(s.attrs,f).filter(Boolean)}))},c}(Ny,n)};["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","marquee","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","clipPath","defs","ellipse","foreignObject","g","image","line","linearGradient","marker","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","textPath","tspan"].forEach(function(n){ra[n]=ra(n)});const Pg=()=>{const[n,r]=R.useState([]),[o,l]=R.useState(!1),[s,c]=R.useState(!1),[f,p]=R.useState(1),h=20,g=["default","updatedAt","ownerLastName","ownerFirstName","title"],[y,v]=R.useState(g[0]),[S,j]=R.useState(1),[w,x]=R.useState("asc"),_=()=>{const E=w==="asc"?"desc":"asc";x(E),j(E==="asc"?1:-1)},[T,C]=R.useState([]),N=Object.keys(It).sort((E,z)=>E.localeCompare(z)),M=async()=>{xt.get("/users/favListingsIds",{withCredentials:!0}).then(E=>{C(E.data.favListingsIds)}).catch(E=>{console.error("Error fetching user's favorite listings:",E),C([]),Fe({text:"Could not load your favorite listings",icon:"warning"})})};R.useEffect(()=>{M()},[]);const O=E=>{r(z=>[...z,...E]),c(E.length{r(E),c(E.length{const D=T;z?(C([E,...D]),xt.put("/users/favListings",{withCredentials:!0,data:{favListings:[E]}}).catch(B=>{C(D),console.error("Error favoriting listing:",B),Fe({text:"Unable to favorite listing",icon:"warning"}),M()})):(C(D.filter(B=>B!==E)),xt.delete("/users/favListings",{withCredentials:!0,data:{favListings:[E]}}).catch(B=>{C(D),console.error("Error unfavoriting listing:",B),Fe({text:"Unable to unfavorite listing",icon:"warning"}),M()}))};return m.jsxs("div",{className:"mx-auto max-w-[1300px] px-6 mt-24 w-full",children:[m.jsx("div",{className:"mt-12",children:m.jsx(pE,{allDepartments:N,resetListings:G,addListings:O,setIsLoading:l,sortBy:y,sortOrder:S,setSortBy:v,setSortOrder:j,sortDirection:w,onToggleSortDirection:_,sortableKeys:g,page:f,setPage:p,pageSize:h})}),m.jsx("div",{className:"mt-4 md:mt-10"}),n.length>0?m.jsx(uE,{loading:o,searchExhausted:s,setPage:p,listings:n,sortableKeys:g,sortBy:y,setSortBy:v,setSortOrder:j,sortDirection:w,onToggleSortDirection:_,favListingsIds:T,updateFavorite:A}):m.jsx(WE,{children:"No results match the search criteria"})]})},WE=ra.h4` @@ -313,4 +313,4 @@ Error generating stack: `+a.message+` @media (max-width: 768px) { margin-top: 20px; } -`,bv=({developer:n})=>n?m.jsxs("div",{children:[m.jsx("img",{src:n.image?n.image:"/assets/developers/no-user.png",alt:`${n.name} Profile Picture`,className:"aspect-square object-cover w-full rounded-lg mb-2",width:500,height:500}),m.jsx("h3",{className:"text-xl font-semibold",children:n.name}),m.jsx("p",{className:"text-gray-700",children:n.position}),m.jsx("p",{className:"text-gray-700 mb-1",children:n.location}),n.website&&m.jsx("a",{href:n.website,target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/website-icon.png",alt:`${n.name} Website`,width:20,height:20,className:"inline-block"})}),n.linkedin&&m.jsx("a",{href:n.linkedin,target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/linkedin-icon.png",alt:`${n.name} LinkedIn`,width:28,height:28,className:"inline-block"})}),n.github&&m.jsx("a",{href:n.github,target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/github-icon.png",alt:`${n.name} Website`,width:20,height:20,className:"inline-block"})})]}):null,ST=()=>m.jsxs("div",{className:"flex flex-col items-center p-8 min-h-screen mt-24",children:[m.jsxs("div",{className:"max-w-5xl text-center",children:[m.jsx("h1",{className:"text-4xl font-bold mb-7",children:"Welcome to Yale Labs! 🔬"}),m.jsxs("p",{className:"text-lg text-gray-700 mb-10 leading-relaxed",children:["A collaboration between the"," ",m.jsx("a",{href:"https://yalecomputersociety.org/",target:"_blank",rel:"noopener noreferrer",className:"text-blue-500",children:"Yale Computer Society"})," ","and the"," ",m.jsx("a",{href:"https://www.yura.yale.edu/",target:"_blank",rel:"noopener noreferrer",className:"text-blue-500",children:"Yale Undergraduate Research Association"}),", Yale Labs brings students a single, streamlined platform to browse research opportunities at Yale! With a mix of lab listings submitted by professors and scraped from the internet, our mission at Yale Labs is to make finding your next lab as stress-free as possible with all the information you need in one place."]}),m.jsx("h1",{className:"text-3xl font-bold mb-7",children:"Help us with our first release!"}),m.jsxs("p",{className:"text-lg text-gray-700 mb-10 leading-relaxed",children:["While we are working dilligently to get more up-to-date listings on the site, we are also working on changes to improve the browsing experience! As you look around the site, please let us know in the"," ",m.jsx("a",{href:"https://docs.google.com/forms/d/e/1FAIpQLSf2BE6MBulJHWXhDDp3y4Nixwe6EH0Oo9X1pTo976-KrJKv5g/viewform?usp=dialog",target:"_blank",rel:"noopener noreferrer",className:"text-blue-500",children:"feedback form"})," ","if there is anything that is broken, annoying, or that you would like to see added to the site."]}),m.jsx("a",{href:"https://yalecomputersociety.org/",target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/ycs-icon.png",alt:"y/cs Website",width:40,height:40,className:"inline-block mx-2"})}),m.jsx("a",{href:"https://www.yura.yale.edu/",target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/yura-icon.png",alt:"YURA Website",width:32,height:40,className:"inline-block mx-2"})}),m.jsx("a",{href:"https://github.com/YaleComputerSociety/ylabs",target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/github-icon.png",alt:"RDB Github",width:40,height:40,className:"inline-block mx-2"})})]}),m.jsxs("div",{className:"max-w-6xl text-center mt-16",children:[m.jsx("h2",{className:"text-3xl font-bold mb-10",children:"Meet our team"}),m.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-16",children:wT.map(n=>m.jsx("div",{className:"bg-gray-50 p-3 rounded-lg shadow-md",children:m.jsx(bv,{developer:n})},n.name))}),m.jsx("h2",{className:"text-3xl font-bold mb-10",children:"RDB alumni"}),m.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4",children:ET.map(n=>m.jsx("div",{className:"bg-gray-50 p-3 rounded-lg shadow-md",children:m.jsx(bv,{developer:n})},n.name))})]})]}),wT=[{name:"Ryan Fernandes",position:"Development Lead",image:"/assets/developers/RyanFernandes.jpeg",location:"Natick, MA",linkedin:"https://www.linkedin.com/in/ryan-fernandes-088109284/",github:"https://github.com/Ryfernandes"},{name:"Sebastian Gonzalez",image:"/assets/developers/SebastianGonzalez.jpeg",position:"Developer",location:"Montclair, NJ",github:"https://github.com/Seb-G0",linkedin:"https://www.linkedin.com/in/sebastian-ravi-gonzalez/"},{name:"Dohun Kim",position:"Developer",image:"/assets/developers/DohunKim.jpeg",location:"Anyang-si, South Korea",github:"https://github.com/rlaehgnss",linkedin:"https://www.linkedin.com/in/dohun-kim-848028251/"},{name:"Alan Zhong",image:"/assets/developers/AlanZhong.jpeg",position:"Developer",location:"Basking Ridge, NJ",github:"https://github.com/azh248",linkedin:"https://www.linkedin.com/in/azhong248/"},{name:"Quntao Zheng",image:"/assets/developers/QuntaoZheng.jpeg",position:"Developer",location:"New York, NY",github:"https://github.com/quntao-z",linkedin:"https://www.linkedin.com/in/quntao-zheng/"},{name:"Christian Phanhthourath",position:"Developer",image:"/assets/developers/ChristianPhanhthourath.jpeg",location:"Marietta, GA",github:"https://github.com/cphanhth",linkedin:"https://linkedin.com/in/christianphanhthourath"},{name:"Christina Xu",position:"Developer",image:"/assets/developers/ChristinaXu.jpeg",location:"Lincoln, Nebraska",github:"https://github.com/shadaxiong"}],ET=[{name:"Julian Lee",position:"RDB Founder",location:"New York, NY",github:"https://github.com/JulianLee123"},{name:"Miles Yamner",position:"Developer",location:"New York, NY"},{name:"Landon Hellman",position:"Developer",location:"Santa Barbara, CA"}],qd=({error:n})=>n?m.jsx("div",{className:"text-red-500 text-xs mt-1",children:n}):null,xv=({id:n,label:r,value:o,onChange:l,placeholder:s,error:c,onValidate:f})=>m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:n,children:r}),m.jsx("input",{id:n,type:"text",value:o,onChange:p=>{l(p.target.value),f&&f(p.target.value)},placeholder:s,className:`shadow appearance-none border ${c?"border-red-500":""} rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline whitespace-nowrap overflow-x-auto`}),m.jsx(qd,{error:c})]}),CT=({id:n,label:r,value:o,onChange:l,placeholder:s,rows:c=10,error:f,onValidate:p})=>m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:"description",children:r}),m.jsx("textarea",{id:n,value:o,onChange:h=>{l(h.target.value),p&&p(h.target.value)},placeholder:s,className:"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline overflow-x-auto",rows:c}),m.jsx(qd,{error:f})]}),pl=({label:n,items:r,setItems:o,placeholder:l,bgColor:s,textColor:c,buttonColor:f,error:p,type:h="text",permanentValue:g,onValidate:y,infoText:v})=>{const S=R.useRef(null),[j,w]=R.useState(!1),x=C=>{if(C.key==="Enter"&&S.current&&S.current.value.trim()){C.preventDefault();const N=S.current.value.trim();if(!r.includes(N)&&(!g||N!==g)){const M=[...r,N];o(M),S.current.value="",y&&y(g?[...M,g]:M)}}},_=C=>{const N=[...r];N.splice(C,1),o(N),y&&y(g?[...N,g]:N)},T=()=>{const C=[];return g&&C.push(m.jsxs("span",{className:`${s} ${c} px-2 py-1 rounded text-sm flex items-center`,children:[m.jsx("span",{className:"whitespace-nowrap",children:g}),m.jsxs("div",{className:"ml-2 w-4 h-4 relative",onMouseEnter:()=>w(!0),onMouseLeave:()=>w(!1),children:[m.jsx("div",{className:"rounded-full border border-current flex items-center justify-center w-full h-full cursor-pointer",children:m.jsx("span",{className:"text-xs",children:"?"})}),j&&m.jsx("div",{className:"absolute left-6 -top-1 bg-gray-800 text-white text-xs rounded py-1 px-2 whitespace-nowrap z-10",children:"Creator"})]})]},"permanent")),r.forEach((N,M)=>{g!==N&&C.push(m.jsxs("span",{className:`${s} ${c} px-2 py-1 rounded text-sm flex items-center`,children:[m.jsx("span",{className:"whitespace-nowrap",children:N}),m.jsx("button",{type:"button",onClick:()=>_(M),className:`ml-2 ${f}`,children:"×"})]},M))}),C};return m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",children:n}),v&&m.jsx("div",{className:"text-xs text-gray-500 mb-2",children:v}),m.jsx("div",{className:"flex flex-wrap gap-2 mb-2 overflow-x-auto",children:T()}),m.jsx("div",{className:"flex",children:m.jsx("input",{type:h,ref:S,placeholder:l,className:"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500",onKeyDown:x})}),m.jsx("div",{className:"text-xs text-gray-500 mt-1",children:"Press Enter to add"}),m.jsx(qd,{error:p})]})},RT=({departments:n,availableDepartments:r,onAddDepartment:o,onRemoveDepartment:l})=>{const[s,c]=R.useState(!1),[f,p]=R.useState(""),[h,g]=R.useState(-1),y=R.useRef(null),v=R.useRef(null),S=r.filter(x=>x.toLowerCase().includes(f.toLowerCase()));R.useEffect(()=>{const x=_=>{y.current&&!y.current.contains(_.target)&&(c(!1),p(""))};return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[]);const j=x=>{switch(x.key){case"ArrowDown":x.preventDefault(),g(_=>__>0?_-1:0);break;case"Enter":x.preventDefault(),h>=0&&h{if(Object.keys(It).includes(x))switch(It[x]){case 0:return"bg-blue-200";case 1:return"bg-green-200";case 2:return"bg-yellow-200";case 3:return"bg-red-200";case 4:return"bg-purple-200";case 5:return"bg-pink-200";case 6:return"bg-teal-200";case 7:return"bg-orange-200";default:return"bg-gray-100"}return"bg-gray-100"};return m.jsxs("div",{className:"mb-4",ref:y,children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",children:"⭐ Departments"}),m.jsxs("div",{className:"text-xs text-gray-500 mb-2",children:["Don't see your department? Let us know"," ",m.jsx("a",{href:"https://docs.google.com/forms/d/e/1FAIpQLSf2BE6MBulJHWXhDDp3y4Nixwe6EH0Oo9X1pTo976-KrJKv5g/viewform",target:"_blank",rel:"noopener noreferrer",className:"text-blue-500",children:"here"})]}),m.jsx("div",{className:"flex flex-wrap gap-2 mb-2 overflow-x-auto",children:n.map((x,_)=>m.jsxs("span",{className:`${w(x)} text-gray-900 px-2 py-1 rounded text-sm flex items-center`,children:[m.jsx("span",{className:"whitespace-nowrap",children:x}),m.jsx("button",{type:"button",onClick:()=>l(_),className:"ml-2 text-gray-500 hover:text-gray-700",children:"×"})]},_))}),m.jsxs("div",{className:"relative",children:[m.jsxs("div",{className:"relative",children:[m.jsx("input",{ref:v,type:"text",value:f,onClick:()=>c(!0),onChange:x=>{p(x.target.value),g(-1)},onKeyDown:j,onFocus:()=>c(!0),className:"shadow appearance-none border rounded w-full py-2 px-3 pr-10 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500",placeholder:"Add departments..."}),m.jsx("div",{className:"absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 cursor-pointer",onClick:()=>{s&&p(""),c(!s),!s&&v.current&&v.current.focus()},children:m.jsx("svg",{className:"fill-current h-4 w-4",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})})]}),s&&m.jsx("div",{className:"absolute w-full bg-white rounded-lg z-10 shadow-lg border overflow-hidden mt-1 max-h-[300px] md:max-h-[350px] border-gray-300",tabIndex:-1,children:m.jsx("ul",{className:"max-h-[350px] p-1 overflow-y-auto",tabIndex:-1,children:S.length>0?S.map((x,_)=>m.jsx("li",{onClick:()=>{o(x),p("")},className:`p-2 cursor-pointer ${h===_?"bg-blue-100":"hover:bg-gray-100"}`,tabIndex:-1,onMouseDown:T=>T.preventDefault(),children:x},_)):m.jsx("li",{className:"p-2 text-gray-500",tabIndex:-1,children:"No departments found"})})})]})]})},TT=({hiringStatus:n,setHiringStatus:r})=>{const[o,l]=R.useState(!1),[s,c]=R.useState(-1),f=R.useRef(null),p=R.useRef(null),h=[{value:-1,label:"Lab not seeking applicants"},{value:0,label:"Lab open to applicants"},{value:1,label:"Lab seeking applicants"}],g=v=>{r(v),l(!1),p.current&&p.current.blur()},y=v=>{switch(v.key){case"ArrowDown":v.preventDefault(),c(S=>SS>0?S-1:0);break;case"Enter":v.preventDefault(),s>=0&&s{l(!0)},onKeyDown:y,onFocus:()=>l(!0),onBlur:()=>{setTimeout(()=>{var v;(v=f.current)!=null&&v.contains(document.activeElement)||l(!1)},100)},className:"shadow appearance-none border rounded w-full py-2 px-3 pr-10 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer"}),m.jsx("div",{className:"absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 cursor-pointer",onClick:()=>{l(!o),!o&&p.current&&p.current.focus()},children:m.jsx("svg",{className:"fill-current h-4 w-4",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})})]}),o&&m.jsx("div",{className:"absolute left-0 right-0 bg-white rounded-lg z-10 shadow-lg border overflow-hidden mt-1 max-h-[350px] border-gray-300",tabIndex:-1,children:m.jsx("ul",{className:"max-h-[350px] overflow-y-auto",tabIndex:-1,children:h.map((v,S)=>m.jsxs("li",{onClick:()=>g(v.value),className:`p-2 cursor-pointer flex items-center justify-between ${s===S?"bg-blue-100":"hover:bg-gray-100"}`,tabIndex:-1,onMouseDown:j=>j.preventDefault(),children:[m.jsx("span",{children:v.label}),n===v.value&&m.jsx("svg",{className:"h-4 w-4 text-blue-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M5 13l4 4L19 7"})})]},S))})})]})]})},Sv=n=>n.trim()?void 0:"Title is required",jT=n=>n.trim()?void 0:"Description is required",wv=n=>{if(!n)return;const r=parseInt(n,10),o=new Date().getFullYear();if(isNaN(r)||!Number.isInteger(r))return"Year must be a valid integer";if(r<1701)return"Yale wasn't established until 1701!";if(r>o)return"Year cannot be in the future";if(n.trim().includes(" "))return"Year cannot include spaces";if(r.toString()!=n.trim())return"Year cannot include non-numeric characters"},Ev=n=>n.length>0?void 0:"At least one professor is required",Cv=n=>{if(n.length===0)return"At least one email is required";for(const r of n)if(!r.includes("@")||!r.includes(".")||r.includes(" "))return`Invalid email format: ${r}`},Rv=n=>{if(n.length!==0){for(const r of n)if(!r.includes(".")||r.includes(" "))return`Invalid website format: ${r}`}},Tv=n=>{if(n.length>3)return"Maximum of 3 collaborators allowed";if(new Set(n).size!==n.length)return"Please remove duplicate collaborators";for(const o of n)if(!/^[a-zA-Z0-9]+$/.test(o))return`Invalid format for collaborator netid: ${o}`},OT=({listing:n,isCreated:r,onLoad:o,onCancel:l,onSave:s,onCreate:c})=>{const[f,p]=R.useState(n.title),[h,g]=R.useState([...n.professorNames]),[y,v]=R.useState(`${n.ownerFirstName} ${n.ownerLastName}`),[S,j]=R.useState([...n.departments]),[w,x]=R.useState([]),[_,T]=R.useState([...n.professorIds]),[C,N]=R.useState([...n.emails]),[M,O]=R.useState(n.ownerEmail),[G,A]=R.useState(n.websites?[...n.websites]:[]),[E,z]=R.useState(n.description),[D,B]=R.useState(n.keywords?[...n.keywords]:[]),[q,X]=R.useState(n.established||""),[ne,oe]=R.useState(n.hiringStatus),[Z,le]=R.useState(n.archived),[re,pe]=R.useState(!0),{user:U}=R.useContext(mn),ie=U&&U.netId===n.ownerId,[Q,V]=R.useState({});R.useEffect(()=>{r?(x(H0.filter(L=>!S.includes(L)).sort()),pe(!1)):(pe(!0),xt.get(`/newListings/${n.id}`,{withCredentials:!0}).then(L=>{if(!L.data.listing){console.error(`Response, but no listing ${n.id}:`,L.data),o(n,!1);return}const te=cr(L.data.listing);p(te.title),g([...te.professorNames]),v(`${te.ownerFirstName} ${te.ownerLastName}`),j([...te.departments]),N([...te.emails]),O(te.ownerEmail),A(te.websites?[...te.websites]:[]),z(te.description),B(te.keywords?[...te.keywords]:[]),X(te.established||""),oe(te.hiringStatus),le(te.archived),o(te,!0),x(H0.filter(he=>!te.departments.includes(he)).sort()),pe(!1)}).catch(L=>{console.error(`Error fetching most recent listing ${n.id}:`,L),o(n,!1)}))},[]),R.useEffect(()=>{const L={...n,title:f,professorNames:h,departments:S,emails:C,websites:G,description:E,keywords:D,established:q,hiringStatus:ne,archived:Z};o(L,!0)},[f,h,S,C,G,E,D,q,ne,Z]);const W=L=>{L.preventDefault();const te={title:Sv(f),description:jT(E),established:wv(q),professorNames:Ev([y,...h]),professorIds:Tv(_),emails:Cv([M,...C]),websites:Rv(G)},he=Object.fromEntries(Object.entries(te).filter(([ge,de])=>de!==void 0));if(V(he),Object.keys(he).length===0){const ge={...n,title:f,professorIds:_,professorNames:h,departments:S,emails:C,websites:G,description:E,keywords:D,established:q,hiringStatus:ne,archived:Z};r?Fe({title:"Create Listing",text:"Are you sure you want to create this listing?",icon:"info",buttons:["Cancel","Create"]}).then(de=>{de&&c&&c(ge)}):Fe({title:"Submit Form",text:"Are you sure you want to save these changes?",icon:"info",buttons:["Cancel","Save"]}).then(de=>{de&&s&&s(ge)})}else console.log("Validation errors:",he)},se=()=>{if(r)Fe({title:"Delete Listing",text:"Are you sure you want to delete this listing? This action cannot be undone",icon:"warning",buttons:["Cancel","Delete"],dangerMode:!0}).then(L=>{L&&l&&l()});else{const L={...n};p(L.title),g([...L.professorNames]),v(`${L.ownerFirstName} ${L.ownerLastName}`),j([...L.departments]),N([...L.emails]),O(L.ownerEmail),A(L.websites?[...L.websites]:[]),z(L.description),B(L.keywords?[...L.keywords]:[]),X(L.established||""),oe(L.hiringStatus),le(L.archived),o({...L},!0),l&&l()}},H=L=>{j(te=>[...te,L]),x(te=>te.filter(he=>he!==L).sort())},fe=L=>{const te=[...S],he=te.splice(L,1)[0];j(te),x(ge=>[...ge,he].sort())};return m.jsx("div",{className:"border border-gray-300 border-t-0 bg-white p-6 rounded-b-lg shadow-md relative",children:re?m.jsx("div",{className:"flex flex-col justify-center items-center h-full",children:m.jsx(Ms,{color:"#66CCFF",size:6})}):m.jsxs("form",{onSubmit:W,children:[m.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-6 mb-16",children:[m.jsxs("div",{className:"col-span-1",children:[m.jsx(xv,{id:"title",label:"⭐ Listing Title",value:f,onChange:p,placeholder:"Add title",error:Q.title,onValidate:L=>{Q.title&&V(te=>({...te,title:Sv(L)}))}}),m.jsx(CT,{id:"description",label:"⭐ Description",value:E,onChange:z,placeholder:"Add description",rows:10,error:Q.description}),m.jsx(xv,{id:"established",label:"Lab Established Year",value:q,onChange:X,placeholder:"e.g. 2006",error:Q.established,onValidate:L=>{Q.established&&V(te=>({...te,established:wv(L)}))}})]}),m.jsx("div",{className:"col-span-1 md:col-span-2",children:m.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-6",children:[m.jsxs("div",{children:[m.jsx(TT,{hiringStatus:ne,setHiringStatus:oe}),ie&&m.jsx(pl,{label:"Co-Editors",items:_,setItems:T,placeholder:"Add netid",bgColor:"bg-green-100",textColor:"text-green-800",buttonColor:"text-green-500 hover:text-green-700",error:Q.professorIds,onValidate:L=>V(te=>({...te,professorIds:Tv(L)})),infoText:"Allow others in your lab to update this listing"}),m.jsx(pl,{label:"Professors",items:h,setItems:g,placeholder:"Add professor",bgColor:"bg-blue-100",textColor:"text-blue-800",buttonColor:"text-blue-500 hover:text-blue-700",error:Q.professorNames,permanentValue:y,onValidate:L=>V(te=>({...te,professorNames:Ev(L)}))}),m.jsx(pl,{label:"Emails",items:C,setItems:N,placeholder:"Add email",bgColor:"bg-green-100",textColor:"text-green-800",buttonColor:"text-green-500 hover:text-green-700",error:Q.emails,permanentValue:M,type:"email",onValidate:L=>V(te=>({...te,emails:Cv(L)}))}),m.jsxs("div",{className:"mb-6 flex items-center",children:[m.jsx("input",{id:"archived",type:"checkbox",checked:Z,onChange:L=>le(L.target.checked),className:"mr-3 h-4 w-4 text-blue-500 focus:ring-blue-400 cursor-pointer"}),m.jsx("label",{className:"text-gray-700 text-sm font-bold cursor-pointer",htmlFor:"archived",children:"Archive this listing"})]})]}),m.jsxs("div",{children:[m.jsx(RT,{departments:S,availableDepartments:w,onAddDepartment:H,onRemoveDepartment:fe}),m.jsx(pl,{label:"Websites",items:G,setItems:A,placeholder:"Add website URL",bgColor:"bg-yellow-100",textColor:"text-yellow-800",buttonColor:"text-yellow-500 hover:text-yellow-700",error:Q.websites,type:"url",onValidate:L=>V(te=>({...te,websites:Rv(L)}))}),m.jsx(pl,{label:"Keywords (for search)",items:D,setItems:B,placeholder:"Add keyword",bgColor:"bg-gray-100",textColor:"text-gray-800",buttonColor:"text-gray-500 hover:text-gray-700"})]})]})})]}),m.jsxs("div",{className:"absolute bottom-6 right-6 flex space-x-3 bg-white py-2 px-1",children:[m.jsx("button",{type:"button",onClick:se,className:`${r?"bg-red-500 hover:bg-red-700 text-white":"bg-gray-300 hover:bg-gray-400 text-gray-800"} font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline`,children:r?"Delete":"Cancel"}),m.jsx("button",{type:"submit",className:`${r?"bg-green-500 hover:bg-green-700":"bg-blue-500 hover:bg-blue-700"} text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline`,children:r?"Create":"Save"})]})]})})},jv=({listing:n,favListingsIds:r,updateFavorite:o,updateListing:l,postListing:s,postNewListing:c,clearCreatedListing:f,deleteListing:p,openModal:h,globalEditing:g,setGlobalEditing:y,editable:v,reloadListings:S})=>{const[j,w]=R.useState([]),[x,_]=R.useState(0),[T,C]=R.useState(!1),[N,M]=R.useState(r.includes(n.id)),[O,G]=R.useState(n.archived),A=R.useRef(null),E=n.id==="create",[z,D]=R.useState(E),{user:B}=R.useContext(mn),q=B&&B.netId===n.ownerId,X=R.useRef(null),ne=["bg-blue-200","bg-green-200","bg-yellow-200","bg-red-200","bg-purple-200","bg-pink-200","bg-teal-200","bg-orange-200"],oe=()=>n.hiringStatus<0?"bg-red-500":n.hiringStatus===0?"bg-yellow-500":"bg-green-500",Z=()=>n.hiringStatus<0?"Lab not seeking applicants":n.hiringStatus===0?"Lab open to applicants":"Lab seeking applicants";R.useEffect(()=>{r&&M(r.includes(n.id))},[r]),R.useEffect(()=>{G(n.archived)},[n]),R.useEffect(()=>{if(!A.current)return;const V=()=>{const W=A.current;if(!W)return;const se=W.clientWidth;let H=0;const fe=[];_(0);const L=document.createElement("span");L.className="bg-blue-200 text-gray-900 text-xs rounded px-1 py-0.5 mt-2 mr-2",L.style.visibility="hidden",L.style.position="absolute",document.body.appendChild(L);for(let te=0;tese&&(fe.pop(),_(n.departments.length-fe.length))}document.body.removeChild(L),w(fe)};return V(),window.addEventListener("resize",V),()=>window.removeEventListener("resize",V)},[n]);const le=V=>{V.stopPropagation(),n.favorites=N?n.favorites-1:n.favorites+1,n.favorites<0&&(n.favorites=0),o(n,n.id,!N)},re=V=>{V.stopPropagation(),Fe({title:"Delete Listing",text:"Are you sure you want to delete this listing? This action cannot be undone",icon:"warning",buttons:["Cancel","Delete"],dangerMode:!0}).then(W=>{W&&p(n)})},pe=V=>{V.stopPropagation(),O?(G(!1),xt.put(`/newListings/${n.id}/unarchive`,{withCredentials:!0}).then(W=>{const se=W.data.listing,H=cr(se);l(H)}).catch(W=>{G(!0),console.error("Error unarchiving listing:",W),W.response.data.incorrectPermissions?(Fe({text:"You no longer have permission to unarchive this listing",icon:"warning"}),S()):(Fe({text:"Unable to unarchive listing",icon:"warning"}),S())})):(G(!0),xt.put(`/newListings/${n.id}/archive`,{withCredentials:!0}).then(W=>{const se=W.data.listing,H=cr(se);l(H)}).catch(W=>{G(!1),console.error("Error archiving listing:",W),W.response.data.incorrectPermissions?(Fe({text:"You no longer have permission to archive this listing",icon:"warning"}),S()):(Fe({text:"Unable to archive listing",icon:"warning"}),S())}))},U=V=>{V.stopPropagation(),X.current=n,D(!0),y(!0)},ie=()=>{h(n)},Q=V=>V?V.startsWith("http://")||V.startsWith("https://")?V:`https://${V}`:"";return n?m.jsxs("div",{className:"mb-4 relative",children:[m.jsxs("div",{className:"flex relative z-10 rounded-md shadow",children:[m.jsx("div",{className:`${oe()} cursor-pointer rounded-l flex-shrink-0 relative ${O?"opacity-50":""}`,style:{width:"6px"},onMouseEnter:()=>C(!0),onMouseLeave:()=>C(!1),children:T&&m.jsx("div",{className:`${oe()} absolute top-1/2 left-4 -translate-y-1/2 text-white text-xs rounded-full py-1 px-2 z-10 whitespace-nowrap shadow`,children:Z()})}),m.jsxs("div",{className:"p-4 flex-grow grid grid-cols-3 md:grid-cols-12 cursor-pointer bg-white hover:bg-gray-100 border border-gray-300 rounded-r",onClick:ie,children:[m.jsxs("div",{className:"col-span-2 md:col-span-4",children:[m.jsx("p",{className:`text-lg font-semibold mb-3 ${O?"opacity-50":""}`,style:{lineHeight:"1.2rem",height:"1.2rem",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:n.title}),m.jsxs("p",{className:`text-sm text-gray-700 ${O?"opacity-50":""}`,style:{overflow:"hidden",display:"-webkit-box",WebkitLineClamp:1,WebkitBoxOrient:"vertical"},children:["Professors: ",[`${n.ownerFirstName} ${n.ownerLastName}`,...n.professorNames].join(", ")]}),m.jsx("div",{ref:A,className:"flex overflow-hidden",style:{whiteSpace:"nowrap"},children:j.length>0?m.jsxs(m.Fragment,{children:[j.map(V=>m.jsx("span",{className:`${Object.keys(It).includes(V)?ne[It[V]]:"bg-gray-200"} text-gray-900 text-xs rounded px-1 py-0.5 mt-3 mr-2 ${O?"opacity-50":""}`,style:{display:"inline-block",whiteSpace:"nowrap"},children:V},V)),x>0&&m.jsxs("span",{className:`bg-gray-200 text-gray-900 text-xs rounded px-1 py-0.5 mt-3 ${O?"opacity-50":""}`,style:{display:"inline-block",whiteSpace:"nowrap"},children:["+",x," more"]})]}):m.jsx("div",{className:"mt-3 flex",children:m.jsx("span",{className:`invisible bg-gray-200 text-gray-900 text-xs rounded px-1 py-0.5 mr-2 ${O?"opacity-50":""}`,style:{display:"inline-block"},children:"placeholder"})})})]}),m.jsxs("div",{className:"col-span-6 hidden md:flex align-middle",children:[m.jsx("div",{className:`flex-shrink-0 border-l border-gray-300 mx-4 ${O?"opacity-50":""}`}),m.jsx("p",{className:`flex-grow text-gray-800 text-sm overflow-hidden overflow-ellipsis ${O?"opacity-50":""}`,style:{display:"-webkit-box",WebkitLineClamp:4,WebkitBoxOrient:"vertical"},children:n.description})]}),m.jsxs("div",{className:"flex flex-col col-span-1 md:col-span-2 items-end",children:[m.jsxs("div",{children:[n.websites&&n.websites.length>0&&m.jsx("a",{href:Q(n.websites[0]),className:"mr-1",onClick:V=>V.stopPropagation(),target:"_blank",rel:"noopener noreferrer",children:m.jsx("button",{className:"p-1 rounded-full hover:bg-gray-200",children:m.jsx("img",{src:"/assets/icons/new-link.png",alt:"Lab Website",className:`w-5 h-5 ${O?"opacity-50":""}`})})}),!E&&m.jsx("a",{onClick:le,className:"inline-block",children:m.jsx("button",{className:"p-1 hover:bg-gray-200 rounded-full","aria-label":N?"Remove from favorites":"Add to favorites",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",className:`transition-colors ${O?"opacity-50":""}`,fill:N?"#FFDA7B":"none",stroke:N?"#F0C04A":"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:m.jsx("path",{d:"M12 17.75l-6.172 3.245l1.179-6.873l-5-4.867l6.9-1l3.086-6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z"})})})})]}),m.jsx("div",{className:"flex-grow"}),m.jsx("p",{className:"text-[8px] mb-0.5 text-gray-700",style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:"Last Update"}),m.jsx("p",{className:`text-sm text-gray-700 ${O?"opacity-50":""}`,style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:new Date(n.updatedAt).toLocaleDateString()})]})]})]},n.id),m.jsx("div",{className:`transform transition-all duration-700 overflow-hidden ${v&&z?"translate-y-0 max-h-[4000px]":"-translate-y-5 max-h-0"} pl-2 pr-0.5 -mt-1`,children:v&&z&&m.jsx(OT,{listing:n,isCreated:E,onLoad:(V,W)=>{if(!W){D(!1),Fe({text:"Unable to fetch most recent listing",icon:"warning"}),S();return}l(V)},onCancel:()=>{E?(D(!1),f()):(X.current&&l({...X.current}),D(!1),y(!1))},onSave:V=>{s(V),D(!1),y(!1)},onCreate:V=>{c(V),D(!1),y(!1)}})}),v&&!z&&m.jsx("div",{className:"flex justify-center",children:m.jsxs("div",{className:"bg-white border border-gray-300 border-t-0 rounded-b-lg shadow px-3 pb-1 pt-3 -mt-1 inline-flex space-x-2",children:[m.jsx("button",{className:"p-1 rounded-full hover:bg-gray-100 text-gray-600 hover:text-green-600 transition-colors",onClick:pe,title:O?"Unarchive listing":"Archive listing","aria-label":O?"Unarchive listing":"Archive listing",children:O?m.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"opacity-50",children:[m.jsx("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),m.jsx("path",{d:"M10.585 10.587a2 2 0 0 0 2.829 2.828"}),m.jsx("path",{d:"M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87"}),m.jsx("path",{d:"M3 3l18 18"})]}):m.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[m.jsx("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),m.jsx("path",{d:"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"}),m.jsx("path",{d:"M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6"})]})}),m.jsx("button",{className:`p-1 rounded-full ${g?"text-gray-400 cursor-not-allowed":"hover:bg-gray-100 text-gray-600 hover:text-blue-600 transition-colors"}`,onClick:V=>{V.stopPropagation(),g||U(V)},title:g?"Must close current editor":"Edit listing","aria-label":g?"Editing disabled":"Edit listing",disabled:g,children:m.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`${O||g?"opacity-50":""}`,children:[m.jsx("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),m.jsx("path",{d:"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"}),m.jsx("path",{d:"M13.5 6.5l4 4"})]})}),m.jsx("button",{className:`p-1 rounded-full ${q&&!g?"hover:bg-gray-100 text-gray-600 hover:text-red-600 transition-colors":"text-gray-400 cursor-not-allowed"}`,onClick:V=>{V.stopPropagation(),q&&!g&&re(V)},title:q?g?"Must close current editor":"Delete listing":"Only owner can delete","aria-label":q?g?"Must close current editor":"Delete listing":"Only owner can delete",disabled:!q,children:m.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`${O||g?"opacity-50":""}`,children:[m.jsx("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),m.jsx("path",{d:"M4 7l16 0"}),m.jsx("path",{d:"M10 11l0 6"}),m.jsx("path",{d:"M14 11l0 6"}),m.jsx("path",{d:"M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"}),m.jsx("path",{d:"M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"})]})})]})})]}):null},NT=({isOpen:n,onClose:r,listing:o,favListingsIds:l,updateFavorite:s})=>{const[c,f]=R.useState(o.id==="create"),[p,h]=R.useState(l.includes(o.id)),[g,y]=R.useState(!0),{user:v}=R.useContext(mn),S=["bg-blue-200","bg-green-200","bg-yellow-200","bg-red-200","bg-purple-200","bg-pink-200","bg-teal-200","bg-orange-200"],j=()=>o.hiringStatus<0?"bg-red-500":o.hiringStatus===0?"bg-yellow-500":"bg-green-500",w=()=>o.hiringStatus<0?"Lab not seeking applicants":o.hiringStatus===0?"Lab open to applicants":"Lab seeking applicants",x=C=>{C.target===C.currentTarget&&r()};R.useEffect(()=>{l&&h(l.includes(o.id))},[l]),R.useEffect(()=>{v&&v.userConfirmed&&["admin","professor","faculty"].includes(v.userType)&&y(!1)},[]),R.useEffect(()=>(n&&(document.body.style.overflow="hidden"),()=>{document.body.style.overflow="auto"}),[n]);const _=C=>{C.stopPropagation(),o.favorites=p?o.favorites-1:o.favorites+1,o.favorites<0&&(o.favorites=0),s(o,o.id,!p)},T=C=>C?C.startsWith("http://")||C.startsWith("https://")?C:`https://${C}`:"";return!n||!o?null:m.jsx("div",{className:"fixed inset-0 bg-black/65 z-50 flex items-center justify-center overflow-y-auto p-4 pt-24",onClick:x,children:m.jsxs("div",{className:"bg-white rounded-lg shadow-xl w-full max-w-4xl max-h-[80vh] overflow-y-auto",onClick:C=>C.stopPropagation(),children:[m.jsx("div",{className:`${j()} h-2 w-full rounded-t-lg`}),m.jsxs("div",{className:"p-6 relative",children:[m.jsxs("div",{className:"absolute top-4 right-4",children:[!c&&m.jsx("a",{onClick:_,className:"inline-block",children:m.jsx("button",{className:"p-1 hover:bg-gray-100 rounded-full mr-2","aria-label":p?"Remove from favorites":"Add to favorites",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",className:"transition-colors h-6 w-6",fill:p?"#FFDA7B":"none",stroke:p?"#F0C04A":"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:m.jsx("path",{d:"M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z"})})})}),m.jsx("button",{onClick:r,className:"p-1 rounded-full hover:bg-gray-100","aria-label":"Close",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-6 w-6",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),m.jsx("div",{className:"mb-6 pr-20",children:m.jsxs("div",{className:"flex flex-col md:flex-row md:items-center gap-2",children:[m.jsx("h2",{className:"text-2xl font-bold md:max-w-[400px] lg:max-w-[600px]",children:o.title}),m.jsx("span",{className:`${j()} mt-2 md:mt-0 md:ml-2 text-white text-xs px-2 py-1 rounded-full inline-block w-fit`,children:w()})]})}),m.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-6",children:[m.jsxs("div",{className:"col-span-1",children:[m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Professors"}),m.jsx("div",{className:"space-y-2",children:[`${o.ownerFirstName} ${o.ownerLastName}`,...o.professorNames].map((C,N)=>m.jsxs("div",{className:"flex items-center",children:[m.jsx("div",{className:"w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center mr-2",children:C.charAt(0).toUpperCase()}),m.jsx("span",{children:C})]},N))})]}),m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Departments"}),m.jsx("div",{className:"flex flex-wrap gap-2",children:o.departments.map(C=>m.jsx("span",{className:`${Object.keys(It).includes(C)?S[It[C]]:"bg-gray-200"} text-gray-900 text-xs rounded px-2 py-1`,children:C},C))})]}),m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Contact Information"}),m.jsxs("div",{className:"mb-4",children:[m.jsx("h4",{className:"text-md font-medium",children:"Emails"}),m.jsx("ul",{className:"mt-1 space-y-1",children:[o.ownerEmail,...o.emails].map((C,N)=>m.jsx("li",{children:m.jsx("a",{href:`mailto:${C}`,className:"text-blue-600 hover:underline",children:C})},N))})]}),o.websites&&o.websites.length>0&&m.jsxs("div",{children:[m.jsx("h4",{className:"text-md font-medium",children:"Websites"}),m.jsx("ul",{className:"mt-1 space-y-1",children:o.websites.map((C,N)=>m.jsx("li",{className:"truncate",children:m.jsx("a",{href:T(C),target:"_blank",rel:"noopener noreferrer",className:"text-blue-600 hover:underline",children:C})},N))})]})]}),m.jsxs("section",{children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Stats"}),m.jsxs("div",{className:"space-y-2 text-sm",children:[!g&&m.jsxs(m.Fragment,{children:[m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Views:"}),m.jsx("span",{className:"font-medium",children:o.views})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Favorites:"}),m.jsx("span",{className:"font-medium",children:o.favorites})]})]}),o.established&&m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Lab Established:"}),m.jsx("span",{className:"font-medium",children:o.established})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Listing Created:"}),m.jsx("span",{className:"font-medium",children:new Date(o.createdAt).toLocaleDateString()})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Listing Updated:"}),m.jsx("span",{className:"font-medium",children:new Date(o.updatedAt).toLocaleDateString()})]})]})]})]}),m.jsxs("div",{className:"col-span-1 md:col-span-2",children:[m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"About"}),m.jsx("div",{className:"whitespace-pre-wrap",children:o.description})]}),o.archived&&m.jsxs("div",{className:"mt-6 p-3 bg-red-100 text-red-700 rounded-lg",children:[m.jsx("div",{className:"font-semibold",children:"This listing is archived"}),m.jsx("div",{className:"text-sm",children:"Archived listings are not visible in search results or as favorites."})]})]})]})]})]})})},_T=({globalEditing:n,handleCreate:r})=>m.jsx("button",{className:`py-1 px-2 rounded-md ${n?"text-gray-400 cursor-not-allowed":"hover:bg-gray-100 text-green-500 hover:text-green-700 transition-colors"}`,onClick:o=>{o.stopPropagation(),n||r()},title:n?"Must close current editor":"Create listing","aria-label":n?"Create listing disabled":"Edit listing",disabled:n,children:m.jsxs("div",{className:"flex items-center justify-center",children:[m.jsx("span",{className:"mr-1 text-md font-semibold",children:"Create Listing"}),m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-5 w-5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"})})]})});function AT(){return m.jsx("div",{children:m.jsx("iframe",{className:"w-[200px] h-[112.5px] sm:w-[400px] sm:h-[225px] lg:w-[800px] lg:h-[450px] transition-all",src:"https://www.youtube.com/embed/Crf3Tyjsk2k?si=eXHPqMv_Fwi04FT4",title:"YouTube video player",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",referrerPolicy:"strict-origin-when-cross-origin",allowFullScreen:!0})})}const MT=()=>{const[n,r]=R.useState([]),[o,l]=R.useState([]),[s,c]=R.useState([]),[f,p]=R.useState(!1),[h,g]=R.useState(!1),[y,v]=R.useState(!1),[S,j]=R.useState(!1),[w,x]=R.useState(null),{user:_}=R.useContext(mn);R.useEffect(()=>{T()},[]),R.useEffect(()=>{const q=X=>{if(y){const ne="You have unsaved changes that will be lost if you leave this page.";return X.preventDefault(),X.returnValue=ne,ne}};return y&&window.addEventListener("beforeunload",q),()=>{window.removeEventListener("beforeunload",q)}},[y]);const T=async()=>{p(!0),await xt.get("/users/listings",{withCredentials:!0}).then(q=>{const X=q.data.ownListings.map(function(oe){return cr(oe)}),ne=q.data.favListings.map(function(oe){return cr(oe)});r(X),l(ne)}).catch(q=>{console.error("Error fetching listings:",q),r([]),l([]),p(!1),Fe({text:"Error fetching your listings",icon:"warning"})}),xt.get("/users/favListingsIds",{withCredentials:!0}).then(q=>{c(q.data.favListingsIds),p(!1)}).catch(q=>{console.error("Error fetching user's favorite listings:",q),r([]),l([]),c([]),p(!1),Fe({text:"Error fetching your listings",icon:"warning"})})},C=q=>{x(q),g(!0)},N=()=>{g(!1),x(null)},M=(q,X,ne)=>{const oe=o,Z=s;ne?(l([q,...oe]),c([X,...Z]),xt.put("/users/favListings",{withCredentials:!0,data:{favListings:[q.id]}}).catch(le=>{l(oe),c(Z),console.error("Error favoriting listing:",le),Fe({text:"Unable to favorite listing",icon:"warning"}),T()})):(l(oe.filter(le=>le.id!==X)),c(Z.filter(le=>le!==X)),xt.delete("/users/favListings",{withCredentials:!0,data:{favListings:[X]}}).catch(le=>{l(oe),c(Z),console.error("Error unfavoriting listing:",le),Fe({text:"Unable to unfavorite listing",icon:"warning"}),T()}))},O=q=>{r(X=>X.map(ne=>ne.id===q.id?q:ne)),l(X=>X.map(ne=>ne.id===q.id?q:ne))},G=q=>q.filter(X=>X.confirmed&&!X.archived),A=async q=>{p(!0),xt.put(`/newListings/${q.id}`,{withCredentials:!0,data:q}).then(X=>{T()}).catch(X=>{console.error("Error saving listing:",X),X.response.data.incorrectPermissions?(Fe({text:"You no longer have permission to edit this listing",icon:"warning"}),T()):(Fe({text:"Unable to update listing",icon:"warning"}),T())})},E=q=>{p(!0),xt.post("/newListings",{withCredentials:!0,data:q}).then(X=>{T(),v(!1),p(!1),j(!1)}).catch(X=>{console.error("Error posting new listing:",X),Fe({text:"Unable to create listing",icon:"warning"}),T(),v(!1),p(!1),j(!1)})},z=()=>{r(q=>q.filter(X=>X.id!=="create")),v(!1),j(!1)},D=q=>{p(!0),xt.delete(`/newListings/${q.id}`,{withCredentials:!0}).then(X=>{T(),p(!1)}).catch(X=>{console.error("Error deleting listing:",X),Fe({text:"Unable to delete listing",icon:"warning"}),T(),p(!1)})},B=()=>{xt.get("/newListings/skeleton",{withCredentials:!0}).then(q=>{const X=cr(q.data.listing);r(ne=>[...ne,X]),v(!0),j(!0)}).catch(q=>{console.error("Error fetching skeleton listing:",q),Fe({text:"Unable to create listing",icon:"warning"})})};return m.jsx("div",{className:"mx-auto max-w-[1300px] px-6 mt-24 w-full",children:f?m.jsx("div",{style:{marginTop:"17%",textAlign:"center"},children:m.jsx(Ms,{color:"#66CCFF",size:10})}):m.jsxs("div",{children:[_&&!_.userConfirmed&&m.jsx("div",{className:"bg-amber-100 border-l-4 border-amber-500 text-amber-700 p-4 mb-6 rounded shadow-sm",children:m.jsx("div",{className:"flex items-center",children:m.jsx("p",{className:"font-medium",children:"Your account is pending confirmation. Any listings that you create will not be publicly visible as favorites or in search results until your account is confirmed."})})}),m.jsx("p",{className:"text-xl text-gray-700 mb-4",children:"Your listings"}),n.length>0&&m.jsx("ul",{children:n.map(q=>m.jsx("li",{className:"mb-2",children:m.jsx(jv,{listing:q,favListingsIds:s,updateFavorite:M,updateListing:O,postListing:A,postNewListing:E,clearCreatedListing:z,deleteListing:D,openModal:C,globalEditing:y,setGlobalEditing:v,editable:!0,reloadListings:T})},q.id))}),_&&(_.userType==="professor"||_.userType==="faculty"||_.userType==="admin")&&!S&&m.jsx("div",{className:"my-8 flex justify-center align-center",children:m.jsx(_T,{globalEditing:y,handleCreate:B})}),m.jsx("p",{className:"text-xl text-gray-700 mb-4",children:"Favorite listings"}),G(o).length>0?m.jsx("ul",{children:G(o).map(q=>m.jsx("li",{className:"mb-2",children:m.jsx(jv,{listing:q,favListingsIds:s,updateFavorite:M,updateListing:O,postListing:A,postNewListing:E,clearCreatedListing:z,deleteListing:D,openModal:C,globalEditing:y,setGlobalEditing:v,editable:!1,reloadListings:T})},q.id))}):m.jsx("p",{className:"my-4 flex align-center",children:"No listings found."}),_&&(_.userType==="professor"||_.userType==="faculty"||_.userType==="admin")&&m.jsxs(m.Fragment,{children:[m.jsx("h1",{className:"text-4xl mt-24 font-bold text-center mb-7",children:"Learn y/labs!"}),m.jsx("div",{className:"mt-4 flex align-center justify-center mb-4",children:m.jsx(AT,{})})]}),w&&m.jsx(NT,{isOpen:h,onClose:N,listing:w,favListingsIds:s,updateFavorite:M})]})})},kT=()=>{var z;const[n,r]=R.useState(""),[o,l]=R.useState(""),[s,c]=R.useState(""),[f,p]=R.useState(""),[h,g]=R.useState(!1),[y,v]=R.useState(-1),S=R.useRef(null),j=R.useRef(null),[w,x]=R.useState({}),_=[{value:"undergraduate",label:"Undergraduate Student"},{value:"graduate",label:"Graduate Student"},{value:"professor",label:"Professor"},{value:"faculty",label:"Faculty"}],T=D=>D.trim()?void 0:"First name is required",C=D=>D.trim()?void 0:"Last name is required",N=D=>{if(!D.trim())return"Email is required";if(!D.includes("@")||!D.includes(".")||D.includes(" "))return"Invalid email format"},M=D=>D.trim()?void 0:"User type is required",O=D=>{p(D),g(!1),j.current&&j.current.blur(),x(B=>({...B,userType:M(D)}))},G=D=>{switch(D.key){case"ArrowDown":D.preventDefault(),v(B=>B<_.length-1?B+1:B);break;case"ArrowUp":D.preventDefault(),v(B=>B>0?B-1:0);break;case"Enter":D.preventDefault(),y>=0&&y<_.length&&O(_[y].value);break;case"Escape":D.preventDefault(),g(!1),j.current&&j.current.blur();break;case"Tab":g(!1);break}},A=D=>{D.preventDefault();const B={firstName:T(n),lastName:C(o),email:N(s),userType:M(f)},q=Object.fromEntries(Object.entries(B).filter(([X,ne])=>ne!==void 0));x(q),Object.keys(q).length===0&&(console.log("Submitting user information:",{firstName:n,lastName:o,email:s,userType:f}),xt.put("/users",{withCredentials:!0,data:{fname:n,lname:o,email:s,userType:f,userConfirmed:!1}}).then(X=>{Fe("Success!","Your information has been updated! You can now access the site. We will verify your information shortly.","success").then(()=>{window.location.href="/"})}).catch(X=>{console.error("Failed to update user information:",X),Fe("Error!","An error occurred while updating your information. Please try again.","error")}))},E=({error:D})=>D?m.jsx("p",{className:"text-red-500 text-xs italic mt-1",children:D}):null;return m.jsx("div",{className:"fixed inset-0 flex items-center justify-center bg-gray-50 p-4",children:m.jsxs("div",{className:"w-full max-w-md bg-white rounded-lg shadow-lg p-6",children:[m.jsx("div",{className:"mb-6",children:m.jsx("h2",{className:"text-xl font-bold text-gray-800 mb-2",children:"Welcome to y/labs!"})}),m.jsxs("form",{onSubmit:A,children:[m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:"firstName",children:"First Name"}),m.jsx("input",{id:"firstName",type:"text",value:n,onChange:D=>{r(D.target.value),w.firstName&&x(B=>({...B,firstName:T(D.target.value)}))},className:`shadow appearance-none border ${w.firstName?"border-red-500":""} rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500`}),m.jsx(E,{error:w.firstName})]}),m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:"lastName",children:"Last Name"}),m.jsx("input",{id:"lastName",type:"text",value:o,onChange:D=>{l(D.target.value),w.lastName&&x(B=>({...B,lastName:C(D.target.value)}))},className:`shadow appearance-none border ${w.lastName?"border-red-500":""} rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500`}),m.jsx(E,{error:w.lastName})]}),m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:"email",children:"Email"}),m.jsx("input",{id:"email",type:"text",value:s,onChange:D=>{c(D.target.value),w.email&&x(B=>({...B,email:N(D.target.value)}))},className:`shadow appearance-none border ${w.email?"border-red-500":""} rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500`}),m.jsx(E,{error:w.email})]}),m.jsxs("div",{className:"mb-6",ref:S,children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",children:"User Type"}),m.jsxs("div",{className:"relative",children:[m.jsxs("div",{className:"relative",children:[m.jsx("input",{ref:j,id:"userType",type:"text",readOnly:!0,value:f&&((z=_.find(D=>D.value===f))==null?void 0:z.label)||"",onClick:()=>{g(!0)},onKeyDown:G,onFocus:()=>g(!0),onBlur:()=>{setTimeout(()=>{var D;(D=S.current)!=null&&D.contains(document.activeElement)||g(!1)},100)},className:`shadow appearance-none border ${w.userType?"border-red-500":""} rounded w-full py-2 px-3 pr-10 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer`}),m.jsx("div",{className:"absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 cursor-pointer",onClick:()=>{g(!h),!h&&j.current&&j.current.focus()},children:m.jsx("svg",{className:"fill-current h-4 w-4",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})})]}),h&&m.jsx("div",{className:"absolute left-0 right-0 bg-white rounded-lg z-10 shadow-lg border overflow-hidden mt-1 max-h-[200px] border-gray-300",tabIndex:-1,children:m.jsx("ul",{className:"max-h-[200px] overflow-y-auto",tabIndex:-1,children:_.map((D,B)=>m.jsxs("li",{onClick:()=>O(D.value),className:`p-2 cursor-pointer flex items-center justify-between ${y===B?"bg-blue-100":"hover:bg-gray-100"}`,tabIndex:-1,onMouseDown:q=>q.preventDefault(),children:[m.jsx("span",{children:D.label}),f===D.value&&m.jsx("svg",{className:"h-4 w-4 text-blue-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M5 13l4 4L19 7"})})]},B))})})]}),m.jsx(E,{error:w.userType})]}),m.jsx("div",{className:"flex justify-end",children:m.jsx("button",{type:"submit",className:"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded focus:outline-none focus:shadow-outline",children:"Continue"})})]})]})})},DT=()=>{const[n,r]=R.useState(!1);return R.useEffect(()=>{setTimeout(()=>{r(!0)},500)}),R.useEffect(()=>{n&&Fe({text:"We were unable to process your login. Please try again or contact support if the issue persists.",icon:"warning"}).then(()=>{window.location.href="/login"})},[n]),null},Ov=n=>{let r;return n<1?r=5.11916*n**2:r=4.5*Math.log(n+1)+2,(r/100).toFixed(2)};function Pd(){const n=Vy(Py);return n[Nl]||n}function zT(n){return Vt("MuiPaper",n)}At("MuiPaper",["root","rounded","outlined","elevation","elevation0","elevation1","elevation2","elevation3","elevation4","elevation5","elevation6","elevation7","elevation8","elevation9","elevation10","elevation11","elevation12","elevation13","elevation14","elevation15","elevation16","elevation17","elevation18","elevation19","elevation20","elevation21","elevation22","elevation23","elevation24"]);const LT=["className","component","elevation","square","variant"],BT=n=>{const{square:r,elevation:o,variant:l,classes:s}=n,c={root:["root",l,!r&&"rounded",l==="elevation"&&`elevation${o}`]};return Wt(c,zT,s)},UT=ct("div",{name:"MuiPaper",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,r[o.variant],!o.square&&r.rounded,o.variant==="elevation"&&r[`elevation${o.elevation}`]]}})(({theme:n,ownerState:r})=>{var o;return ee({backgroundColor:(n.vars||n).palette.background.paper,color:(n.vars||n).palette.text.primary,transition:n.transitions.create("box-shadow")},!r.square&&{borderRadius:n.shape.borderRadius},r.variant==="outlined"&&{border:`1px solid ${(n.vars||n).palette.divider}`},r.variant==="elevation"&&ee({boxShadow:(n.vars||n).shadows[r.elevation]},!n.vars&&n.palette.mode==="dark"&&{backgroundImage:`linear-gradient(${Lt.alpha("#fff",Ov(r.elevation))}, ${Lt.alpha("#fff",Ov(r.elevation))})`},n.vars&&{backgroundImage:(o=n.vars.overlays)==null?void 0:o[r.elevation]}))}),Wy=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiPaper"}),{className:s,component:c="div",elevation:f=1,square:p=!1,variant:h="elevation"}=l,g=$e(l,LT),y=ee({},l,{component:c,elevation:f,square:p,variant:h}),v=BT(y);return m.jsx(UT,ee({as:c,ownerState:y,className:ke(v.root,s),ref:o},g))});function $T(n){return Vt("MuiAppBar",n)}At("MuiAppBar",["root","positionFixed","positionAbsolute","positionSticky","positionStatic","positionRelative","colorDefault","colorPrimary","colorSecondary","colorInherit","colorTransparent","colorError","colorInfo","colorSuccess","colorWarning"]);const HT=["className","color","enableColorOnDark","position"],qT=n=>{const{color:r,position:o,classes:l}=n,s={root:["root",`color${qe(r)}`,`position${qe(o)}`]};return Wt(s,$T,l)},ss=(n,r)=>n?`${n==null?void 0:n.replace(")","")}, ${r})`:r,PT=ct(Wy,{name:"MuiAppBar",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,r[`position${qe(o.position)}`],r[`color${qe(o.color)}`]]}})(({theme:n,ownerState:r})=>{const o=n.palette.mode==="light"?n.palette.grey[100]:n.palette.grey[900];return ee({display:"flex",flexDirection:"column",width:"100%",boxSizing:"border-box",flexShrink:0},r.position==="fixed"&&{position:"fixed",zIndex:(n.vars||n).zIndex.appBar,top:0,left:"auto",right:0,"@media print":{position:"absolute"}},r.position==="absolute"&&{position:"absolute",zIndex:(n.vars||n).zIndex.appBar,top:0,left:"auto",right:0},r.position==="sticky"&&{position:"sticky",zIndex:(n.vars||n).zIndex.appBar,top:0,left:"auto",right:0},r.position==="static"&&{position:"static"},r.position==="relative"&&{position:"relative"},!n.vars&&ee({},r.color==="default"&&{backgroundColor:o,color:n.palette.getContrastText(o)},r.color&&r.color!=="default"&&r.color!=="inherit"&&r.color!=="transparent"&&{backgroundColor:n.palette[r.color].main,color:n.palette[r.color].contrastText},r.color==="inherit"&&{color:"inherit"},n.palette.mode==="dark"&&!r.enableColorOnDark&&{backgroundColor:null,color:null},r.color==="transparent"&&ee({backgroundColor:"transparent",color:"inherit"},n.palette.mode==="dark"&&{backgroundImage:"none"})),n.vars&&ee({},r.color==="default"&&{"--AppBar-background":r.enableColorOnDark?n.vars.palette.AppBar.defaultBg:ss(n.vars.palette.AppBar.darkBg,n.vars.palette.AppBar.defaultBg),"--AppBar-color":r.enableColorOnDark?n.vars.palette.text.primary:ss(n.vars.palette.AppBar.darkColor,n.vars.palette.text.primary)},r.color&&!r.color.match(/^(default|inherit|transparent)$/)&&{"--AppBar-background":r.enableColorOnDark?n.vars.palette[r.color].main:ss(n.vars.palette.AppBar.darkBg,n.vars.palette[r.color].main),"--AppBar-color":r.enableColorOnDark?n.vars.palette[r.color].contrastText:ss(n.vars.palette.AppBar.darkColor,n.vars.palette[r.color].contrastText)},!["inherit","transparent"].includes(r.color)&&{backgroundColor:"var(--AppBar-background)"},{color:r.color==="inherit"?"inherit":"var(--AppBar-color)"},r.color==="transparent"&&{backgroundImage:"none",backgroundColor:"transparent",color:"inherit"}))}),YT=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiAppBar"}),{className:s,color:c="primary",enableColorOnDark:f=!1,position:p="fixed"}=l,h=$e(l,HT),g=ee({},l,{color:c,position:p,enableColorOnDark:f}),y=qT(g);return m.jsx(PT,ee({square:!0,component:"header",ownerState:g,elevation:4,className:ke(y.root,s,p==="fixed"&&"mui-fixed"),ref:o},h))}),GT=["theme"];function VT(n){let{theme:r}=n,o=$e(n,GT);const l=r[Nl];let s=l||r;return typeof r!="function"&&(l&&!l.vars?s=ee({},l,{vars:null}):r&&!r.vars&&(s=ee({},r,{vars:null}))),m.jsx(VR,ee({},o,{themeId:l?Nl:void 0,theme:s}))}const XT=At("MuiBox",["root"]),FT=zd(),Xf=TR({themeId:Nl,defaultTheme:FT,defaultClassName:XT.root,generateClassName:qy.generate});function KT(n){return Vt("MuiToolbar",n)}At("MuiToolbar",["root","gutters","regular","dense"]);const QT=["className","component","disableGutters","variant"],ZT=n=>{const{classes:r,disableGutters:o,variant:l}=n;return Wt({root:["root",!o&&"gutters",l]},KT,r)},IT=ct("div",{name:"MuiToolbar",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,!o.disableGutters&&r.gutters,r[o.variant]]}})(({theme:n,ownerState:r})=>ee({position:"relative",display:"flex",alignItems:"center"},!r.disableGutters&&{paddingLeft:n.spacing(2),paddingRight:n.spacing(2),[n.breakpoints.up("sm")]:{paddingLeft:n.spacing(3),paddingRight:n.spacing(3)}},r.variant==="dense"&&{minHeight:48}),({theme:n,ownerState:r})=>r.variant==="regular"&&n.mixins.toolbar),WT=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiToolbar"}),{className:s,component:c="div",disableGutters:f=!1,variant:p="regular"}=l,h=$e(l,QT),g=ee({},l,{component:c,disableGutters:f,variant:p}),y=ZT(g);return m.jsx(IT,ee({as:c,className:ke(y.root,s),ref:o,ownerState:g},h))});function JT(n){return Vt("MuiTypography",n)}At("MuiTypography",["root","h1","h2","h3","h4","h5","h6","subtitle1","subtitle2","body1","body2","inherit","button","caption","overline","alignLeft","alignRight","alignCenter","alignJustify","noWrap","gutterBottom","paragraph"]);const ej=["align","className","component","gutterBottom","noWrap","paragraph","variant","variantMapping"],tj=n=>{const{align:r,gutterBottom:o,noWrap:l,paragraph:s,variant:c,classes:f}=n,p={root:["root",c,n.align!=="inherit"&&`align${qe(r)}`,o&&"gutterBottom",l&&"noWrap",s&&"paragraph"]};return Wt(p,JT,f)},nj=ct("span",{name:"MuiTypography",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,o.variant&&r[o.variant],o.align!=="inherit"&&r[`align${qe(o.align)}`],o.noWrap&&r.noWrap,o.gutterBottom&&r.gutterBottom,o.paragraph&&r.paragraph]}})(({theme:n,ownerState:r})=>ee({margin:0},r.variant==="inherit"&&{font:"inherit"},r.variant!=="inherit"&&n.typography[r.variant],r.align!=="inherit"&&{textAlign:r.align},r.noWrap&&{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},r.gutterBottom&&{marginBottom:"0.35em"},r.paragraph&&{marginBottom:16})),Nv={h1:"h1",h2:"h2",h3:"h3",h4:"h4",h5:"h5",h6:"h6",subtitle1:"h6",subtitle2:"h6",body1:"p",body2:"p",inherit:"p"},aj={primary:"primary.main",textPrimary:"text.primary",secondary:"secondary.main",textSecondary:"text.secondary",error:"error.main"},rj=n=>aj[n]||n,ij=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiTypography"}),s=rj(l.color),c=Dd(ee({},l,{color:s})),{align:f="inherit",className:p,component:h,gutterBottom:g=!1,noWrap:y=!1,paragraph:v=!1,variant:S="body1",variantMapping:j=Nv}=c,w=$e(c,ej),x=ee({},c,{align:f,color:s,className:p,component:h,gutterBottom:g,noWrap:y,paragraph:v,variant:S,variantMapping:j}),_=h||(v?"p":j[S]||Nv[S])||"span",T=tj(x);return m.jsx(nj,ee({as:_,ref:o,ownerState:x,className:ke(T.root,p)},w))});function lj(n){return Vt("MuiIconButton",n)}const oj=At("MuiIconButton",["root","disabled","colorInherit","colorPrimary","colorSecondary","colorError","colorInfo","colorSuccess","colorWarning","edgeStart","edgeEnd","sizeSmall","sizeMedium","sizeLarge"]),sj=["edge","children","className","color","disabled","disableFocusRipple","size"],uj=n=>{const{classes:r,disabled:o,color:l,edge:s,size:c}=n,f={root:["root",o&&"disabled",l!=="default"&&`color${qe(l)}`,s&&`edge${qe(s)}`,`size${qe(c)}`]};return Wt(f,lj,r)},cj=ct(Hd,{name:"MuiIconButton",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,o.color!=="default"&&r[`color${qe(o.color)}`],o.edge&&r[`edge${qe(o.edge)}`],r[`size${qe(o.size)}`]]}})(({theme:n,ownerState:r})=>ee({textAlign:"center",flex:"0 0 auto",fontSize:n.typography.pxToRem(24),padding:8,borderRadius:"50%",overflow:"visible",color:(n.vars||n).palette.action.active,transition:n.transitions.create("background-color",{duration:n.transitions.duration.shortest})},!r.disableRipple&&{"&:hover":{backgroundColor:n.vars?`rgba(${n.vars.palette.action.activeChannel} / ${n.vars.palette.action.hoverOpacity})`:Lt.alpha(n.palette.action.active,n.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:"transparent"}}},r.edge==="start"&&{marginLeft:r.size==="small"?-3:-12},r.edge==="end"&&{marginRight:r.size==="small"?-3:-12}),({theme:n,ownerState:r})=>{var o;const l=(o=(n.vars||n).palette)==null?void 0:o[r.color];return ee({},r.color==="inherit"&&{color:"inherit"},r.color!=="inherit"&&r.color!=="default"&&ee({color:l==null?void 0:l.main},!r.disableRipple&&{"&:hover":ee({},l&&{backgroundColor:n.vars?`rgba(${l.mainChannel} / ${n.vars.palette.action.hoverOpacity})`:Lt.alpha(l.main,n.palette.action.hoverOpacity)},{"@media (hover: none)":{backgroundColor:"transparent"}})}),r.size==="small"&&{padding:5,fontSize:n.typography.pxToRem(18)},r.size==="large"&&{padding:12,fontSize:n.typography.pxToRem(28)},{[`&.${oj.disabled}`]:{backgroundColor:"transparent",color:(n.vars||n).palette.action.disabled}})}),fj=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiIconButton"}),{edge:s=!1,children:c,className:f,color:p="default",disabled:h=!1,disableFocusRipple:g=!1,size:y="medium"}=l,v=$e(l,sj),S=ee({},l,{edge:s,color:p,disabled:h,disableFocusRipple:g,size:y}),j=uj(S);return m.jsx(cj,ee({className:ke(j.root,f),centerRipple:!0,focusRipple:!g,disabled:h,ref:o},v,{ownerState:S,children:c}))});function dj(n){const r=Da(n);return r.body===n?Hl(n).innerWidth>r.documentElement.clientWidth:n.scrollHeight>n.clientHeight}function yl(n,r){r?n.setAttribute("aria-hidden","true"):n.removeAttribute("aria-hidden")}function _v(n){return parseInt(Hl(n).getComputedStyle(n).paddingRight,10)||0}function pj(n){const o=["TEMPLATE","SCRIPT","STYLE","LINK","MAP","META","NOSCRIPT","PICTURE","COL","COLGROUP","PARAM","SLOT","SOURCE","TRACK"].indexOf(n.tagName)!==-1,l=n.tagName==="INPUT"&&n.getAttribute("type")==="hidden";return o||l}function Av(n,r,o,l,s){const c=[r,o,...l];[].forEach.call(n.children,f=>{const p=c.indexOf(f)===-1,h=!pj(f);p&&h&&yl(f,s)})}function Ff(n,r){let o=-1;return n.some((l,s)=>r(l)?(o=s,!0):!1),o}function hj(n,r){const o=[],l=n.container;if(!r.disableScrollLock){if(dj(l)){const f=kR(Da(l));o.push({value:l.style.paddingRight,property:"padding-right",el:l}),l.style.paddingRight=`${_v(l)+f}px`;const p=Da(l).querySelectorAll(".mui-fixed");[].forEach.call(p,h=>{o.push({value:h.style.paddingRight,property:"padding-right",el:h}),h.style.paddingRight=`${_v(h)+f}px`})}let c;if(l.parentNode instanceof DocumentFragment)c=Da(l).body;else{const f=l.parentElement,p=Hl(l);c=(f==null?void 0:f.nodeName)==="HTML"&&p.getComputedStyle(f).overflowY==="scroll"?f:l}o.push({value:c.style.overflow,property:"overflow",el:c},{value:c.style.overflowX,property:"overflow-x",el:c},{value:c.style.overflowY,property:"overflow-y",el:c}),c.style.overflow="hidden"}return()=>{o.forEach(({value:c,el:f,property:p})=>{c?f.style.setProperty(p,c):f.style.removeProperty(p)})}}function mj(n){const r=[];return[].forEach.call(n.children,o=>{o.getAttribute("aria-hidden")==="true"&&r.push(o)}),r}class gj{constructor(){this.containers=void 0,this.modals=void 0,this.modals=[],this.containers=[]}add(r,o){let l=this.modals.indexOf(r);if(l!==-1)return l;l=this.modals.length,this.modals.push(r),r.modalRef&&yl(r.modalRef,!1);const s=mj(o);Av(o,r.mount,r.modalRef,s,!0);const c=Ff(this.containers,f=>f.container===o);return c!==-1?(this.containers[c].modals.push(r),l):(this.containers.push({modals:[r],container:o,restore:null,hiddenSiblings:s}),l)}mount(r,o){const l=Ff(this.containers,c=>c.modals.indexOf(r)!==-1),s=this.containers[l];s.restore||(s.restore=hj(s,o))}remove(r,o=!0){const l=this.modals.indexOf(r);if(l===-1)return l;const s=Ff(this.containers,f=>f.modals.indexOf(r)!==-1),c=this.containers[s];if(c.modals.splice(c.modals.indexOf(r),1),this.modals.splice(l,1),c.modals.length===0)c.restore&&c.restore(),r.modalRef&&yl(r.modalRef,o),Av(c.container,r.mount,r.modalRef,c.hiddenSiblings,!1),this.containers.splice(s,1);else{const f=c.modals[c.modals.length-1];f.modalRef&&yl(f.modalRef,!1)}return l}isTopModal(r){return this.modals.length>0&&this.modals[this.modals.length-1]===r}}const vj=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])'].join(",");function yj(n){const r=parseInt(n.getAttribute("tabindex")||"",10);return Number.isNaN(r)?n.contentEditable==="true"||(n.nodeName==="AUDIO"||n.nodeName==="VIDEO"||n.nodeName==="DETAILS")&&n.getAttribute("tabindex")===null?0:n.tabIndex:r}function bj(n){if(n.tagName!=="INPUT"||n.type!=="radio"||!n.name)return!1;const r=l=>n.ownerDocument.querySelector(`input[type="radio"]${l}`);let o=r(`[name="${n.name}"]:checked`);return o||(o=r(`[name="${n.name}"]`)),o!==n}function xj(n){return!(n.disabled||n.tagName==="INPUT"&&n.type==="hidden"||bj(n))}function Sj(n){const r=[],o=[];return Array.from(n.querySelectorAll(vj)).forEach((l,s)=>{const c=yj(l);c===-1||!xj(l)||(c===0?r.push(l):o.push({documentOrder:s,tabIndex:c,node:l}))}),o.sort((l,s)=>l.tabIndex===s.tabIndex?l.documentOrder-s.documentOrder:l.tabIndex-s.tabIndex).map(l=>l.node).concat(r)}function wj(){return!0}function Ej(n){const{children:r,disableAutoFocus:o=!1,disableEnforceFocus:l=!1,disableRestoreFocus:s=!1,getTabbable:c=Sj,isEnabled:f=wj,open:p}=n,h=R.useRef(!1),g=R.useRef(null),y=R.useRef(null),v=R.useRef(null),S=R.useRef(null),j=R.useRef(!1),w=R.useRef(null),x=la(qs(r),w),_=R.useRef(null);R.useEffect(()=>{!p||!w.current||(j.current=!o)},[o,p]),R.useEffect(()=>{if(!p||!w.current)return;const N=Da(w.current);return w.current.contains(N.activeElement)||(w.current.hasAttribute("tabIndex")||w.current.setAttribute("tabIndex","-1"),j.current&&w.current.focus()),()=>{s||(v.current&&v.current.focus&&(h.current=!0,v.current.focus()),v.current=null)}},[p]),R.useEffect(()=>{if(!p||!w.current)return;const N=Da(w.current),M=A=>{_.current=A,!(l||!f()||A.key!=="Tab")&&N.activeElement===w.current&&A.shiftKey&&(h.current=!0,y.current&&y.current.focus())},O=()=>{const A=w.current;if(A===null)return;if(!N.hasFocus()||!f()||h.current){h.current=!1;return}if(A.contains(N.activeElement)||l&&N.activeElement!==g.current&&N.activeElement!==y.current)return;if(N.activeElement!==S.current)S.current=null;else if(S.current!==null)return;if(!j.current)return;let E=[];if((N.activeElement===g.current||N.activeElement===y.current)&&(E=c(w.current)),E.length>0){var z,D;const B=!!((z=_.current)!=null&&z.shiftKey&&((D=_.current)==null?void 0:D.key)==="Tab"),q=E[0],X=E[E.length-1];typeof q!="string"&&typeof X!="string"&&(B?X.focus():q.focus())}else A.focus()};N.addEventListener("focusin",O),N.addEventListener("keydown",M,!0);const G=setInterval(()=>{N.activeElement&&N.activeElement.tagName==="BODY"&&O()},50);return()=>{clearInterval(G),N.removeEventListener("focusin",O),N.removeEventListener("keydown",M,!0)}},[o,l,s,f,p,c]);const T=N=>{v.current===null&&(v.current=N.relatedTarget),j.current=!0,S.current=N.target;const M=r.props.onFocus;M&&M(N)},C=N=>{v.current===null&&(v.current=N.relatedTarget),j.current=!0};return m.jsxs(R.Fragment,{children:[m.jsx("div",{tabIndex:p?0:-1,onFocus:C,ref:g,"data-testid":"sentinelStart"}),R.cloneElement(r,{ref:x,onFocus:T}),m.jsx("div",{tabIndex:p?0:-1,onFocus:C,ref:y,"data-testid":"sentinelEnd"})]})}function Cj(n){return typeof n=="function"?n():n}const Rj=R.forwardRef(function(r,o){const{children:l,container:s,disablePortal:c=!1}=r,[f,p]=R.useState(null),h=la(R.isValidElement(l)?qs(l):null,o);if(_l(()=>{c||p(Cj(s)||document.body)},[s,c]),_l(()=>{if(f&&!c)return sd(o,f),()=>{sd(o,null)}},[o,f,c]),c){if(R.isValidElement(l)){const g={ref:h};return R.cloneElement(l,g)}return m.jsx(R.Fragment,{children:l})}return m.jsx(R.Fragment,{children:f&&Uv.createPortal(l,f)})}),Jy=n=>n.scrollTop;function ws(n,r){var o,l;const{timeout:s,easing:c,style:f={}}=n;return{duration:(o=f.transitionDuration)!=null?o:typeof s=="number"?s:s[r.mode]||0,easing:(l=f.transitionTimingFunction)!=null?l:typeof c=="object"?c[r.mode]:c,delay:f.transitionDelay}}const Tj=["addEndListener","appear","children","easing","in","onEnter","onEntered","onEntering","onExit","onExited","onExiting","style","timeout","TransitionComponent"],jj={entering:{opacity:1},entered:{opacity:1}},Oj=R.forwardRef(function(r,o){const l=Pd(),s={enter:l.transitions.duration.enteringScreen,exit:l.transitions.duration.leavingScreen},{addEndListener:c,appear:f=!0,children:p,easing:h,in:g,onEnter:y,onEntered:v,onEntering:S,onExit:j,onExited:w,onExiting:x,style:_,timeout:T=s,TransitionComponent:C=$n}=r,N=$e(r,Tj),M=R.useRef(null),O=la(M,qs(p),o),G=ne=>oe=>{if(ne){const Z=M.current;oe===void 0?ne(Z):ne(Z,oe)}},A=G(S),E=G((ne,oe)=>{Jy(ne);const Z=ws({style:_,timeout:T,easing:h},{mode:"enter"});ne.style.webkitTransition=l.transitions.create("opacity",Z),ne.style.transition=l.transitions.create("opacity",Z),y&&y(ne,oe)}),z=G(v),D=G(x),B=G(ne=>{const oe=ws({style:_,timeout:T,easing:h},{mode:"exit"});ne.style.webkitTransition=l.transitions.create("opacity",oe),ne.style.transition=l.transitions.create("opacity",oe),j&&j(ne)}),q=G(w),X=ne=>{c&&c(M.current,ne)};return m.jsx(C,ee({appear:f,in:g,nodeRef:M,onEnter:E,onEntered:z,onEntering:A,onExit:B,onExited:q,onExiting:D,addEndListener:X,timeout:T},N,{children:(ne,oe)=>R.cloneElement(p,ee({style:ee({opacity:0,visibility:ne==="exited"&&!g?"hidden":void 0},jj[ne],_,p.props.style),ref:O},oe))}))});function Nj(n){return Vt("MuiBackdrop",n)}At("MuiBackdrop",["root","invisible"]);const _j=["children","className","component","components","componentsProps","invisible","open","slotProps","slots","TransitionComponent","transitionDuration"],Aj=n=>{const{classes:r,invisible:o}=n;return Wt({root:["root",o&&"invisible"]},Nj,r)},Mj=ct("div",{name:"MuiBackdrop",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,o.invisible&&r.invisible]}})(({ownerState:n})=>ee({position:"fixed",display:"flex",alignItems:"center",justifyContent:"center",right:0,bottom:0,top:0,left:0,backgroundColor:"rgba(0, 0, 0, 0.5)",WebkitTapHighlightColor:"transparent"},n.invisible&&{backgroundColor:"transparent"})),kj=R.forwardRef(function(r,o){var l,s,c;const f=Xt({props:r,name:"MuiBackdrop"}),{children:p,className:h,component:g="div",components:y={},componentsProps:v={},invisible:S=!1,open:j,slotProps:w={},slots:x={},TransitionComponent:_=Oj,transitionDuration:T}=f,C=$e(f,_j),N=ee({},f,{component:g,invisible:S}),M=Aj(N),O=(l=w.root)!=null?l:v.root;return m.jsx(_,ee({in:j,timeout:T},C,{children:m.jsx(Mj,ee({"aria-hidden":!0},O,{as:(s=(c=x.root)!=null?c:y.Root)!=null?s:g,className:ke(M.root,h,O==null?void 0:O.className),ownerState:ee({},N,O==null?void 0:O.ownerState),classes:M,ref:o,children:p}))}))});function Dj(n){return typeof n=="function"?n():n}function zj(n){return n?n.props.hasOwnProperty("in"):!1}const Lj=new gj;function Bj(n){const{container:r,disableEscapeKeyDown:o=!1,disableScrollLock:l=!1,manager:s=Lj,closeAfterTransition:c=!1,onTransitionEnter:f,onTransitionExited:p,children:h,onClose:g,open:y,rootRef:v}=n,S=R.useRef({}),j=R.useRef(null),w=R.useRef(null),x=la(w,v),[_,T]=R.useState(!y),C=zj(h);let N=!0;(n["aria-hidden"]==="false"||n["aria-hidden"]===!1)&&(N=!1);const M=()=>Da(j.current),O=()=>(S.current.modalRef=w.current,S.current.mount=j.current,S.current),G=()=>{s.mount(O(),{disableScrollLock:l}),w.current&&(w.current.scrollTop=0)},A=ii(()=>{const Z=Dj(r)||M().body;s.add(O(),Z),w.current&&G()}),E=R.useCallback(()=>s.isTopModal(O()),[s]),z=ii(Z=>{j.current=Z,Z&&(y&&E()?G():w.current&&yl(w.current,N))}),D=R.useCallback(()=>{s.remove(O(),N)},[N,s]);R.useEffect(()=>()=>{D()},[D]),R.useEffect(()=>{y?A():(!C||!c)&&D()},[y,D,C,c,A]);const B=Z=>le=>{var re;(re=Z.onKeyDown)==null||re.call(Z,le),!(le.key!=="Escape"||le.which===229||!E())&&(o||(le.stopPropagation(),g&&g(le,"escapeKeyDown")))},q=Z=>le=>{var re;(re=Z.onClick)==null||re.call(Z,le),le.target===le.currentTarget&&g&&g(le,"backdropClick")};return{getRootProps:(Z={})=>{const le=Fy(n);delete le.onTransitionEnter,delete le.onTransitionExited;const re=ee({},le,Z);return ee({role:"presentation"},re,{onKeyDown:B(re),ref:x})},getBackdropProps:(Z={})=>{const le=Z;return ee({"aria-hidden":!0},le,{onClick:q(le),open:y})},getTransitionProps:()=>{const Z=()=>{T(!1),f&&f()},le=()=>{T(!0),p&&p(),c&&D()};return{onEnter:cv(Z,h==null?void 0:h.props.onEnter),onExited:cv(le,h==null?void 0:h.props.onExited)}},rootRef:x,portalRef:z,isTopModal:E,exited:_,hasTransition:C}}function Uj(n){return Vt("MuiModal",n)}At("MuiModal",["root","hidden","backdrop"]);const $j=["BackdropComponent","BackdropProps","classes","className","closeAfterTransition","children","container","component","components","componentsProps","disableAutoFocus","disableEnforceFocus","disableEscapeKeyDown","disablePortal","disableRestoreFocus","disableScrollLock","hideBackdrop","keepMounted","onBackdropClick","onClose","onTransitionEnter","onTransitionExited","open","slotProps","slots","theme"],Hj=n=>{const{open:r,exited:o,classes:l}=n;return Wt({root:["root",!r&&o&&"hidden"],backdrop:["backdrop"]},Uj,l)},qj=ct("div",{name:"MuiModal",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,!o.open&&o.exited&&r.hidden]}})(({theme:n,ownerState:r})=>ee({position:"fixed",zIndex:(n.vars||n).zIndex.modal,right:0,bottom:0,top:0,left:0},!r.open&&r.exited&&{visibility:"hidden"})),Pj=ct(kj,{name:"MuiModal",slot:"Backdrop",overridesResolver:(n,r)=>r.backdrop})({zIndex:-1}),Yj=R.forwardRef(function(r,o){var l,s,c,f,p,h;const g=Xt({name:"MuiModal",props:r}),{BackdropComponent:y=Pj,BackdropProps:v,className:S,closeAfterTransition:j=!1,children:w,container:x,component:_,components:T={},componentsProps:C={},disableAutoFocus:N=!1,disableEnforceFocus:M=!1,disableEscapeKeyDown:O=!1,disablePortal:G=!1,disableRestoreFocus:A=!1,disableScrollLock:E=!1,hideBackdrop:z=!1,keepMounted:D=!1,onBackdropClick:B,open:q,slotProps:X,slots:ne}=g,oe=$e(g,$j),Z=ee({},g,{closeAfterTransition:j,disableAutoFocus:N,disableEnforceFocus:M,disableEscapeKeyDown:O,disablePortal:G,disableRestoreFocus:A,disableScrollLock:E,hideBackdrop:z,keepMounted:D}),{getRootProps:le,getBackdropProps:re,getTransitionProps:pe,portalRef:U,isTopModal:ie,exited:Q,hasTransition:V}=Bj(ee({},Z,{rootRef:o})),W=ee({},Z,{exited:Q}),se=Hj(W),H={};if(w.props.tabIndex===void 0&&(H.tabIndex="-1"),V){const{onEnter:Ee,onExited:_e}=pe();H.onEnter=Ee,H.onExited=_e}const fe=(l=(s=ne==null?void 0:ne.root)!=null?s:T.Root)!=null?l:qj,L=(c=(f=ne==null?void 0:ne.backdrop)!=null?f:T.Backdrop)!=null?c:y,te=(p=X==null?void 0:X.root)!=null?p:C.root,he=(h=X==null?void 0:X.backdrop)!=null?h:C.backdrop,ge=dv({elementType:fe,externalSlotProps:te,externalForwardedProps:oe,getSlotProps:le,additionalProps:{ref:o,as:_},ownerState:W,className:ke(S,te==null?void 0:te.className,se==null?void 0:se.root,!W.open&&W.exited&&(se==null?void 0:se.hidden))}),de=dv({elementType:L,externalSlotProps:he,additionalProps:v,getSlotProps:Ee=>re(ee({},Ee,{onClick:_e=>{B&&B(_e),Ee!=null&&Ee.onClick&&Ee.onClick(_e)}})),className:ke(he==null?void 0:he.className,v==null?void 0:v.className,se==null?void 0:se.backdrop),ownerState:W});return!D&&!q&&(!V||Q)?null:m.jsx(Rj,{ref:U,container:x,disablePortal:G,children:m.jsxs(fe,ee({},ge,{children:[!z&&y?m.jsx(L,ee({},de)):null,m.jsx(Ej,{disableEnforceFocus:M,disableAutoFocus:N,disableRestoreFocus:A,isEnabled:ie,open:q,children:R.cloneElement(w,H)})]}))})}),Gj=["addEndListener","appear","children","container","direction","easing","in","onEnter","onEntered","onEntering","onExit","onExited","onExiting","style","timeout","TransitionComponent"];function Vj(n,r,o){const l=r.getBoundingClientRect(),s=o&&o.getBoundingClientRect(),c=Hl(r);let f;if(r.fakeTransform)f=r.fakeTransform;else{const g=c.getComputedStyle(r);f=g.getPropertyValue("-webkit-transform")||g.getPropertyValue("transform")}let p=0,h=0;if(f&&f!=="none"&&typeof f=="string"){const g=f.split("(")[1].split(")")[0].split(",");p=parseInt(g[4],10),h=parseInt(g[5],10)}return n==="left"?s?`translateX(${s.right+p-l.left}px)`:`translateX(${c.innerWidth+p-l.left}px)`:n==="right"?s?`translateX(-${l.right-s.left-p}px)`:`translateX(-${l.left+l.width-p}px)`:n==="up"?s?`translateY(${s.bottom+h-l.top}px)`:`translateY(${c.innerHeight+h-l.top}px)`:s?`translateY(-${l.top-s.top+l.height-h}px)`:`translateY(-${l.top+l.height-h}px)`}function Xj(n){return typeof n=="function"?n():n}function us(n,r,o){const l=Xj(o),s=Vj(n,r,l);s&&(r.style.webkitTransform=s,r.style.transform=s)}const Fj=R.forwardRef(function(r,o){const l=Pd(),s={enter:l.transitions.easing.easeOut,exit:l.transitions.easing.sharp},c={enter:l.transitions.duration.enteringScreen,exit:l.transitions.duration.leavingScreen},{addEndListener:f,appear:p=!0,children:h,container:g,direction:y="down",easing:v=s,in:S,onEnter:j,onEntered:w,onEntering:x,onExit:_,onExited:T,onExiting:C,style:N,timeout:M=c,TransitionComponent:O=$n}=r,G=$e(r,Gj),A=R.useRef(null),E=la(qs(h),A,o),z=re=>pe=>{re&&(pe===void 0?re(A.current):re(A.current,pe))},D=z((re,pe)=>{us(y,re,g),Jy(re),j&&j(re,pe)}),B=z((re,pe)=>{const U=ws({timeout:M,style:N,easing:v},{mode:"enter"});re.style.webkitTransition=l.transitions.create("-webkit-transform",ee({},U)),re.style.transition=l.transitions.create("transform",ee({},U)),re.style.webkitTransform="none",re.style.transform="none",x&&x(re,pe)}),q=z(w),X=z(C),ne=z(re=>{const pe=ws({timeout:M,style:N,easing:v},{mode:"exit"});re.style.webkitTransition=l.transitions.create("-webkit-transform",pe),re.style.transition=l.transitions.create("transform",pe),us(y,re,g),_&&_(re)}),oe=z(re=>{re.style.webkitTransition="",re.style.transition="",T&&T(re)}),Z=re=>{f&&f(A.current,re)},le=R.useCallback(()=>{A.current&&us(y,A.current,g)},[y,g]);return R.useEffect(()=>{if(S||y==="down"||y==="right")return;const re=AR(()=>{A.current&&us(y,A.current,g)}),pe=Hl(A.current);return pe.addEventListener("resize",re),()=>{re.clear(),pe.removeEventListener("resize",re)}},[y,S,g]),R.useEffect(()=>{S||le()},[S,le]),m.jsx(O,ee({nodeRef:A,onEnter:D,onEntered:q,onEntering:B,onExit:ne,onExited:oe,onExiting:X,addEndListener:Z,appear:p,in:S,timeout:M},G,{children:(re,pe)=>R.cloneElement(h,ee({ref:E,style:ee({visibility:re==="exited"&&!S?"hidden":void 0},N,h.props.style)},pe))}))});function Kj(n){return Vt("MuiDrawer",n)}At("MuiDrawer",["root","docked","paper","paperAnchorLeft","paperAnchorRight","paperAnchorTop","paperAnchorBottom","paperAnchorDockedLeft","paperAnchorDockedRight","paperAnchorDockedTop","paperAnchorDockedBottom","modal"]);const Qj=["BackdropProps"],Zj=["anchor","BackdropProps","children","className","elevation","hideBackdrop","ModalProps","onClose","open","PaperProps","SlideProps","TransitionComponent","transitionDuration","variant"],eb=(n,r)=>{const{ownerState:o}=n;return[r.root,(o.variant==="permanent"||o.variant==="persistent")&&r.docked,r.modal]},Ij=n=>{const{classes:r,anchor:o,variant:l}=n,s={root:["root"],docked:[(l==="permanent"||l==="persistent")&&"docked"],modal:["modal"],paper:["paper",`paperAnchor${qe(o)}`,l!=="temporary"&&`paperAnchorDocked${qe(o)}`]};return Wt(s,Kj,r)},Wj=ct(Yj,{name:"MuiDrawer",slot:"Root",overridesResolver:eb})(({theme:n})=>({zIndex:(n.vars||n).zIndex.drawer})),Mv=ct("div",{shouldForwardProp:Ld,name:"MuiDrawer",slot:"Docked",skipVariantsResolver:!1,overridesResolver:eb})({flex:"0 0 auto"}),Jj=ct(Wy,{name:"MuiDrawer",slot:"Paper",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.paper,r[`paperAnchor${qe(o.anchor)}`],o.variant!=="temporary"&&r[`paperAnchorDocked${qe(o.anchor)}`]]}})(({theme:n,ownerState:r})=>ee({overflowY:"auto",display:"flex",flexDirection:"column",height:"100%",flex:"1 0 auto",zIndex:(n.vars||n).zIndex.drawer,WebkitOverflowScrolling:"touch",position:"fixed",top:0,outline:0},r.anchor==="left"&&{left:0},r.anchor==="top"&&{top:0,left:0,right:0,height:"auto",maxHeight:"100%"},r.anchor==="right"&&{right:0},r.anchor==="bottom"&&{top:"auto",left:0,bottom:0,right:0,height:"auto",maxHeight:"100%"},r.anchor==="left"&&r.variant!=="temporary"&&{borderRight:`1px solid ${(n.vars||n).palette.divider}`},r.anchor==="top"&&r.variant!=="temporary"&&{borderBottom:`1px solid ${(n.vars||n).palette.divider}`},r.anchor==="right"&&r.variant!=="temporary"&&{borderLeft:`1px solid ${(n.vars||n).palette.divider}`},r.anchor==="bottom"&&r.variant!=="temporary"&&{borderTop:`1px solid ${(n.vars||n).palette.divider}`})),tb={left:"right",right:"left",top:"down",bottom:"up"};function e5(n){return["left","right"].indexOf(n)!==-1}function t5({direction:n},r){return n==="rtl"&&e5(r)?tb[r]:r}const n5=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiDrawer"}),s=Pd(),c=GR(),f={enter:s.transitions.duration.enteringScreen,exit:s.transitions.duration.leavingScreen},{anchor:p="left",BackdropProps:h,children:g,className:y,elevation:v=16,hideBackdrop:S=!1,ModalProps:{BackdropProps:j}={},onClose:w,open:x=!1,PaperProps:_={},SlideProps:T,TransitionComponent:C=Fj,transitionDuration:N=f,variant:M="temporary"}=l,O=$e(l.ModalProps,Qj),G=$e(l,Zj),A=R.useRef(!1);R.useEffect(()=>{A.current=!0},[]);const E=t5({direction:c?"rtl":"ltr"},p),D=ee({},l,{anchor:p,elevation:v,open:x,variant:M},G),B=Ij(D),q=m.jsx(Jj,ee({elevation:M==="temporary"?v:0,square:!0},_,{className:ke(B.paper,_.className),ownerState:D,children:g}));if(M==="permanent")return m.jsx(Mv,ee({className:ke(B.root,B.docked,y),ownerState:D,ref:o},G,{children:q}));const X=m.jsx(C,ee({in:x,direction:tb[E],timeout:N,appear:A.current},T,{children:q}));return M==="persistent"?m.jsx(Mv,ee({className:ke(B.root,B.docked,y),ownerState:D,ref:o},G,{children:X})):m.jsx(Wj,ee({BackdropProps:ee({},h,j,{transitionDuration:N}),className:ke(B.root,B.modal,y),open:x,ownerState:D,onClose:w,hideBackdrop:S,ref:o},G,O,{children:X}))}),bl=R.createContext({});function a5(n){return Vt("MuiList",n)}At("MuiList",["root","padding","dense","subheader"]);const r5=["children","className","component","dense","disablePadding","subheader"],i5=n=>{const{classes:r,disablePadding:o,dense:l,subheader:s}=n;return Wt({root:["root",!o&&"padding",l&&"dense",s&&"subheader"]},a5,r)},l5=ct("ul",{name:"MuiList",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,!o.disablePadding&&r.padding,o.dense&&r.dense,o.subheader&&r.subheader]}})(({ownerState:n})=>ee({listStyle:"none",margin:0,padding:0,position:"relative"},!n.disablePadding&&{paddingTop:8,paddingBottom:8},n.subheader&&{paddingTop:0})),o5=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiList"}),{children:s,className:c,component:f="ul",dense:p=!1,disablePadding:h=!1,subheader:g}=l,y=$e(l,r5),v=R.useMemo(()=>({dense:p}),[p]),S=ee({},l,{component:f,dense:p,disablePadding:h}),j=i5(S);return m.jsx(bl.Provider,{value:v,children:m.jsxs(l5,ee({as:f,className:ke(j.root,c),ref:o,ownerState:S},y,{children:[g,s]}))})});function s5(n){return Vt("MuiListItem",n)}const ai=At("MuiListItem",["root","container","focusVisible","dense","alignItemsFlexStart","disabled","divider","gutters","padding","button","secondaryAction","selected"]),u5=At("MuiListItemButton",["root","focusVisible","dense","alignItemsFlexStart","disabled","divider","gutters","selected"]);function c5(n){return Vt("MuiListItemSecondaryAction",n)}At("MuiListItemSecondaryAction",["root","disableGutters"]);const f5=["className"],d5=n=>{const{disableGutters:r,classes:o}=n;return Wt({root:["root",r&&"disableGutters"]},c5,o)},p5=ct("div",{name:"MuiListItemSecondaryAction",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,o.disableGutters&&r.disableGutters]}})(({ownerState:n})=>ee({position:"absolute",right:16,top:"50%",transform:"translateY(-50%)"},n.disableGutters&&{right:0})),nb=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiListItemSecondaryAction"}),{className:s}=l,c=$e(l,f5),f=R.useContext(bl),p=ee({},l,{disableGutters:f.disableGutters}),h=d5(p);return m.jsx(p5,ee({className:ke(h.root,s),ownerState:p,ref:o},c))});nb.muiName="ListItemSecondaryAction";const h5=["className"],m5=["alignItems","autoFocus","button","children","className","component","components","componentsProps","ContainerComponent","ContainerProps","dense","disabled","disableGutters","disablePadding","divider","focusVisibleClassName","secondaryAction","selected","slotProps","slots"],g5=(n,r)=>{const{ownerState:o}=n;return[r.root,o.dense&&r.dense,o.alignItems==="flex-start"&&r.alignItemsFlexStart,o.divider&&r.divider,!o.disableGutters&&r.gutters,!o.disablePadding&&r.padding,o.button&&r.button,o.hasSecondaryAction&&r.secondaryAction]},v5=n=>{const{alignItems:r,button:o,classes:l,dense:s,disabled:c,disableGutters:f,disablePadding:p,divider:h,hasSecondaryAction:g,selected:y}=n;return Wt({root:["root",s&&"dense",!f&&"gutters",!p&&"padding",h&&"divider",c&&"disabled",o&&"button",r==="flex-start"&&"alignItemsFlexStart",g&&"secondaryAction",y&&"selected"],container:["container"]},s5,l)},y5=ct("div",{name:"MuiListItem",slot:"Root",overridesResolver:g5})(({theme:n,ownerState:r})=>ee({display:"flex",justifyContent:"flex-start",alignItems:"center",position:"relative",textDecoration:"none",width:"100%",boxSizing:"border-box",textAlign:"left"},!r.disablePadding&&ee({paddingTop:8,paddingBottom:8},r.dense&&{paddingTop:4,paddingBottom:4},!r.disableGutters&&{paddingLeft:16,paddingRight:16},!!r.secondaryAction&&{paddingRight:48}),!!r.secondaryAction&&{[`& > .${u5.root}`]:{paddingRight:48}},{[`&.${ai.focusVisible}`]:{backgroundColor:(n.vars||n).palette.action.focus},[`&.${ai.selected}`]:{backgroundColor:n.vars?`rgba(${n.vars.palette.primary.mainChannel} / ${n.vars.palette.action.selectedOpacity})`:Lt.alpha(n.palette.primary.main,n.palette.action.selectedOpacity),[`&.${ai.focusVisible}`]:{backgroundColor:n.vars?`rgba(${n.vars.palette.primary.mainChannel} / calc(${n.vars.palette.action.selectedOpacity} + ${n.vars.palette.action.focusOpacity}))`:Lt.alpha(n.palette.primary.main,n.palette.action.selectedOpacity+n.palette.action.focusOpacity)}},[`&.${ai.disabled}`]:{opacity:(n.vars||n).palette.action.disabledOpacity}},r.alignItems==="flex-start"&&{alignItems:"flex-start"},r.divider&&{borderBottom:`1px solid ${(n.vars||n).palette.divider}`,backgroundClip:"padding-box"},r.button&&{transition:n.transitions.create("background-color",{duration:n.transitions.duration.shortest}),"&:hover":{textDecoration:"none",backgroundColor:(n.vars||n).palette.action.hover,"@media (hover: none)":{backgroundColor:"transparent"}},[`&.${ai.selected}:hover`]:{backgroundColor:n.vars?`rgba(${n.vars.palette.primary.mainChannel} / calc(${n.vars.palette.action.selectedOpacity} + ${n.vars.palette.action.hoverOpacity}))`:Lt.alpha(n.palette.primary.main,n.palette.action.selectedOpacity+n.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:n.vars?`rgba(${n.vars.palette.primary.mainChannel} / ${n.vars.palette.action.selectedOpacity})`:Lt.alpha(n.palette.primary.main,n.palette.action.selectedOpacity)}}},r.hasSecondaryAction&&{paddingRight:48})),b5=ct("li",{name:"MuiListItem",slot:"Container",overridesResolver:(n,r)=>r.container})({position:"relative"}),ti=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiListItem"}),{alignItems:s="center",autoFocus:c=!1,button:f=!1,children:p,className:h,component:g,components:y={},componentsProps:v={},ContainerComponent:S="li",ContainerProps:{className:j}={},dense:w=!1,disabled:x=!1,disableGutters:_=!1,disablePadding:T=!1,divider:C=!1,focusVisibleClassName:N,secondaryAction:M,selected:O=!1,slotProps:G={},slots:A={}}=l,E=$e(l.ContainerProps,h5),z=$e(l,m5),D=R.useContext(bl),B=R.useMemo(()=>({dense:w||D.dense||!1,alignItems:s,disableGutters:_}),[s,D.dense,w,_]),q=R.useRef(null);_l(()=>{c&&q.current&&q.current.focus()},[c]);const X=R.Children.toArray(p),ne=X.length&&MR(X[X.length-1],["ListItemSecondaryAction"]),oe=ee({},l,{alignItems:s,autoFocus:c,button:f,dense:B.dense,disabled:x,disableGutters:_,disablePadding:T,divider:C,hasSecondaryAction:ne,selected:O}),Z=v5(oe),le=la(q,o),re=A.root||y.Root||y5,pe=G.root||v.root||{},U=ee({className:ke(Z.root,pe.className,h),disabled:x},z);let ie=g||"li";return f&&(U.component=g||"div",U.focusVisibleClassName=ke(ai.focusVisible,N),ie=Hd),ne?(ie=!U.component&&!g?"div":ie,S==="li"&&(ie==="li"?ie="div":U.component==="li"&&(U.component="div")),m.jsx(bl.Provider,{value:B,children:m.jsxs(b5,ee({as:S,className:ke(Z.container,j),ref:le,ownerState:oe},E,{children:[m.jsx(re,ee({},pe,!dd(re)&&{as:ie,ownerState:ee({},oe,pe.ownerState)},U,{children:X})),X.pop()]}))})):m.jsx(bl.Provider,{value:B,children:m.jsxs(re,ee({},pe,{as:ie,ref:le},!dd(re)&&{ownerState:ee({},oe,pe.ownerState)},U,{children:[X,M&&m.jsx(nb,{children:M})]}))})}),kv=()=>{const{checkContext:n}=R.useContext(mn),r=()=>{const o=window.location.pathname;if(o!=="/login"){const l=window.location.origin+o;localStorage.setItem("logoutReturnPath",l)}xt.get("/logout").then(({data:l})=>{l.success?n():console.log("LOGOUT: Logout failed")}).catch(l=>{console.error("LOGOUT: Error during logout:",l)})};return m.jsx(Ba,{color:"inherit",sx:{textTransform:"none",color:"#3874CB",fontFamily:"Inter",fontWeight:450,fontSize:"14px",textDecoration:"underline"},onClick:r,disableRipple:!0,children:"Logout"})},Dv=()=>m.jsx(Ba,{color:"inherit",component:Rs,to:"/about",sx:{textTransform:"none",color:"#000000",fontFamily:"Inter",fontWeight:450,fontSize:"14px"},disableRipple:!0,children:"About"}),zv=()=>m.jsx(Ba,{color:"inherit",component:Rs,to:"/account",sx:{textTransform:"none",color:"#000000",fontFamily:"Inter",fontWeight:450,fontSize:"14px"},disableRipple:!0,children:"My Labs"}),x5=()=>{const n=pi(),r=o=>{n.pathname==="/"&&(o.preventDefault(),window.location.reload())};return m.jsxs(Ba,{component:Rs,to:"/",onClick:r,disableRipple:!0,children:[m.jsx("img",{src:"/assets/logos/paperclip.png",alt:"ylabs-logo",className:"mr-2",style:{width:"31.65px",height:"27px"}}),m.jsx("img",{src:"/assets/logos/ylabs-blue.png",alt:"ylabs-logo",style:{width:"65.17px",height:"27px"}})]})},S5=()=>m.jsx(Ba,{color:"inherit",component:Rs,to:"/",sx:{textTransform:"none",color:"#000000",fontFamily:"Inter",fontWeight:450,fontSize:"14px"},disableRipple:!0,children:"Find Labs"}),Lv=()=>{const n=()=>{window.location.reload()};return m.jsxs(Ba,{onClick:n,disableRipple:!0,children:[m.jsx("img",{src:"/assets/logos/paperclip.png",alt:"ylabs-logo",className:"mr-2",style:{width:"31.65px",height:"27px"}}),m.jsx("img",{src:"/assets/logos/ylabs-blue.png",alt:"ylabs-logo",style:{width:"65.17px",height:"27px"}})]})},w5=()=>m.jsx(Ba,{component:"a",href:"https://docs.google.com/forms/d/e/1FAIpQLSf2BE6MBulJHWXhDDp3y4Nixwe6EH0Oo9X1pTo976-KrJKv5g/viewform?usp=dialog",target:"_blank",rel:"noopener noreferrer",color:"inherit",sx:{textTransform:"none",color:"#000000",fontFamily:"Inter",fontWeight:450,fontSize:"14px"},disableRipple:!0,children:"Feedback"}),E5=zd({breakpoints:{values:{xs:0,sm:640,md:768,lg:1024,xl:1280}}}),C5="768px",R5=()=>m.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"4px",padding:"2px"},children:[m.jsx("div",{style:{width:"18px",height:"2px",backgroundColor:"black"}}),m.jsx("div",{style:{width:"18px",height:"2px",backgroundColor:"black"}}),m.jsx("div",{style:{width:"18px",height:"2px",backgroundColor:"black"}})]});function T5(){const{isAuthenticated:n}=R.useContext(mn),[r,o]=R.useState(!1);_R(`(max-width:${C5})`);const l=c=>f=>{f.type==="keydown"&&(f.key==="Tab"||f.key==="Shift")||o(c)},s=()=>{const c={"& .MuiButton-root":{paddingLeft:1,justifyContent:"flex-start",width:"100%"}};return m.jsx(Xf,{sx:{width:250},role:"presentation",onClick:l(!1),onKeyDown:l(!1),children:m.jsx(o5,{children:n?m.jsxs(m.Fragment,{children:[m.jsx(ti,{sx:c,children:m.jsx(S5,{})}),m.jsx(ti,{sx:c,children:m.jsx(zv,{})}),m.jsx(ti,{sx:c,children:m.jsx(Dv,{})}),m.jsx(ti,{sx:c,children:m.jsx(w5,{})}),m.jsx(ti,{sx:c,children:m.jsx(kv,{})})]}):m.jsx(ti,{sx:c,children:m.jsx(Lv,{})})})})};return m.jsx(VT,{theme:E5,children:m.jsx(Xf,{sx:{flexGrow:1},children:m.jsx(YT,{position:"fixed",sx:{backgroundColor:"#FFFFFF",height:{xs:"64px",sm:"64px"},"& .MuiToolbar-root":{minHeight:"64px !important",height:"64px !important",paddingLeft:{lg:"85px"},paddingRight:{lg:"85px"},transition:"padding 0.3s ease"},boxShadow:"0px 1px 5px rgba(0, 0, 0, 0.2)"},children:m.jsxs(WT,{sx:{height:"64px"},children:[n?m.jsx(x5,{}):m.jsx(Lv,{}),m.jsx(ij,{variant:"h6",component:"div",sx:{flexGrow:1}}),n&&m.jsxs(m.Fragment,{children:[m.jsxs(Xf,{sx:{display:{xs:"none",md:"flex"},gap:"14px"},children:[m.jsx(zv,{}),m.jsx(Dv,{}),m.jsx(kv,{})]}),m.jsx(fj,{size:"large",edge:"start",color:"inherit","aria-label":"menu",onClick:l(!0),sx:{marginLeft:"18px",borderRadius:"4px",padding:"8px","&:hover":{backgroundColor:"rgba(0, 0, 0, 0.04)",borderRadius:"4px"}},children:m.jsx(R5,{})}),m.jsx(n5,{anchor:"right",open:r,onClose:l(!1),children:s()})]})]})})})})}const j5=()=>m.jsxs(OS,{children:[m.jsx(T5,{}),m.jsxs(SS,{children:[m.jsx(Na,{path:"/",element:m.jsx(ul,{Component:Pg,unknownBlocked:!0})}),m.jsx(Na,{path:"/about",element:m.jsx(ul,{Component:ST,unknownBlocked:!0})}),m.jsx(Na,{path:"/account",element:m.jsx(ul,{Component:MT,unknownBlocked:!0})}),m.jsx(Na,{path:"/login",element:m.jsx(mT,{})}),m.jsx(Na,{path:"/login-error",element:m.jsx(kS,{Component:DT})}),m.jsx(Na,{path:"/unknown",element:m.jsx(ul,{Component:kT,knownBlocked:!0})}),m.jsx(Na,{path:"/*",element:m.jsx(ul,{Component:Pg,unknownBlocked:!0})})]})]}),O5=({children:n})=>{const[r,o]=R.useState(!0),[l,s]=R.useState(!1),[c,f]=R.useState(),p=R.useCallback(()=>{o(!0),xt.get("/check",{withCredentials:!0}).then(({data:h})=>{h.auth?(s(!0),f(h.user)):(s(!1),f(void 0)),o(!1)}).catch(h=>{console.error("Auth check failed:",h),s(!1),f(void 0),o(!1),Fe({text:"Something went wrong while checking authentication status.",icon:"warning"})})},[]);return R.useEffect(()=>{p()},[p]),m.jsx(mn.Provider,{value:{isLoading:r,isAuthenticated:l,user:c,checkContext:p},children:n})},ab=document.getElementById("root");if(!ab)throw new Error("Root container missing in index.html");const N5=_1.createRoot(ab);N5.render(m.jsx(zt.StrictMode,{children:m.jsx(O5,{children:m.jsx(j5,{})})})); +`,bv=({developer:n})=>n?m.jsxs("div",{children:[m.jsx("img",{src:n.image?n.image:"/assets/developers/no-user.png",alt:`${n.name} Profile Picture`,className:"aspect-square object-cover w-full rounded-lg mb-2",width:500,height:500}),m.jsx("h3",{className:"text-xl font-semibold",children:n.name}),m.jsx("p",{className:"text-gray-700",children:n.position}),m.jsx("p",{className:"text-gray-700 mb-1",children:n.location}),n.website&&m.jsx("a",{href:n.website,target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/website-icon.png",alt:`${n.name} Website`,width:20,height:20,className:"inline-block"})}),n.linkedin&&m.jsx("a",{href:n.linkedin,target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/linkedin-icon.png",alt:`${n.name} LinkedIn`,width:28,height:28,className:"inline-block"})}),n.github&&m.jsx("a",{href:n.github,target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/github-icon.png",alt:`${n.name} Website`,width:20,height:20,className:"inline-block"})})]}):null,ST=()=>m.jsxs("div",{className:"flex flex-col items-center p-8 min-h-screen mt-24",children:[m.jsxs("div",{className:"max-w-5xl text-center",children:[m.jsx("h1",{className:"text-4xl font-bold mb-7",children:"Welcome to Yale Labs! 🔬"}),m.jsxs("p",{className:"text-lg text-gray-700 mb-10 leading-relaxed",children:["A collaboration between the"," ",m.jsx("a",{href:"https://yalecomputersociety.org/",target:"_blank",rel:"noopener noreferrer",className:"text-blue-500",children:"Yale Computer Society"})," ","and the"," ",m.jsx("a",{href:"https://www.yura.yale.edu/",target:"_blank",rel:"noopener noreferrer",className:"text-blue-500",children:"Yale Undergraduate Research Association"}),", Yale Labs brings students a single, streamlined platform to browse research opportunities at Yale! With a mix of lab listings submitted by professors and scraped from the internet, our mission at Yale Labs is to make finding your next lab as stress-free as possible with all the information you need in one place."]}),m.jsx("h1",{className:"text-3xl font-bold mb-7",children:"Help us with our first release!"}),m.jsxs("p",{className:"text-lg text-gray-700 mb-10 leading-relaxed",children:["While we are working dilligently to get more up-to-date listings on the site, we are also working on changes to improve the browsing experience! As you look around the site, please let us know in the"," ",m.jsx("a",{href:"https://docs.google.com/forms/d/e/1FAIpQLSf2BE6MBulJHWXhDDp3y4Nixwe6EH0Oo9X1pTo976-KrJKv5g/viewform?usp=dialog",target:"_blank",rel:"noopener noreferrer",className:"text-blue-500",children:"feedback form"})," ","if there is anything that is broken, annoying, or that you would like to see added to the site."]}),m.jsx("a",{href:"https://yalecomputersociety.org/",target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/ycs-icon.png",alt:"y/cs Website",width:40,height:40,className:"inline-block mx-2"})}),m.jsx("a",{href:"https://www.yura.yale.edu/",target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/yura-icon.png",alt:"YURA Website",width:32,height:40,className:"inline-block mx-2"})}),m.jsx("a",{href:"https://github.com/YaleComputerSociety/ylabs",target:"_blank",rel:"noopener noreferrer",children:m.jsx("img",{src:"/assets/icons/github-icon.png",alt:"RDB Github",width:40,height:40,className:"inline-block mx-2"})})]}),m.jsxs("div",{className:"max-w-6xl text-center mt-16",children:[m.jsx("h2",{className:"text-3xl font-bold mb-10",children:"Meet our team"}),m.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-16",children:wT.map(n=>m.jsx("div",{className:"bg-gray-50 p-3 rounded-lg shadow-md",children:m.jsx(bv,{developer:n})},n.name))}),m.jsx("h2",{className:"text-3xl font-bold mb-10",children:"RDB alumni"}),m.jsx("div",{className:"grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4",children:ET.map(n=>m.jsx("div",{className:"bg-gray-50 p-3 rounded-lg shadow-md",children:m.jsx(bv,{developer:n})},n.name))})]})]}),wT=[{name:"Ryan Fernandes",position:"Development Lead",image:"/assets/developers/RyanFernandes.jpeg",location:"Natick, MA",linkedin:"https://www.linkedin.com/in/ryan-fernandes-088109284/",github:"https://github.com/Ryfernandes"},{name:"Sebastian Gonzalez",image:"/assets/developers/SebastianGonzalez.jpeg",position:"Developer",location:"Montclair, NJ",github:"https://github.com/Seb-G0",linkedin:"https://www.linkedin.com/in/sebastian-ravi-gonzalez/"},{name:"Dohun Kim",position:"Developer",image:"/assets/developers/DohunKim.jpeg",location:"Anyang-si, South Korea",github:"https://github.com/rlaehgnss",linkedin:"https://www.linkedin.com/in/dohun-kim-848028251/"},{name:"Alan Zhong",image:"/assets/developers/AlanZhong.jpeg",position:"Developer",location:"Basking Ridge, NJ",github:"https://github.com/azh248",linkedin:"https://www.linkedin.com/in/azhong248/"},{name:"Quntao Zheng",image:"/assets/developers/QuntaoZheng.jpeg",position:"Developer",location:"New York, NY",github:"https://github.com/quntao-z",linkedin:"https://www.linkedin.com/in/quntao-zheng/"},{name:"Christian Phanhthourath",position:"Developer",image:"/assets/developers/ChristianPhanhthourath.jpeg",location:"Marietta, GA",github:"https://github.com/cphanhth",linkedin:"https://linkedin.com/in/christianphanhthourath"},{name:"Christina Xu",position:"Developer",image:"/assets/developers/ChristinaXu.jpeg",location:"Lincoln, Nebraska",github:"https://github.com/shadaxiong"}],ET=[{name:"Julian Lee",position:"RDB Founder",location:"New York, NY",github:"https://github.com/JulianLee123"},{name:"Miles Yamner",position:"Developer",location:"New York, NY"},{name:"Landon Hellman",position:"Developer",location:"Santa Barbara, CA"}],qd=({error:n})=>n?m.jsx("div",{className:"text-red-500 text-xs mt-1",children:n}):null,xv=({id:n,label:r,value:o,onChange:l,placeholder:s,error:c,onValidate:f})=>m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:n,children:r}),m.jsx("input",{id:n,type:"text",value:o,onChange:p=>{l(p.target.value),f&&f(p.target.value)},placeholder:s,className:`shadow appearance-none border ${c?"border-red-500":""} rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline whitespace-nowrap overflow-x-auto`}),m.jsx(qd,{error:c})]}),CT=({id:n,label:r,value:o,onChange:l,placeholder:s,rows:c=10,error:f,onValidate:p})=>m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:"description",children:r}),m.jsx("textarea",{id:n,value:o,onChange:h=>{l(h.target.value),p&&p(h.target.value)},placeholder:s,className:"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline overflow-x-auto",rows:c}),m.jsx(qd,{error:f})]}),pl=({label:n,items:r,setItems:o,placeholder:l,bgColor:s,textColor:c,buttonColor:f,error:p,type:h="text",permanentValue:g,onValidate:y,infoText:v})=>{const S=R.useRef(null),[j,w]=R.useState(!1),x=C=>{if(C.key==="Enter"&&S.current&&S.current.value.trim()){C.preventDefault();const N=S.current.value.trim();if(!r.includes(N)&&(!g||N!==g)){const M=[...r,N];o(M),S.current.value="",y&&y(g?[...M,g]:M)}}},_=C=>{const N=[...r];N.splice(C,1),o(N),y&&y(g?[...N,g]:N)},T=()=>{const C=[];return g&&C.push(m.jsxs("span",{className:`${s} ${c} px-2 py-1 rounded text-sm flex items-center`,children:[m.jsx("span",{className:"whitespace-nowrap",children:g}),m.jsxs("div",{className:"ml-2 w-4 h-4 relative",onMouseEnter:()=>w(!0),onMouseLeave:()=>w(!1),children:[m.jsx("div",{className:"rounded-full border border-current flex items-center justify-center w-full h-full cursor-pointer",children:m.jsx("span",{className:"text-xs",children:"?"})}),j&&m.jsx("div",{className:"absolute left-6 -top-1 bg-gray-800 text-white text-xs rounded py-1 px-2 whitespace-nowrap z-10",children:"Creator"})]})]},"permanent")),r.forEach((N,M)=>{g!==N&&C.push(m.jsxs("span",{className:`${s} ${c} px-2 py-1 rounded text-sm flex items-center`,children:[m.jsx("span",{className:"whitespace-nowrap",children:N}),m.jsx("button",{type:"button",onClick:()=>_(M),className:`ml-2 ${f}`,children:"×"})]},M))}),C};return m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",children:n}),v&&m.jsx("div",{className:"text-xs text-gray-500 mb-2",children:v}),m.jsx("div",{className:"flex flex-wrap gap-2 mb-2 overflow-x-auto",children:T()}),m.jsx("div",{className:"flex",children:m.jsx("input",{type:h,ref:S,placeholder:l,className:"shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500",onKeyDown:x})}),m.jsx("div",{className:"text-xs text-gray-500 mt-1",children:"Press Enter to add"}),m.jsx(qd,{error:p})]})},RT=({departments:n,availableDepartments:r,onAddDepartment:o,onRemoveDepartment:l})=>{const[s,c]=R.useState(!1),[f,p]=R.useState(""),[h,g]=R.useState(-1),y=R.useRef(null),v=R.useRef(null),S=r.filter(x=>x.toLowerCase().includes(f.toLowerCase()));R.useEffect(()=>{const x=_=>{y.current&&!y.current.contains(_.target)&&(c(!1),p(""))};return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[]);const j=x=>{switch(x.key){case"ArrowDown":x.preventDefault(),g(_=>__>0?_-1:0);break;case"Enter":x.preventDefault(),h>=0&&h{if(Object.keys(It).includes(x))switch(It[x]){case 0:return"bg-blue-200";case 1:return"bg-green-200";case 2:return"bg-yellow-200";case 3:return"bg-red-200";case 4:return"bg-purple-200";case 5:return"bg-pink-200";case 6:return"bg-teal-200";case 7:return"bg-orange-200";default:return"bg-gray-100"}return"bg-gray-100"};return m.jsxs("div",{className:"mb-4",ref:y,children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",children:"⭐ Departments"}),m.jsxs("div",{className:"text-xs text-gray-500 mb-2",children:["Don't see your department? Let us know"," ",m.jsx("a",{href:"https://docs.google.com/forms/d/e/1FAIpQLSf2BE6MBulJHWXhDDp3y4Nixwe6EH0Oo9X1pTo976-KrJKv5g/viewform",target:"_blank",rel:"noopener noreferrer",className:"text-blue-500",children:"here"})]}),m.jsx("div",{className:"flex flex-wrap gap-2 mb-2 overflow-x-auto",children:n.map((x,_)=>m.jsxs("span",{className:`${w(x)} text-gray-900 px-2 py-1 rounded text-sm flex items-center`,children:[m.jsx("span",{className:"whitespace-nowrap",children:x}),m.jsx("button",{type:"button",onClick:()=>l(_),className:"ml-2 text-gray-500 hover:text-gray-700",children:"×"})]},_))}),m.jsxs("div",{className:"relative",children:[m.jsxs("div",{className:"relative",children:[m.jsx("input",{ref:v,type:"text",value:f,onClick:()=>c(!0),onChange:x=>{p(x.target.value),g(-1)},onKeyDown:j,onFocus:()=>c(!0),className:"shadow appearance-none border rounded w-full py-2 px-3 pr-10 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500",placeholder:"Add departments..."}),m.jsx("div",{className:"absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 cursor-pointer",onClick:()=>{s&&p(""),c(!s),!s&&v.current&&v.current.focus()},children:m.jsx("svg",{className:"fill-current h-4 w-4",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})})]}),s&&m.jsx("div",{className:"absolute w-full bg-white rounded-lg z-10 shadow-lg border overflow-hidden mt-1 max-h-[300px] md:max-h-[350px] border-gray-300",tabIndex:-1,children:m.jsx("ul",{className:"max-h-[350px] p-1 overflow-y-auto",tabIndex:-1,children:S.length>0?S.map((x,_)=>m.jsx("li",{onClick:()=>{o(x),p("")},className:`p-2 cursor-pointer ${h===_?"bg-blue-100":"hover:bg-gray-100"}`,tabIndex:-1,onMouseDown:T=>T.preventDefault(),children:x},_)):m.jsx("li",{className:"p-2 text-gray-500",tabIndex:-1,children:"No departments found"})})})]})]})},TT=({hiringStatus:n,setHiringStatus:r})=>{const[o,l]=R.useState(!1),[s,c]=R.useState(-1),f=R.useRef(null),p=R.useRef(null),h=[{value:-1,label:"Lab not seeking applicants"},{value:0,label:"Lab open to applicants"},{value:1,label:"Lab seeking applicants"}],g=v=>{r(v),l(!1),p.current&&p.current.blur()},y=v=>{switch(v.key){case"ArrowDown":v.preventDefault(),c(S=>SS>0?S-1:0);break;case"Enter":v.preventDefault(),s>=0&&s{l(!0)},onKeyDown:y,onFocus:()=>l(!0),onBlur:()=>{setTimeout(()=>{var v;(v=f.current)!=null&&v.contains(document.activeElement)||l(!1)},100)},className:"shadow appearance-none border rounded w-full py-2 px-3 pr-10 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer"}),m.jsx("div",{className:"absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 cursor-pointer",onClick:()=>{l(!o),!o&&p.current&&p.current.focus()},children:m.jsx("svg",{className:"fill-current h-4 w-4",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})})]}),o&&m.jsx("div",{className:"absolute left-0 right-0 bg-white rounded-lg z-10 shadow-lg border overflow-hidden mt-1 max-h-[350px] border-gray-300",tabIndex:-1,children:m.jsx("ul",{className:"max-h-[350px] overflow-y-auto",tabIndex:-1,children:h.map((v,S)=>m.jsxs("li",{onClick:()=>g(v.value),className:`p-2 cursor-pointer flex items-center justify-between ${s===S?"bg-blue-100":"hover:bg-gray-100"}`,tabIndex:-1,onMouseDown:j=>j.preventDefault(),children:[m.jsx("span",{children:v.label}),n===v.value&&m.jsx("svg",{className:"h-4 w-4 text-blue-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M5 13l4 4L19 7"})})]},S))})})]})]})},Sv=n=>n.trim()?void 0:"Title is required",jT=n=>n.trim()?void 0:"Description is required",wv=n=>{if(!n)return;const r=parseInt(n,10),o=new Date().getFullYear();if(isNaN(r)||!Number.isInteger(r))return"Year must be a valid integer";if(r<1701)return"Yale wasn't established until 1701!";if(r>o)return"Year cannot be in the future";if(n.trim().includes(" "))return"Year cannot include spaces";if(r.toString()!=n.trim())return"Year cannot include non-numeric characters"},Ev=n=>n.length>0?void 0:"At least one professor is required",Cv=n=>{if(n.length===0)return"At least one email is required";for(const r of n)if(!r.includes("@")||!r.includes(".")||r.includes(" "))return`Invalid email format: ${r}`},Rv=n=>{if(n.length!==0){for(const r of n)if(!r.includes(".")||r.includes(" "))return`Invalid website format: ${r}`}},Tv=n=>{if(n.length>3)return"Maximum of 3 collaborators allowed";if(new Set(n).size!==n.length)return"Please remove duplicate collaborators";for(const o of n)if(!/^[a-zA-Z0-9]+$/.test(o))return`Invalid format for collaborator netid: ${o}`},OT=({listing:n,isCreated:r,onLoad:o,onCancel:l,onSave:s,onCreate:c})=>{const[f,p]=R.useState(n.title),[h,g]=R.useState([...n.professorNames]),[y,v]=R.useState(`${n.ownerFirstName} ${n.ownerLastName}`),[S,j]=R.useState([...n.departments]),[w,x]=R.useState([]),[_,T]=R.useState([...n.professorIds]),[C,N]=R.useState([...n.emails]),[M,O]=R.useState(n.ownerEmail),[G,A]=R.useState(n.websites?[...n.websites]:[]),[E,z]=R.useState(n.description),[D,B]=R.useState(n.keywords?[...n.keywords]:[]),[q,X]=R.useState(n.established||""),[ne,oe]=R.useState(n.hiringStatus),[Z,le]=R.useState(n.archived),[re,pe]=R.useState(!0),{user:U}=R.useContext(mn),ie=U&&U.netId===n.ownerId,[Q,V]=R.useState({});R.useEffect(()=>{r?(x(H0.filter(L=>!S.includes(L)).sort()),pe(!1)):(pe(!0),xt.get(`/listings/${n.id}`,{withCredentials:!0}).then(L=>{if(!L.data.listing){console.error(`Response, but no listing ${n.id}:`,L.data),o(n,!1);return}const te=cr(L.data.listing);p(te.title),g([...te.professorNames]),v(`${te.ownerFirstName} ${te.ownerLastName}`),j([...te.departments]),N([...te.emails]),O(te.ownerEmail),A(te.websites?[...te.websites]:[]),z(te.description),B(te.keywords?[...te.keywords]:[]),X(te.established||""),oe(te.hiringStatus),le(te.archived),o(te,!0),x(H0.filter(he=>!te.departments.includes(he)).sort()),pe(!1)}).catch(L=>{console.error(`Error fetching most recent listing ${n.id}:`,L),o(n,!1)}))},[]),R.useEffect(()=>{const L={...n,title:f,professorNames:h,departments:S,emails:C,websites:G,description:E,keywords:D,established:q,hiringStatus:ne,archived:Z};o(L,!0)},[f,h,S,C,G,E,D,q,ne,Z]);const W=L=>{L.preventDefault();const te={title:Sv(f),description:jT(E),established:wv(q),professorNames:Ev([y,...h]),professorIds:Tv(_),emails:Cv([M,...C]),websites:Rv(G)},he=Object.fromEntries(Object.entries(te).filter(([ge,de])=>de!==void 0));if(V(he),Object.keys(he).length===0){const ge={...n,title:f,professorIds:_,professorNames:h,departments:S,emails:C,websites:G,description:E,keywords:D,established:q,hiringStatus:ne,archived:Z};r?Fe({title:"Create Listing",text:"Are you sure you want to create this listing?",icon:"info",buttons:["Cancel","Create"]}).then(de=>{de&&c&&c(ge)}):Fe({title:"Submit Form",text:"Are you sure you want to save these changes?",icon:"info",buttons:["Cancel","Save"]}).then(de=>{de&&s&&s(ge)})}else console.log("Validation errors:",he)},se=()=>{if(r)Fe({title:"Delete Listing",text:"Are you sure you want to delete this listing? This action cannot be undone",icon:"warning",buttons:["Cancel","Delete"],dangerMode:!0}).then(L=>{L&&l&&l()});else{const L={...n};p(L.title),g([...L.professorNames]),v(`${L.ownerFirstName} ${L.ownerLastName}`),j([...L.departments]),N([...L.emails]),O(L.ownerEmail),A(L.websites?[...L.websites]:[]),z(L.description),B(L.keywords?[...L.keywords]:[]),X(L.established||""),oe(L.hiringStatus),le(L.archived),o({...L},!0),l&&l()}},H=L=>{j(te=>[...te,L]),x(te=>te.filter(he=>he!==L).sort())},fe=L=>{const te=[...S],he=te.splice(L,1)[0];j(te),x(ge=>[...ge,he].sort())};return m.jsx("div",{className:"border border-gray-300 border-t-0 bg-white p-6 rounded-b-lg shadow-md relative",children:re?m.jsx("div",{className:"flex flex-col justify-center items-center h-full",children:m.jsx(Ms,{color:"#66CCFF",size:6})}):m.jsxs("form",{onSubmit:W,children:[m.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-6 mb-16",children:[m.jsxs("div",{className:"col-span-1",children:[m.jsx(xv,{id:"title",label:"⭐ Listing Title",value:f,onChange:p,placeholder:"Add title",error:Q.title,onValidate:L=>{Q.title&&V(te=>({...te,title:Sv(L)}))}}),m.jsx(CT,{id:"description",label:"⭐ Description",value:E,onChange:z,placeholder:"Add description",rows:10,error:Q.description}),m.jsx(xv,{id:"established",label:"Lab Established Year",value:q,onChange:X,placeholder:"e.g. 2006",error:Q.established,onValidate:L=>{Q.established&&V(te=>({...te,established:wv(L)}))}})]}),m.jsx("div",{className:"col-span-1 md:col-span-2",children:m.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-6",children:[m.jsxs("div",{children:[m.jsx(TT,{hiringStatus:ne,setHiringStatus:oe}),ie&&m.jsx(pl,{label:"Co-Editors",items:_,setItems:T,placeholder:"Add netid",bgColor:"bg-green-100",textColor:"text-green-800",buttonColor:"text-green-500 hover:text-green-700",error:Q.professorIds,onValidate:L=>V(te=>({...te,professorIds:Tv(L)})),infoText:"Allow others in your lab to update this listing"}),m.jsx(pl,{label:"Professors",items:h,setItems:g,placeholder:"Add professor",bgColor:"bg-blue-100",textColor:"text-blue-800",buttonColor:"text-blue-500 hover:text-blue-700",error:Q.professorNames,permanentValue:y,onValidate:L=>V(te=>({...te,professorNames:Ev(L)}))}),m.jsx(pl,{label:"Emails",items:C,setItems:N,placeholder:"Add email",bgColor:"bg-green-100",textColor:"text-green-800",buttonColor:"text-green-500 hover:text-green-700",error:Q.emails,permanentValue:M,type:"email",onValidate:L=>V(te=>({...te,emails:Cv(L)}))}),m.jsxs("div",{className:"mb-6 flex items-center",children:[m.jsx("input",{id:"archived",type:"checkbox",checked:Z,onChange:L=>le(L.target.checked),className:"mr-3 h-4 w-4 text-blue-500 focus:ring-blue-400 cursor-pointer"}),m.jsx("label",{className:"text-gray-700 text-sm font-bold cursor-pointer",htmlFor:"archived",children:"Archive this listing"})]})]}),m.jsxs("div",{children:[m.jsx(RT,{departments:S,availableDepartments:w,onAddDepartment:H,onRemoveDepartment:fe}),m.jsx(pl,{label:"Websites",items:G,setItems:A,placeholder:"Add website URL",bgColor:"bg-yellow-100",textColor:"text-yellow-800",buttonColor:"text-yellow-500 hover:text-yellow-700",error:Q.websites,type:"url",onValidate:L=>V(te=>({...te,websites:Rv(L)}))}),m.jsx(pl,{label:"Keywords (for search)",items:D,setItems:B,placeholder:"Add keyword",bgColor:"bg-gray-100",textColor:"text-gray-800",buttonColor:"text-gray-500 hover:text-gray-700"})]})]})})]}),m.jsxs("div",{className:"absolute bottom-6 right-6 flex space-x-3 bg-white py-2 px-1",children:[m.jsx("button",{type:"button",onClick:se,className:`${r?"bg-red-500 hover:bg-red-700 text-white":"bg-gray-300 hover:bg-gray-400 text-gray-800"} font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline`,children:r?"Delete":"Cancel"}),m.jsx("button",{type:"submit",className:`${r?"bg-green-500 hover:bg-green-700":"bg-blue-500 hover:bg-blue-700"} text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline`,children:r?"Create":"Save"})]})]})})},jv=({listing:n,favListingsIds:r,updateFavorite:o,updateListing:l,postListing:s,postListing:c,clearCreatedListing:f,deleteListing:p,openModal:h,globalEditing:g,setGlobalEditing:y,editable:v,reloadListings:S})=>{const[j,w]=R.useState([]),[x,_]=R.useState(0),[T,C]=R.useState(!1),[N,M]=R.useState(r.includes(n.id)),[O,G]=R.useState(n.archived),A=R.useRef(null),E=n.id==="create",[z,D]=R.useState(E),{user:B}=R.useContext(mn),q=B&&B.netId===n.ownerId,X=R.useRef(null),ne=["bg-blue-200","bg-green-200","bg-yellow-200","bg-red-200","bg-purple-200","bg-pink-200","bg-teal-200","bg-orange-200"],oe=()=>n.hiringStatus<0?"bg-red-500":n.hiringStatus===0?"bg-yellow-500":"bg-green-500",Z=()=>n.hiringStatus<0?"Lab not seeking applicants":n.hiringStatus===0?"Lab open to applicants":"Lab seeking applicants";R.useEffect(()=>{r&&M(r.includes(n.id))},[r]),R.useEffect(()=>{G(n.archived)},[n]),R.useEffect(()=>{if(!A.current)return;const V=()=>{const W=A.current;if(!W)return;const se=W.clientWidth;let H=0;const fe=[];_(0);const L=document.createElement("span");L.className="bg-blue-200 text-gray-900 text-xs rounded px-1 py-0.5 mt-2 mr-2",L.style.visibility="hidden",L.style.position="absolute",document.body.appendChild(L);for(let te=0;tese&&(fe.pop(),_(n.departments.length-fe.length))}document.body.removeChild(L),w(fe)};return V(),window.addEventListener("resize",V),()=>window.removeEventListener("resize",V)},[n]);const le=V=>{V.stopPropagation(),n.favorites=N?n.favorites-1:n.favorites+1,n.favorites<0&&(n.favorites=0),o(n,n.id,!N)},re=V=>{V.stopPropagation(),Fe({title:"Delete Listing",text:"Are you sure you want to delete this listing? This action cannot be undone",icon:"warning",buttons:["Cancel","Delete"],dangerMode:!0}).then(W=>{W&&p(n)})},pe=V=>{V.stopPropagation(),O?(G(!1),xt.put(`/listings/${n.id}/unarchive`,{withCredentials:!0}).then(W=>{const se=W.data.listing,H=cr(se);l(H)}).catch(W=>{G(!0),console.error("Error unarchiving listing:",W),W.response.data.incorrectPermissions?(Fe({text:"You no longer have permission to unarchive this listing",icon:"warning"}),S()):(Fe({text:"Unable to unarchive listing",icon:"warning"}),S())})):(G(!0),xt.put(`/listings/${n.id}/archive`,{withCredentials:!0}).then(W=>{const se=W.data.listing,H=cr(se);l(H)}).catch(W=>{G(!1),console.error("Error archiving listing:",W),W.response.data.incorrectPermissions?(Fe({text:"You no longer have permission to archive this listing",icon:"warning"}),S()):(Fe({text:"Unable to archive listing",icon:"warning"}),S())}))},U=V=>{V.stopPropagation(),X.current=n,D(!0),y(!0)},ie=()=>{h(n)},Q=V=>V?V.startsWith("http://")||V.startsWith("https://")?V:`https://${V}`:"";return n?m.jsxs("div",{className:"mb-4 relative",children:[m.jsxs("div",{className:"flex relative z-10 rounded-md shadow",children:[m.jsx("div",{className:`${oe()} cursor-pointer rounded-l flex-shrink-0 relative ${O?"opacity-50":""}`,style:{width:"6px"},onMouseEnter:()=>C(!0),onMouseLeave:()=>C(!1),children:T&&m.jsx("div",{className:`${oe()} absolute top-1/2 left-4 -translate-y-1/2 text-white text-xs rounded-full py-1 px-2 z-10 whitespace-nowrap shadow`,children:Z()})}),m.jsxs("div",{className:"p-4 flex-grow grid grid-cols-3 md:grid-cols-12 cursor-pointer bg-white hover:bg-gray-100 border border-gray-300 rounded-r",onClick:ie,children:[m.jsxs("div",{className:"col-span-2 md:col-span-4",children:[m.jsx("p",{className:`text-lg font-semibold mb-3 ${O?"opacity-50":""}`,style:{lineHeight:"1.2rem",height:"1.2rem",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:n.title}),m.jsxs("p",{className:`text-sm text-gray-700 ${O?"opacity-50":""}`,style:{overflow:"hidden",display:"-webkit-box",WebkitLineClamp:1,WebkitBoxOrient:"vertical"},children:["Professors: ",[`${n.ownerFirstName} ${n.ownerLastName}`,...n.professorNames].join(", ")]}),m.jsx("div",{ref:A,className:"flex overflow-hidden",style:{whiteSpace:"nowrap"},children:j.length>0?m.jsxs(m.Fragment,{children:[j.map(V=>m.jsx("span",{className:`${Object.keys(It).includes(V)?ne[It[V]]:"bg-gray-200"} text-gray-900 text-xs rounded px-1 py-0.5 mt-3 mr-2 ${O?"opacity-50":""}`,style:{display:"inline-block",whiteSpace:"nowrap"},children:V},V)),x>0&&m.jsxs("span",{className:`bg-gray-200 text-gray-900 text-xs rounded px-1 py-0.5 mt-3 ${O?"opacity-50":""}`,style:{display:"inline-block",whiteSpace:"nowrap"},children:["+",x," more"]})]}):m.jsx("div",{className:"mt-3 flex",children:m.jsx("span",{className:`invisible bg-gray-200 text-gray-900 text-xs rounded px-1 py-0.5 mr-2 ${O?"opacity-50":""}`,style:{display:"inline-block"},children:"placeholder"})})})]}),m.jsxs("div",{className:"col-span-6 hidden md:flex align-middle",children:[m.jsx("div",{className:`flex-shrink-0 border-l border-gray-300 mx-4 ${O?"opacity-50":""}`}),m.jsx("p",{className:`flex-grow text-gray-800 text-sm overflow-hidden overflow-ellipsis ${O?"opacity-50":""}`,style:{display:"-webkit-box",WebkitLineClamp:4,WebkitBoxOrient:"vertical"},children:n.description})]}),m.jsxs("div",{className:"flex flex-col col-span-1 md:col-span-2 items-end",children:[m.jsxs("div",{children:[n.websites&&n.websites.length>0&&m.jsx("a",{href:Q(n.websites[0]),className:"mr-1",onClick:V=>V.stopPropagation(),target:"_blank",rel:"noopener noreferrer",children:m.jsx("button",{className:"p-1 rounded-full hover:bg-gray-200",children:m.jsx("img",{src:"/assets/icons/new-link.png",alt:"Lab Website",className:`w-5 h-5 ${O?"opacity-50":""}`})})}),!E&&m.jsx("a",{onClick:le,className:"inline-block",children:m.jsx("button",{className:"p-1 hover:bg-gray-200 rounded-full","aria-label":N?"Remove from favorites":"Add to favorites",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",className:`transition-colors ${O?"opacity-50":""}`,fill:N?"#FFDA7B":"none",stroke:N?"#F0C04A":"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:m.jsx("path",{d:"M12 17.75l-6.172 3.245l1.179-6.873l-5-4.867l6.9-1l3.086-6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z"})})})})]}),m.jsx("div",{className:"flex-grow"}),m.jsx("p",{className:"text-[8px] mb-0.5 text-gray-700",style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:"Last Update"}),m.jsx("p",{className:`text-sm text-gray-700 ${O?"opacity-50":""}`,style:{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},children:new Date(n.updatedAt).toLocaleDateString()})]})]})]},n.id),m.jsx("div",{className:`transform transition-all duration-700 overflow-hidden ${v&&z?"translate-y-0 max-h-[4000px]":"-translate-y-5 max-h-0"} pl-2 pr-0.5 -mt-1`,children:v&&z&&m.jsx(OT,{listing:n,isCreated:E,onLoad:(V,W)=>{if(!W){D(!1),Fe({text:"Unable to fetch most recent listing",icon:"warning"}),S();return}l(V)},onCancel:()=>{E?(D(!1),f()):(X.current&&l({...X.current}),D(!1),y(!1))},onSave:V=>{s(V),D(!1),y(!1)},onCreate:V=>{c(V),D(!1),y(!1)}})}),v&&!z&&m.jsx("div",{className:"flex justify-center",children:m.jsxs("div",{className:"bg-white border border-gray-300 border-t-0 rounded-b-lg shadow px-3 pb-1 pt-3 -mt-1 inline-flex space-x-2",children:[m.jsx("button",{className:"p-1 rounded-full hover:bg-gray-100 text-gray-600 hover:text-green-600 transition-colors",onClick:pe,title:O?"Unarchive listing":"Archive listing","aria-label":O?"Unarchive listing":"Archive listing",children:O?m.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"opacity-50",children:[m.jsx("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),m.jsx("path",{d:"M10.585 10.587a2 2 0 0 0 2.829 2.828"}),m.jsx("path",{d:"M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87"}),m.jsx("path",{d:"M3 3l18 18"})]}):m.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[m.jsx("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),m.jsx("path",{d:"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"}),m.jsx("path",{d:"M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6"})]})}),m.jsx("button",{className:`p-1 rounded-full ${g?"text-gray-400 cursor-not-allowed":"hover:bg-gray-100 text-gray-600 hover:text-blue-600 transition-colors"}`,onClick:V=>{V.stopPropagation(),g||U(V)},title:g?"Must close current editor":"Edit listing","aria-label":g?"Editing disabled":"Edit listing",disabled:g,children:m.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`${O||g?"opacity-50":""}`,children:[m.jsx("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),m.jsx("path",{d:"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4"}),m.jsx("path",{d:"M13.5 6.5l4 4"})]})}),m.jsx("button",{className:`p-1 rounded-full ${q&&!g?"hover:bg-gray-100 text-gray-600 hover:text-red-600 transition-colors":"text-gray-400 cursor-not-allowed"}`,onClick:V=>{V.stopPropagation(),q&&!g&&re(V)},title:q?g?"Must close current editor":"Delete listing":"Only owner can delete","aria-label":q?g?"Must close current editor":"Delete listing":"Only owner can delete",disabled:!q,children:m.jsxs("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:`${O||g?"opacity-50":""}`,children:[m.jsx("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),m.jsx("path",{d:"M4 7l16 0"}),m.jsx("path",{d:"M10 11l0 6"}),m.jsx("path",{d:"M14 11l0 6"}),m.jsx("path",{d:"M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"}),m.jsx("path",{d:"M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"})]})})]})})]}):null},NT=({isOpen:n,onClose:r,listing:o,favListingsIds:l,updateFavorite:s})=>{const[c,f]=R.useState(o.id==="create"),[p,h]=R.useState(l.includes(o.id)),[g,y]=R.useState(!0),{user:v}=R.useContext(mn),S=["bg-blue-200","bg-green-200","bg-yellow-200","bg-red-200","bg-purple-200","bg-pink-200","bg-teal-200","bg-orange-200"],j=()=>o.hiringStatus<0?"bg-red-500":o.hiringStatus===0?"bg-yellow-500":"bg-green-500",w=()=>o.hiringStatus<0?"Lab not seeking applicants":o.hiringStatus===0?"Lab open to applicants":"Lab seeking applicants",x=C=>{C.target===C.currentTarget&&r()};R.useEffect(()=>{l&&h(l.includes(o.id))},[l]),R.useEffect(()=>{v&&v.userConfirmed&&["admin","professor","faculty"].includes(v.userType)&&y(!1)},[]),R.useEffect(()=>(n&&(document.body.style.overflow="hidden"),()=>{document.body.style.overflow="auto"}),[n]);const _=C=>{C.stopPropagation(),o.favorites=p?o.favorites-1:o.favorites+1,o.favorites<0&&(o.favorites=0),s(o,o.id,!p)},T=C=>C?C.startsWith("http://")||C.startsWith("https://")?C:`https://${C}`:"";return!n||!o?null:m.jsx("div",{className:"fixed inset-0 bg-black/65 z-50 flex items-center justify-center overflow-y-auto p-4 pt-24",onClick:x,children:m.jsxs("div",{className:"bg-white rounded-lg shadow-xl w-full max-w-4xl max-h-[80vh] overflow-y-auto",onClick:C=>C.stopPropagation(),children:[m.jsx("div",{className:`${j()} h-2 w-full rounded-t-lg`}),m.jsxs("div",{className:"p-6 relative",children:[m.jsxs("div",{className:"absolute top-4 right-4",children:[!c&&m.jsx("a",{onClick:_,className:"inline-block",children:m.jsx("button",{className:"p-1 hover:bg-gray-100 rounded-full mr-2","aria-label":p?"Remove from favorites":"Add to favorites",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",className:"transition-colors h-6 w-6",fill:p?"#FFDA7B":"none",stroke:p?"#F0C04A":"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:m.jsx("path",{d:"M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z"})})})}),m.jsx("button",{onClick:r,className:"p-1 rounded-full hover:bg-gray-100","aria-label":"Close",children:m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-6 w-6",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),m.jsx("div",{className:"mb-6 pr-20",children:m.jsxs("div",{className:"flex flex-col md:flex-row md:items-center gap-2",children:[m.jsx("h2",{className:"text-2xl font-bold md:max-w-[400px] lg:max-w-[600px]",children:o.title}),m.jsx("span",{className:`${j()} mt-2 md:mt-0 md:ml-2 text-white text-xs px-2 py-1 rounded-full inline-block w-fit`,children:w()})]})}),m.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-6",children:[m.jsxs("div",{className:"col-span-1",children:[m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Professors"}),m.jsx("div",{className:"space-y-2",children:[`${o.ownerFirstName} ${o.ownerLastName}`,...o.professorNames].map((C,N)=>m.jsxs("div",{className:"flex items-center",children:[m.jsx("div",{className:"w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center mr-2",children:C.charAt(0).toUpperCase()}),m.jsx("span",{children:C})]},N))})]}),m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Departments"}),m.jsx("div",{className:"flex flex-wrap gap-2",children:o.departments.map(C=>m.jsx("span",{className:`${Object.keys(It).includes(C)?S[It[C]]:"bg-gray-200"} text-gray-900 text-xs rounded px-2 py-1`,children:C},C))})]}),m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Contact Information"}),m.jsxs("div",{className:"mb-4",children:[m.jsx("h4",{className:"text-md font-medium",children:"Emails"}),m.jsx("ul",{className:"mt-1 space-y-1",children:[o.ownerEmail,...o.emails].map((C,N)=>m.jsx("li",{children:m.jsx("a",{href:`mailto:${C}`,className:"text-blue-600 hover:underline",children:C})},N))})]}),o.websites&&o.websites.length>0&&m.jsxs("div",{children:[m.jsx("h4",{className:"text-md font-medium",children:"Websites"}),m.jsx("ul",{className:"mt-1 space-y-1",children:o.websites.map((C,N)=>m.jsx("li",{className:"truncate",children:m.jsx("a",{href:T(C),target:"_blank",rel:"noopener noreferrer",className:"text-blue-600 hover:underline",children:C})},N))})]})]}),m.jsxs("section",{children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"Stats"}),m.jsxs("div",{className:"space-y-2 text-sm",children:[!g&&m.jsxs(m.Fragment,{children:[m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Views:"}),m.jsx("span",{className:"font-medium",children:o.views})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Favorites:"}),m.jsx("span",{className:"font-medium",children:o.favorites})]})]}),o.established&&m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Lab Established:"}),m.jsx("span",{className:"font-medium",children:o.established})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Listing Created:"}),m.jsx("span",{className:"font-medium",children:new Date(o.createdAt).toLocaleDateString()})]}),m.jsxs("div",{className:"flex justify-between",children:[m.jsx("span",{children:"Listing Updated:"}),m.jsx("span",{className:"font-medium",children:new Date(o.updatedAt).toLocaleDateString()})]})]})]})]}),m.jsxs("div",{className:"col-span-1 md:col-span-2",children:[m.jsxs("section",{className:"mb-6",children:[m.jsx("h3",{className:"text-lg font-semibold mb-2",children:"About"}),m.jsx("div",{className:"whitespace-pre-wrap",children:o.description})]}),o.archived&&m.jsxs("div",{className:"mt-6 p-3 bg-red-100 text-red-700 rounded-lg",children:[m.jsx("div",{className:"font-semibold",children:"This listing is archived"}),m.jsx("div",{className:"text-sm",children:"Archived listings are not visible in search results or as favorites."})]})]})]})]})]})})},_T=({globalEditing:n,handleCreate:r})=>m.jsx("button",{className:`py-1 px-2 rounded-md ${n?"text-gray-400 cursor-not-allowed":"hover:bg-gray-100 text-green-500 hover:text-green-700 transition-colors"}`,onClick:o=>{o.stopPropagation(),n||r()},title:n?"Must close current editor":"Create listing","aria-label":n?"Create listing disabled":"Edit listing",disabled:n,children:m.jsxs("div",{className:"flex items-center justify-center",children:[m.jsx("span",{className:"mr-1 text-md font-semibold",children:"Create Listing"}),m.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-5 w-5",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"})})]})});function AT(){return m.jsx("div",{children:m.jsx("iframe",{className:"w-[200px] h-[112.5px] sm:w-[400px] sm:h-[225px] lg:w-[800px] lg:h-[450px] transition-all",src:"https://www.youtube.com/embed/Crf3Tyjsk2k?si=eXHPqMv_Fwi04FT4",title:"YouTube video player",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",referrerPolicy:"strict-origin-when-cross-origin",allowFullScreen:!0})})}const MT=()=>{const[n,r]=R.useState([]),[o,l]=R.useState([]),[s,c]=R.useState([]),[f,p]=R.useState(!1),[h,g]=R.useState(!1),[y,v]=R.useState(!1),[S,j]=R.useState(!1),[w,x]=R.useState(null),{user:_}=R.useContext(mn);R.useEffect(()=>{T()},[]),R.useEffect(()=>{const q=X=>{if(y){const ne="You have unsaved changes that will be lost if you leave this page.";return X.preventDefault(),X.returnValue=ne,ne}};return y&&window.addEventListener("beforeunload",q),()=>{window.removeEventListener("beforeunload",q)}},[y]);const T=async()=>{p(!0),await xt.get("/users/listings",{withCredentials:!0}).then(q=>{const X=q.data.ownListings.map(function(oe){return cr(oe)}),ne=q.data.favListings.map(function(oe){return cr(oe)});r(X),l(ne)}).catch(q=>{console.error("Error fetching listings:",q),r([]),l([]),p(!1),Fe({text:"Error fetching your listings",icon:"warning"})}),xt.get("/users/favListingsIds",{withCredentials:!0}).then(q=>{c(q.data.favListingsIds),p(!1)}).catch(q=>{console.error("Error fetching user's favorite listings:",q),r([]),l([]),c([]),p(!1),Fe({text:"Error fetching your listings",icon:"warning"})})},C=q=>{x(q),g(!0)},N=()=>{g(!1),x(null)},M=(q,X,ne)=>{const oe=o,Z=s;ne?(l([q,...oe]),c([X,...Z]),xt.put("/users/favListings",{withCredentials:!0,data:{favListings:[q.id]}}).catch(le=>{l(oe),c(Z),console.error("Error favoriting listing:",le),Fe({text:"Unable to favorite listing",icon:"warning"}),T()})):(l(oe.filter(le=>le.id!==X)),c(Z.filter(le=>le!==X)),xt.delete("/users/favListings",{withCredentials:!0,data:{favListings:[X]}}).catch(le=>{l(oe),c(Z),console.error("Error unfavoriting listing:",le),Fe({text:"Unable to unfavorite listing",icon:"warning"}),T()}))},O=q=>{r(X=>X.map(ne=>ne.id===q.id?q:ne)),l(X=>X.map(ne=>ne.id===q.id?q:ne))},G=q=>q.filter(X=>X.confirmed&&!X.archived),A=async q=>{p(!0),xt.put(`/listings/${q.id}`,{withCredentials:!0,data:q}).then(X=>{T()}).catch(X=>{console.error("Error saving listing:",X),X.response.data.incorrectPermissions?(Fe({text:"You no longer have permission to edit this listing",icon:"warning"}),T()):(Fe({text:"Unable to update listing",icon:"warning"}),T())})},E=q=>{p(!0),xt.post("/listings",{withCredentials:!0,data:q}).then(X=>{T(),v(!1),p(!1),j(!1)}).catch(X=>{console.error("Error posting new listing:",X),Fe({text:"Unable to create listing",icon:"warning"}),T(),v(!1),p(!1),j(!1)})},z=()=>{r(q=>q.filter(X=>X.id!=="create")),v(!1),j(!1)},D=q=>{p(!0),xt.delete(`/listings/${q.id}`,{withCredentials:!0}).then(X=>{T(),p(!1)}).catch(X=>{console.error("Error deleting listing:",X),Fe({text:"Unable to delete listing",icon:"warning"}),T(),p(!1)})},B=()=>{xt.get("/listings/skeleton",{withCredentials:!0}).then(q=>{const X=cr(q.data.listing);r(ne=>[...ne,X]),v(!0),j(!0)}).catch(q=>{console.error("Error fetching skeleton listing:",q),Fe({text:"Unable to create listing",icon:"warning"})})};return m.jsx("div",{className:"mx-auto max-w-[1300px] px-6 mt-24 w-full",children:f?m.jsx("div",{style:{marginTop:"17%",textAlign:"center"},children:m.jsx(Ms,{color:"#66CCFF",size:10})}):m.jsxs("div",{children:[_&&!_.userConfirmed&&m.jsx("div",{className:"bg-amber-100 border-l-4 border-amber-500 text-amber-700 p-4 mb-6 rounded shadow-sm",children:m.jsx("div",{className:"flex items-center",children:m.jsx("p",{className:"font-medium",children:"Your account is pending confirmation. Any listings that you create will not be publicly visible as favorites or in search results until your account is confirmed."})})}),m.jsx("p",{className:"text-xl text-gray-700 mb-4",children:"Your listings"}),n.length>0&&m.jsx("ul",{children:n.map(q=>m.jsx("li",{className:"mb-2",children:m.jsx(jv,{listing:q,favListingsIds:s,updateFavorite:M,updateListing:O,postListing:A,postListing:E,clearCreatedListing:z,deleteListing:D,openModal:C,globalEditing:y,setGlobalEditing:v,editable:!0,reloadListings:T})},q.id))}),_&&(_.userType==="professor"||_.userType==="faculty"||_.userType==="admin")&&!S&&m.jsx("div",{className:"my-8 flex justify-center align-center",children:m.jsx(_T,{globalEditing:y,handleCreate:B})}),m.jsx("p",{className:"text-xl text-gray-700 mb-4",children:"Favorite listings"}),G(o).length>0?m.jsx("ul",{children:G(o).map(q=>m.jsx("li",{className:"mb-2",children:m.jsx(jv,{listing:q,favListingsIds:s,updateFavorite:M,updateListing:O,postListing:A,postListing:E,clearCreatedListing:z,deleteListing:D,openModal:C,globalEditing:y,setGlobalEditing:v,editable:!1,reloadListings:T})},q.id))}):m.jsx("p",{className:"my-4 flex align-center",children:"No listings found."}),_&&(_.userType==="professor"||_.userType==="faculty"||_.userType==="admin")&&m.jsxs(m.Fragment,{children:[m.jsx("h1",{className:"text-4xl mt-24 font-bold text-center mb-7",children:"Learn y/labs!"}),m.jsx("div",{className:"mt-4 flex align-center justify-center mb-4",children:m.jsx(AT,{})})]}),w&&m.jsx(NT,{isOpen:h,onClose:N,listing:w,favListingsIds:s,updateFavorite:M})]})})},kT=()=>{var z;const[n,r]=R.useState(""),[o,l]=R.useState(""),[s,c]=R.useState(""),[f,p]=R.useState(""),[h,g]=R.useState(!1),[y,v]=R.useState(-1),S=R.useRef(null),j=R.useRef(null),[w,x]=R.useState({}),_=[{value:"undergraduate",label:"Undergraduate Student"},{value:"graduate",label:"Graduate Student"},{value:"professor",label:"Professor"},{value:"faculty",label:"Faculty"}],T=D=>D.trim()?void 0:"First name is required",C=D=>D.trim()?void 0:"Last name is required",N=D=>{if(!D.trim())return"Email is required";if(!D.includes("@")||!D.includes(".")||D.includes(" "))return"Invalid email format"},M=D=>D.trim()?void 0:"User type is required",O=D=>{p(D),g(!1),j.current&&j.current.blur(),x(B=>({...B,userType:M(D)}))},G=D=>{switch(D.key){case"ArrowDown":D.preventDefault(),v(B=>B<_.length-1?B+1:B);break;case"ArrowUp":D.preventDefault(),v(B=>B>0?B-1:0);break;case"Enter":D.preventDefault(),y>=0&&y<_.length&&O(_[y].value);break;case"Escape":D.preventDefault(),g(!1),j.current&&j.current.blur();break;case"Tab":g(!1);break}},A=D=>{D.preventDefault();const B={firstName:T(n),lastName:C(o),email:N(s),userType:M(f)},q=Object.fromEntries(Object.entries(B).filter(([X,ne])=>ne!==void 0));x(q),Object.keys(q).length===0&&(console.log("Submitting user information:",{firstName:n,lastName:o,email:s,userType:f}),xt.put("/users",{withCredentials:!0,data:{fname:n,lname:o,email:s,userType:f,userConfirmed:!1}}).then(X=>{Fe("Success!","Your information has been updated! You can now access the site. We will verify your information shortly.","success").then(()=>{window.location.href="/"})}).catch(X=>{console.error("Failed to update user information:",X),Fe("Error!","An error occurred while updating your information. Please try again.","error")}))},E=({error:D})=>D?m.jsx("p",{className:"text-red-500 text-xs italic mt-1",children:D}):null;return m.jsx("div",{className:"fixed inset-0 flex items-center justify-center bg-gray-50 p-4",children:m.jsxs("div",{className:"w-full max-w-md bg-white rounded-lg shadow-lg p-6",children:[m.jsx("div",{className:"mb-6",children:m.jsx("h2",{className:"text-xl font-bold text-gray-800 mb-2",children:"Welcome to y/labs!"})}),m.jsxs("form",{onSubmit:A,children:[m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:"firstName",children:"First Name"}),m.jsx("input",{id:"firstName",type:"text",value:n,onChange:D=>{r(D.target.value),w.firstName&&x(B=>({...B,firstName:T(D.target.value)}))},className:`shadow appearance-none border ${w.firstName?"border-red-500":""} rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500`}),m.jsx(E,{error:w.firstName})]}),m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:"lastName",children:"Last Name"}),m.jsx("input",{id:"lastName",type:"text",value:o,onChange:D=>{l(D.target.value),w.lastName&&x(B=>({...B,lastName:C(D.target.value)}))},className:`shadow appearance-none border ${w.lastName?"border-red-500":""} rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500`}),m.jsx(E,{error:w.lastName})]}),m.jsxs("div",{className:"mb-4",children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",htmlFor:"email",children:"Email"}),m.jsx("input",{id:"email",type:"text",value:s,onChange:D=>{c(D.target.value),w.email&&x(B=>({...B,email:N(D.target.value)}))},className:`shadow appearance-none border ${w.email?"border-red-500":""} rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500`}),m.jsx(E,{error:w.email})]}),m.jsxs("div",{className:"mb-6",ref:S,children:[m.jsx("label",{className:"block text-gray-700 text-sm font-bold mb-2",children:"User Type"}),m.jsxs("div",{className:"relative",children:[m.jsxs("div",{className:"relative",children:[m.jsx("input",{ref:j,id:"userType",type:"text",readOnly:!0,value:f&&((z=_.find(D=>D.value===f))==null?void 0:z.label)||"",onClick:()=>{g(!0)},onKeyDown:G,onFocus:()=>g(!0),onBlur:()=>{setTimeout(()=>{var D;(D=S.current)!=null&&D.contains(document.activeElement)||g(!1)},100)},className:`shadow appearance-none border ${w.userType?"border-red-500":""} rounded w-full py-2 px-3 pr-10 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer`}),m.jsx("div",{className:"absolute inset-y-0 right-0 flex items-center px-2 text-gray-700 cursor-pointer",onClick:()=>{g(!h),!h&&j.current&&j.current.focus()},children:m.jsx("svg",{className:"fill-current h-4 w-4",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 20 20",children:m.jsx("path",{d:"M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"})})})]}),h&&m.jsx("div",{className:"absolute left-0 right-0 bg-white rounded-lg z-10 shadow-lg border overflow-hidden mt-1 max-h-[200px] border-gray-300",tabIndex:-1,children:m.jsx("ul",{className:"max-h-[200px] overflow-y-auto",tabIndex:-1,children:_.map((D,B)=>m.jsxs("li",{onClick:()=>O(D.value),className:`p-2 cursor-pointer flex items-center justify-between ${y===B?"bg-blue-100":"hover:bg-gray-100"}`,tabIndex:-1,onMouseDown:q=>q.preventDefault(),children:[m.jsx("span",{children:D.label}),f===D.value&&m.jsx("svg",{className:"h-4 w-4 text-blue-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:m.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M5 13l4 4L19 7"})})]},B))})})]}),m.jsx(E,{error:w.userType})]}),m.jsx("div",{className:"flex justify-end",children:m.jsx("button",{type:"submit",className:"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded focus:outline-none focus:shadow-outline",children:"Continue"})})]})]})})},DT=()=>{const[n,r]=R.useState(!1);return R.useEffect(()=>{setTimeout(()=>{r(!0)},500)}),R.useEffect(()=>{n&&Fe({text:"We were unable to process your login. Please try again or contact support if the issue persists.",icon:"warning"}).then(()=>{window.location.href="/login"})},[n]),null},Ov=n=>{let r;return n<1?r=5.11916*n**2:r=4.5*Math.log(n+1)+2,(r/100).toFixed(2)};function Pd(){const n=Vy(Py);return n[Nl]||n}function zT(n){return Vt("MuiPaper",n)}At("MuiPaper",["root","rounded","outlined","elevation","elevation0","elevation1","elevation2","elevation3","elevation4","elevation5","elevation6","elevation7","elevation8","elevation9","elevation10","elevation11","elevation12","elevation13","elevation14","elevation15","elevation16","elevation17","elevation18","elevation19","elevation20","elevation21","elevation22","elevation23","elevation24"]);const LT=["className","component","elevation","square","variant"],BT=n=>{const{square:r,elevation:o,variant:l,classes:s}=n,c={root:["root",l,!r&&"rounded",l==="elevation"&&`elevation${o}`]};return Wt(c,zT,s)},UT=ct("div",{name:"MuiPaper",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,r[o.variant],!o.square&&r.rounded,o.variant==="elevation"&&r[`elevation${o.elevation}`]]}})(({theme:n,ownerState:r})=>{var o;return ee({backgroundColor:(n.vars||n).palette.background.paper,color:(n.vars||n).palette.text.primary,transition:n.transitions.create("box-shadow")},!r.square&&{borderRadius:n.shape.borderRadius},r.variant==="outlined"&&{border:`1px solid ${(n.vars||n).palette.divider}`},r.variant==="elevation"&&ee({boxShadow:(n.vars||n).shadows[r.elevation]},!n.vars&&n.palette.mode==="dark"&&{backgroundImage:`linear-gradient(${Lt.alpha("#fff",Ov(r.elevation))}, ${Lt.alpha("#fff",Ov(r.elevation))})`},n.vars&&{backgroundImage:(o=n.vars.overlays)==null?void 0:o[r.elevation]}))}),Wy=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiPaper"}),{className:s,component:c="div",elevation:f=1,square:p=!1,variant:h="elevation"}=l,g=$e(l,LT),y=ee({},l,{component:c,elevation:f,square:p,variant:h}),v=BT(y);return m.jsx(UT,ee({as:c,ownerState:y,className:ke(v.root,s),ref:o},g))});function $T(n){return Vt("MuiAppBar",n)}At("MuiAppBar",["root","positionFixed","positionAbsolute","positionSticky","positionStatic","positionRelative","colorDefault","colorPrimary","colorSecondary","colorInherit","colorTransparent","colorError","colorInfo","colorSuccess","colorWarning"]);const HT=["className","color","enableColorOnDark","position"],qT=n=>{const{color:r,position:o,classes:l}=n,s={root:["root",`color${qe(r)}`,`position${qe(o)}`]};return Wt(s,$T,l)},ss=(n,r)=>n?`${n==null?void 0:n.replace(")","")}, ${r})`:r,PT=ct(Wy,{name:"MuiAppBar",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,r[`position${qe(o.position)}`],r[`color${qe(o.color)}`]]}})(({theme:n,ownerState:r})=>{const o=n.palette.mode==="light"?n.palette.grey[100]:n.palette.grey[900];return ee({display:"flex",flexDirection:"column",width:"100%",boxSizing:"border-box",flexShrink:0},r.position==="fixed"&&{position:"fixed",zIndex:(n.vars||n).zIndex.appBar,top:0,left:"auto",right:0,"@media print":{position:"absolute"}},r.position==="absolute"&&{position:"absolute",zIndex:(n.vars||n).zIndex.appBar,top:0,left:"auto",right:0},r.position==="sticky"&&{position:"sticky",zIndex:(n.vars||n).zIndex.appBar,top:0,left:"auto",right:0},r.position==="static"&&{position:"static"},r.position==="relative"&&{position:"relative"},!n.vars&&ee({},r.color==="default"&&{backgroundColor:o,color:n.palette.getContrastText(o)},r.color&&r.color!=="default"&&r.color!=="inherit"&&r.color!=="transparent"&&{backgroundColor:n.palette[r.color].main,color:n.palette[r.color].contrastText},r.color==="inherit"&&{color:"inherit"},n.palette.mode==="dark"&&!r.enableColorOnDark&&{backgroundColor:null,color:null},r.color==="transparent"&&ee({backgroundColor:"transparent",color:"inherit"},n.palette.mode==="dark"&&{backgroundImage:"none"})),n.vars&&ee({},r.color==="default"&&{"--AppBar-background":r.enableColorOnDark?n.vars.palette.AppBar.defaultBg:ss(n.vars.palette.AppBar.darkBg,n.vars.palette.AppBar.defaultBg),"--AppBar-color":r.enableColorOnDark?n.vars.palette.text.primary:ss(n.vars.palette.AppBar.darkColor,n.vars.palette.text.primary)},r.color&&!r.color.match(/^(default|inherit|transparent)$/)&&{"--AppBar-background":r.enableColorOnDark?n.vars.palette[r.color].main:ss(n.vars.palette.AppBar.darkBg,n.vars.palette[r.color].main),"--AppBar-color":r.enableColorOnDark?n.vars.palette[r.color].contrastText:ss(n.vars.palette.AppBar.darkColor,n.vars.palette[r.color].contrastText)},!["inherit","transparent"].includes(r.color)&&{backgroundColor:"var(--AppBar-background)"},{color:r.color==="inherit"?"inherit":"var(--AppBar-color)"},r.color==="transparent"&&{backgroundImage:"none",backgroundColor:"transparent",color:"inherit"}))}),YT=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiAppBar"}),{className:s,color:c="primary",enableColorOnDark:f=!1,position:p="fixed"}=l,h=$e(l,HT),g=ee({},l,{color:c,position:p,enableColorOnDark:f}),y=qT(g);return m.jsx(PT,ee({square:!0,component:"header",ownerState:g,elevation:4,className:ke(y.root,s,p==="fixed"&&"mui-fixed"),ref:o},h))}),GT=["theme"];function VT(n){let{theme:r}=n,o=$e(n,GT);const l=r[Nl];let s=l||r;return typeof r!="function"&&(l&&!l.vars?s=ee({},l,{vars:null}):r&&!r.vars&&(s=ee({},r,{vars:null}))),m.jsx(VR,ee({},o,{themeId:l?Nl:void 0,theme:s}))}const XT=At("MuiBox",["root"]),FT=zd(),Xf=TR({themeId:Nl,defaultTheme:FT,defaultClassName:XT.root,generateClassName:qy.generate});function KT(n){return Vt("MuiToolbar",n)}At("MuiToolbar",["root","gutters","regular","dense"]);const QT=["className","component","disableGutters","variant"],ZT=n=>{const{classes:r,disableGutters:o,variant:l}=n;return Wt({root:["root",!o&&"gutters",l]},KT,r)},IT=ct("div",{name:"MuiToolbar",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,!o.disableGutters&&r.gutters,r[o.variant]]}})(({theme:n,ownerState:r})=>ee({position:"relative",display:"flex",alignItems:"center"},!r.disableGutters&&{paddingLeft:n.spacing(2),paddingRight:n.spacing(2),[n.breakpoints.up("sm")]:{paddingLeft:n.spacing(3),paddingRight:n.spacing(3)}},r.variant==="dense"&&{minHeight:48}),({theme:n,ownerState:r})=>r.variant==="regular"&&n.mixins.toolbar),WT=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiToolbar"}),{className:s,component:c="div",disableGutters:f=!1,variant:p="regular"}=l,h=$e(l,QT),g=ee({},l,{component:c,disableGutters:f,variant:p}),y=ZT(g);return m.jsx(IT,ee({as:c,className:ke(y.root,s),ref:o,ownerState:g},h))});function JT(n){return Vt("MuiTypography",n)}At("MuiTypography",["root","h1","h2","h3","h4","h5","h6","subtitle1","subtitle2","body1","body2","inherit","button","caption","overline","alignLeft","alignRight","alignCenter","alignJustify","noWrap","gutterBottom","paragraph"]);const ej=["align","className","component","gutterBottom","noWrap","paragraph","variant","variantMapping"],tj=n=>{const{align:r,gutterBottom:o,noWrap:l,paragraph:s,variant:c,classes:f}=n,p={root:["root",c,n.align!=="inherit"&&`align${qe(r)}`,o&&"gutterBottom",l&&"noWrap",s&&"paragraph"]};return Wt(p,JT,f)},nj=ct("span",{name:"MuiTypography",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,o.variant&&r[o.variant],o.align!=="inherit"&&r[`align${qe(o.align)}`],o.noWrap&&r.noWrap,o.gutterBottom&&r.gutterBottom,o.paragraph&&r.paragraph]}})(({theme:n,ownerState:r})=>ee({margin:0},r.variant==="inherit"&&{font:"inherit"},r.variant!=="inherit"&&n.typography[r.variant],r.align!=="inherit"&&{textAlign:r.align},r.noWrap&&{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},r.gutterBottom&&{marginBottom:"0.35em"},r.paragraph&&{marginBottom:16})),Nv={h1:"h1",h2:"h2",h3:"h3",h4:"h4",h5:"h5",h6:"h6",subtitle1:"h6",subtitle2:"h6",body1:"p",body2:"p",inherit:"p"},aj={primary:"primary.main",textPrimary:"text.primary",secondary:"secondary.main",textSecondary:"text.secondary",error:"error.main"},rj=n=>aj[n]||n,ij=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiTypography"}),s=rj(l.color),c=Dd(ee({},l,{color:s})),{align:f="inherit",className:p,component:h,gutterBottom:g=!1,noWrap:y=!1,paragraph:v=!1,variant:S="body1",variantMapping:j=Nv}=c,w=$e(c,ej),x=ee({},c,{align:f,color:s,className:p,component:h,gutterBottom:g,noWrap:y,paragraph:v,variant:S,variantMapping:j}),_=h||(v?"p":j[S]||Nv[S])||"span",T=tj(x);return m.jsx(nj,ee({as:_,ref:o,ownerState:x,className:ke(T.root,p)},w))});function lj(n){return Vt("MuiIconButton",n)}const oj=At("MuiIconButton",["root","disabled","colorInherit","colorPrimary","colorSecondary","colorError","colorInfo","colorSuccess","colorWarning","edgeStart","edgeEnd","sizeSmall","sizeMedium","sizeLarge"]),sj=["edge","children","className","color","disabled","disableFocusRipple","size"],uj=n=>{const{classes:r,disabled:o,color:l,edge:s,size:c}=n,f={root:["root",o&&"disabled",l!=="default"&&`color${qe(l)}`,s&&`edge${qe(s)}`,`size${qe(c)}`]};return Wt(f,lj,r)},cj=ct(Hd,{name:"MuiIconButton",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,o.color!=="default"&&r[`color${qe(o.color)}`],o.edge&&r[`edge${qe(o.edge)}`],r[`size${qe(o.size)}`]]}})(({theme:n,ownerState:r})=>ee({textAlign:"center",flex:"0 0 auto",fontSize:n.typography.pxToRem(24),padding:8,borderRadius:"50%",overflow:"visible",color:(n.vars||n).palette.action.active,transition:n.transitions.create("background-color",{duration:n.transitions.duration.shortest})},!r.disableRipple&&{"&:hover":{backgroundColor:n.vars?`rgba(${n.vars.palette.action.activeChannel} / ${n.vars.palette.action.hoverOpacity})`:Lt.alpha(n.palette.action.active,n.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:"transparent"}}},r.edge==="start"&&{marginLeft:r.size==="small"?-3:-12},r.edge==="end"&&{marginRight:r.size==="small"?-3:-12}),({theme:n,ownerState:r})=>{var o;const l=(o=(n.vars||n).palette)==null?void 0:o[r.color];return ee({},r.color==="inherit"&&{color:"inherit"},r.color!=="inherit"&&r.color!=="default"&&ee({color:l==null?void 0:l.main},!r.disableRipple&&{"&:hover":ee({},l&&{backgroundColor:n.vars?`rgba(${l.mainChannel} / ${n.vars.palette.action.hoverOpacity})`:Lt.alpha(l.main,n.palette.action.hoverOpacity)},{"@media (hover: none)":{backgroundColor:"transparent"}})}),r.size==="small"&&{padding:5,fontSize:n.typography.pxToRem(18)},r.size==="large"&&{padding:12,fontSize:n.typography.pxToRem(28)},{[`&.${oj.disabled}`]:{backgroundColor:"transparent",color:(n.vars||n).palette.action.disabled}})}),fj=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiIconButton"}),{edge:s=!1,children:c,className:f,color:p="default",disabled:h=!1,disableFocusRipple:g=!1,size:y="medium"}=l,v=$e(l,sj),S=ee({},l,{edge:s,color:p,disabled:h,disableFocusRipple:g,size:y}),j=uj(S);return m.jsx(cj,ee({className:ke(j.root,f),centerRipple:!0,focusRipple:!g,disabled:h,ref:o},v,{ownerState:S,children:c}))});function dj(n){const r=Da(n);return r.body===n?Hl(n).innerWidth>r.documentElement.clientWidth:n.scrollHeight>n.clientHeight}function yl(n,r){r?n.setAttribute("aria-hidden","true"):n.removeAttribute("aria-hidden")}function _v(n){return parseInt(Hl(n).getComputedStyle(n).paddingRight,10)||0}function pj(n){const o=["TEMPLATE","SCRIPT","STYLE","LINK","MAP","META","NOSCRIPT","PICTURE","COL","COLGROUP","PARAM","SLOT","SOURCE","TRACK"].indexOf(n.tagName)!==-1,l=n.tagName==="INPUT"&&n.getAttribute("type")==="hidden";return o||l}function Av(n,r,o,l,s){const c=[r,o,...l];[].forEach.call(n.children,f=>{const p=c.indexOf(f)===-1,h=!pj(f);p&&h&&yl(f,s)})}function Ff(n,r){let o=-1;return n.some((l,s)=>r(l)?(o=s,!0):!1),o}function hj(n,r){const o=[],l=n.container;if(!r.disableScrollLock){if(dj(l)){const f=kR(Da(l));o.push({value:l.style.paddingRight,property:"padding-right",el:l}),l.style.paddingRight=`${_v(l)+f}px`;const p=Da(l).querySelectorAll(".mui-fixed");[].forEach.call(p,h=>{o.push({value:h.style.paddingRight,property:"padding-right",el:h}),h.style.paddingRight=`${_v(h)+f}px`})}let c;if(l.parentNode instanceof DocumentFragment)c=Da(l).body;else{const f=l.parentElement,p=Hl(l);c=(f==null?void 0:f.nodeName)==="HTML"&&p.getComputedStyle(f).overflowY==="scroll"?f:l}o.push({value:c.style.overflow,property:"overflow",el:c},{value:c.style.overflowX,property:"overflow-x",el:c},{value:c.style.overflowY,property:"overflow-y",el:c}),c.style.overflow="hidden"}return()=>{o.forEach(({value:c,el:f,property:p})=>{c?f.style.setProperty(p,c):f.style.removeProperty(p)})}}function mj(n){const r=[];return[].forEach.call(n.children,o=>{o.getAttribute("aria-hidden")==="true"&&r.push(o)}),r}class gj{constructor(){this.containers=void 0,this.modals=void 0,this.modals=[],this.containers=[]}add(r,o){let l=this.modals.indexOf(r);if(l!==-1)return l;l=this.modals.length,this.modals.push(r),r.modalRef&&yl(r.modalRef,!1);const s=mj(o);Av(o,r.mount,r.modalRef,s,!0);const c=Ff(this.containers,f=>f.container===o);return c!==-1?(this.containers[c].modals.push(r),l):(this.containers.push({modals:[r],container:o,restore:null,hiddenSiblings:s}),l)}mount(r,o){const l=Ff(this.containers,c=>c.modals.indexOf(r)!==-1),s=this.containers[l];s.restore||(s.restore=hj(s,o))}remove(r,o=!0){const l=this.modals.indexOf(r);if(l===-1)return l;const s=Ff(this.containers,f=>f.modals.indexOf(r)!==-1),c=this.containers[s];if(c.modals.splice(c.modals.indexOf(r),1),this.modals.splice(l,1),c.modals.length===0)c.restore&&c.restore(),r.modalRef&&yl(r.modalRef,o),Av(c.container,r.mount,r.modalRef,c.hiddenSiblings,!1),this.containers.splice(s,1);else{const f=c.modals[c.modals.length-1];f.modalRef&&yl(f.modalRef,!1)}return l}isTopModal(r){return this.modals.length>0&&this.modals[this.modals.length-1]===r}}const vj=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])'].join(",");function yj(n){const r=parseInt(n.getAttribute("tabindex")||"",10);return Number.isNaN(r)?n.contentEditable==="true"||(n.nodeName==="AUDIO"||n.nodeName==="VIDEO"||n.nodeName==="DETAILS")&&n.getAttribute("tabindex")===null?0:n.tabIndex:r}function bj(n){if(n.tagName!=="INPUT"||n.type!=="radio"||!n.name)return!1;const r=l=>n.ownerDocument.querySelector(`input[type="radio"]${l}`);let o=r(`[name="${n.name}"]:checked`);return o||(o=r(`[name="${n.name}"]`)),o!==n}function xj(n){return!(n.disabled||n.tagName==="INPUT"&&n.type==="hidden"||bj(n))}function Sj(n){const r=[],o=[];return Array.from(n.querySelectorAll(vj)).forEach((l,s)=>{const c=yj(l);c===-1||!xj(l)||(c===0?r.push(l):o.push({documentOrder:s,tabIndex:c,node:l}))}),o.sort((l,s)=>l.tabIndex===s.tabIndex?l.documentOrder-s.documentOrder:l.tabIndex-s.tabIndex).map(l=>l.node).concat(r)}function wj(){return!0}function Ej(n){const{children:r,disableAutoFocus:o=!1,disableEnforceFocus:l=!1,disableRestoreFocus:s=!1,getTabbable:c=Sj,isEnabled:f=wj,open:p}=n,h=R.useRef(!1),g=R.useRef(null),y=R.useRef(null),v=R.useRef(null),S=R.useRef(null),j=R.useRef(!1),w=R.useRef(null),x=la(qs(r),w),_=R.useRef(null);R.useEffect(()=>{!p||!w.current||(j.current=!o)},[o,p]),R.useEffect(()=>{if(!p||!w.current)return;const N=Da(w.current);return w.current.contains(N.activeElement)||(w.current.hasAttribute("tabIndex")||w.current.setAttribute("tabIndex","-1"),j.current&&w.current.focus()),()=>{s||(v.current&&v.current.focus&&(h.current=!0,v.current.focus()),v.current=null)}},[p]),R.useEffect(()=>{if(!p||!w.current)return;const N=Da(w.current),M=A=>{_.current=A,!(l||!f()||A.key!=="Tab")&&N.activeElement===w.current&&A.shiftKey&&(h.current=!0,y.current&&y.current.focus())},O=()=>{const A=w.current;if(A===null)return;if(!N.hasFocus()||!f()||h.current){h.current=!1;return}if(A.contains(N.activeElement)||l&&N.activeElement!==g.current&&N.activeElement!==y.current)return;if(N.activeElement!==S.current)S.current=null;else if(S.current!==null)return;if(!j.current)return;let E=[];if((N.activeElement===g.current||N.activeElement===y.current)&&(E=c(w.current)),E.length>0){var z,D;const B=!!((z=_.current)!=null&&z.shiftKey&&((D=_.current)==null?void 0:D.key)==="Tab"),q=E[0],X=E[E.length-1];typeof q!="string"&&typeof X!="string"&&(B?X.focus():q.focus())}else A.focus()};N.addEventListener("focusin",O),N.addEventListener("keydown",M,!0);const G=setInterval(()=>{N.activeElement&&N.activeElement.tagName==="BODY"&&O()},50);return()=>{clearInterval(G),N.removeEventListener("focusin",O),N.removeEventListener("keydown",M,!0)}},[o,l,s,f,p,c]);const T=N=>{v.current===null&&(v.current=N.relatedTarget),j.current=!0,S.current=N.target;const M=r.props.onFocus;M&&M(N)},C=N=>{v.current===null&&(v.current=N.relatedTarget),j.current=!0};return m.jsxs(R.Fragment,{children:[m.jsx("div",{tabIndex:p?0:-1,onFocus:C,ref:g,"data-testid":"sentinelStart"}),R.cloneElement(r,{ref:x,onFocus:T}),m.jsx("div",{tabIndex:p?0:-1,onFocus:C,ref:y,"data-testid":"sentinelEnd"})]})}function Cj(n){return typeof n=="function"?n():n}const Rj=R.forwardRef(function(r,o){const{children:l,container:s,disablePortal:c=!1}=r,[f,p]=R.useState(null),h=la(R.isValidElement(l)?qs(l):null,o);if(_l(()=>{c||p(Cj(s)||document.body)},[s,c]),_l(()=>{if(f&&!c)return sd(o,f),()=>{sd(o,null)}},[o,f,c]),c){if(R.isValidElement(l)){const g={ref:h};return R.cloneElement(l,g)}return m.jsx(R.Fragment,{children:l})}return m.jsx(R.Fragment,{children:f&&Uv.createPortal(l,f)})}),Jy=n=>n.scrollTop;function ws(n,r){var o,l;const{timeout:s,easing:c,style:f={}}=n;return{duration:(o=f.transitionDuration)!=null?o:typeof s=="number"?s:s[r.mode]||0,easing:(l=f.transitionTimingFunction)!=null?l:typeof c=="object"?c[r.mode]:c,delay:f.transitionDelay}}const Tj=["addEndListener","appear","children","easing","in","onEnter","onEntered","onEntering","onExit","onExited","onExiting","style","timeout","TransitionComponent"],jj={entering:{opacity:1},entered:{opacity:1}},Oj=R.forwardRef(function(r,o){const l=Pd(),s={enter:l.transitions.duration.enteringScreen,exit:l.transitions.duration.leavingScreen},{addEndListener:c,appear:f=!0,children:p,easing:h,in:g,onEnter:y,onEntered:v,onEntering:S,onExit:j,onExited:w,onExiting:x,style:_,timeout:T=s,TransitionComponent:C=$n}=r,N=$e(r,Tj),M=R.useRef(null),O=la(M,qs(p),o),G=ne=>oe=>{if(ne){const Z=M.current;oe===void 0?ne(Z):ne(Z,oe)}},A=G(S),E=G((ne,oe)=>{Jy(ne);const Z=ws({style:_,timeout:T,easing:h},{mode:"enter"});ne.style.webkitTransition=l.transitions.create("opacity",Z),ne.style.transition=l.transitions.create("opacity",Z),y&&y(ne,oe)}),z=G(v),D=G(x),B=G(ne=>{const oe=ws({style:_,timeout:T,easing:h},{mode:"exit"});ne.style.webkitTransition=l.transitions.create("opacity",oe),ne.style.transition=l.transitions.create("opacity",oe),j&&j(ne)}),q=G(w),X=ne=>{c&&c(M.current,ne)};return m.jsx(C,ee({appear:f,in:g,nodeRef:M,onEnter:E,onEntered:z,onEntering:A,onExit:B,onExited:q,onExiting:D,addEndListener:X,timeout:T},N,{children:(ne,oe)=>R.cloneElement(p,ee({style:ee({opacity:0,visibility:ne==="exited"&&!g?"hidden":void 0},jj[ne],_,p.props.style),ref:O},oe))}))});function Nj(n){return Vt("MuiBackdrop",n)}At("MuiBackdrop",["root","invisible"]);const _j=["children","className","component","components","componentsProps","invisible","open","slotProps","slots","TransitionComponent","transitionDuration"],Aj=n=>{const{classes:r,invisible:o}=n;return Wt({root:["root",o&&"invisible"]},Nj,r)},Mj=ct("div",{name:"MuiBackdrop",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,o.invisible&&r.invisible]}})(({ownerState:n})=>ee({position:"fixed",display:"flex",alignItems:"center",justifyContent:"center",right:0,bottom:0,top:0,left:0,backgroundColor:"rgba(0, 0, 0, 0.5)",WebkitTapHighlightColor:"transparent"},n.invisible&&{backgroundColor:"transparent"})),kj=R.forwardRef(function(r,o){var l,s,c;const f=Xt({props:r,name:"MuiBackdrop"}),{children:p,className:h,component:g="div",components:y={},componentsProps:v={},invisible:S=!1,open:j,slotProps:w={},slots:x={},TransitionComponent:_=Oj,transitionDuration:T}=f,C=$e(f,_j),N=ee({},f,{component:g,invisible:S}),M=Aj(N),O=(l=w.root)!=null?l:v.root;return m.jsx(_,ee({in:j,timeout:T},C,{children:m.jsx(Mj,ee({"aria-hidden":!0},O,{as:(s=(c=x.root)!=null?c:y.Root)!=null?s:g,className:ke(M.root,h,O==null?void 0:O.className),ownerState:ee({},N,O==null?void 0:O.ownerState),classes:M,ref:o,children:p}))}))});function Dj(n){return typeof n=="function"?n():n}function zj(n){return n?n.props.hasOwnProperty("in"):!1}const Lj=new gj;function Bj(n){const{container:r,disableEscapeKeyDown:o=!1,disableScrollLock:l=!1,manager:s=Lj,closeAfterTransition:c=!1,onTransitionEnter:f,onTransitionExited:p,children:h,onClose:g,open:y,rootRef:v}=n,S=R.useRef({}),j=R.useRef(null),w=R.useRef(null),x=la(w,v),[_,T]=R.useState(!y),C=zj(h);let N=!0;(n["aria-hidden"]==="false"||n["aria-hidden"]===!1)&&(N=!1);const M=()=>Da(j.current),O=()=>(S.current.modalRef=w.current,S.current.mount=j.current,S.current),G=()=>{s.mount(O(),{disableScrollLock:l}),w.current&&(w.current.scrollTop=0)},A=ii(()=>{const Z=Dj(r)||M().body;s.add(O(),Z),w.current&&G()}),E=R.useCallback(()=>s.isTopModal(O()),[s]),z=ii(Z=>{j.current=Z,Z&&(y&&E()?G():w.current&&yl(w.current,N))}),D=R.useCallback(()=>{s.remove(O(),N)},[N,s]);R.useEffect(()=>()=>{D()},[D]),R.useEffect(()=>{y?A():(!C||!c)&&D()},[y,D,C,c,A]);const B=Z=>le=>{var re;(re=Z.onKeyDown)==null||re.call(Z,le),!(le.key!=="Escape"||le.which===229||!E())&&(o||(le.stopPropagation(),g&&g(le,"escapeKeyDown")))},q=Z=>le=>{var re;(re=Z.onClick)==null||re.call(Z,le),le.target===le.currentTarget&&g&&g(le,"backdropClick")};return{getRootProps:(Z={})=>{const le=Fy(n);delete le.onTransitionEnter,delete le.onTransitionExited;const re=ee({},le,Z);return ee({role:"presentation"},re,{onKeyDown:B(re),ref:x})},getBackdropProps:(Z={})=>{const le=Z;return ee({"aria-hidden":!0},le,{onClick:q(le),open:y})},getTransitionProps:()=>{const Z=()=>{T(!1),f&&f()},le=()=>{T(!0),p&&p(),c&&D()};return{onEnter:cv(Z,h==null?void 0:h.props.onEnter),onExited:cv(le,h==null?void 0:h.props.onExited)}},rootRef:x,portalRef:z,isTopModal:E,exited:_,hasTransition:C}}function Uj(n){return Vt("MuiModal",n)}At("MuiModal",["root","hidden","backdrop"]);const $j=["BackdropComponent","BackdropProps","classes","className","closeAfterTransition","children","container","component","components","componentsProps","disableAutoFocus","disableEnforceFocus","disableEscapeKeyDown","disablePortal","disableRestoreFocus","disableScrollLock","hideBackdrop","keepMounted","onBackdropClick","onClose","onTransitionEnter","onTransitionExited","open","slotProps","slots","theme"],Hj=n=>{const{open:r,exited:o,classes:l}=n;return Wt({root:["root",!r&&o&&"hidden"],backdrop:["backdrop"]},Uj,l)},qj=ct("div",{name:"MuiModal",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,!o.open&&o.exited&&r.hidden]}})(({theme:n,ownerState:r})=>ee({position:"fixed",zIndex:(n.vars||n).zIndex.modal,right:0,bottom:0,top:0,left:0},!r.open&&r.exited&&{visibility:"hidden"})),Pj=ct(kj,{name:"MuiModal",slot:"Backdrop",overridesResolver:(n,r)=>r.backdrop})({zIndex:-1}),Yj=R.forwardRef(function(r,o){var l,s,c,f,p,h;const g=Xt({name:"MuiModal",props:r}),{BackdropComponent:y=Pj,BackdropProps:v,className:S,closeAfterTransition:j=!1,children:w,container:x,component:_,components:T={},componentsProps:C={},disableAutoFocus:N=!1,disableEnforceFocus:M=!1,disableEscapeKeyDown:O=!1,disablePortal:G=!1,disableRestoreFocus:A=!1,disableScrollLock:E=!1,hideBackdrop:z=!1,keepMounted:D=!1,onBackdropClick:B,open:q,slotProps:X,slots:ne}=g,oe=$e(g,$j),Z=ee({},g,{closeAfterTransition:j,disableAutoFocus:N,disableEnforceFocus:M,disableEscapeKeyDown:O,disablePortal:G,disableRestoreFocus:A,disableScrollLock:E,hideBackdrop:z,keepMounted:D}),{getRootProps:le,getBackdropProps:re,getTransitionProps:pe,portalRef:U,isTopModal:ie,exited:Q,hasTransition:V}=Bj(ee({},Z,{rootRef:o})),W=ee({},Z,{exited:Q}),se=Hj(W),H={};if(w.props.tabIndex===void 0&&(H.tabIndex="-1"),V){const{onEnter:Ee,onExited:_e}=pe();H.onEnter=Ee,H.onExited=_e}const fe=(l=(s=ne==null?void 0:ne.root)!=null?s:T.Root)!=null?l:qj,L=(c=(f=ne==null?void 0:ne.backdrop)!=null?f:T.Backdrop)!=null?c:y,te=(p=X==null?void 0:X.root)!=null?p:C.root,he=(h=X==null?void 0:X.backdrop)!=null?h:C.backdrop,ge=dv({elementType:fe,externalSlotProps:te,externalForwardedProps:oe,getSlotProps:le,additionalProps:{ref:o,as:_},ownerState:W,className:ke(S,te==null?void 0:te.className,se==null?void 0:se.root,!W.open&&W.exited&&(se==null?void 0:se.hidden))}),de=dv({elementType:L,externalSlotProps:he,additionalProps:v,getSlotProps:Ee=>re(ee({},Ee,{onClick:_e=>{B&&B(_e),Ee!=null&&Ee.onClick&&Ee.onClick(_e)}})),className:ke(he==null?void 0:he.className,v==null?void 0:v.className,se==null?void 0:se.backdrop),ownerState:W});return!D&&!q&&(!V||Q)?null:m.jsx(Rj,{ref:U,container:x,disablePortal:G,children:m.jsxs(fe,ee({},ge,{children:[!z&&y?m.jsx(L,ee({},de)):null,m.jsx(Ej,{disableEnforceFocus:M,disableAutoFocus:N,disableRestoreFocus:A,isEnabled:ie,open:q,children:R.cloneElement(w,H)})]}))})}),Gj=["addEndListener","appear","children","container","direction","easing","in","onEnter","onEntered","onEntering","onExit","onExited","onExiting","style","timeout","TransitionComponent"];function Vj(n,r,o){const l=r.getBoundingClientRect(),s=o&&o.getBoundingClientRect(),c=Hl(r);let f;if(r.fakeTransform)f=r.fakeTransform;else{const g=c.getComputedStyle(r);f=g.getPropertyValue("-webkit-transform")||g.getPropertyValue("transform")}let p=0,h=0;if(f&&f!=="none"&&typeof f=="string"){const g=f.split("(")[1].split(")")[0].split(",");p=parseInt(g[4],10),h=parseInt(g[5],10)}return n==="left"?s?`translateX(${s.right+p-l.left}px)`:`translateX(${c.innerWidth+p-l.left}px)`:n==="right"?s?`translateX(-${l.right-s.left-p}px)`:`translateX(-${l.left+l.width-p}px)`:n==="up"?s?`translateY(${s.bottom+h-l.top}px)`:`translateY(${c.innerHeight+h-l.top}px)`:s?`translateY(-${l.top-s.top+l.height-h}px)`:`translateY(-${l.top+l.height-h}px)`}function Xj(n){return typeof n=="function"?n():n}function us(n,r,o){const l=Xj(o),s=Vj(n,r,l);s&&(r.style.webkitTransform=s,r.style.transform=s)}const Fj=R.forwardRef(function(r,o){const l=Pd(),s={enter:l.transitions.easing.easeOut,exit:l.transitions.easing.sharp},c={enter:l.transitions.duration.enteringScreen,exit:l.transitions.duration.leavingScreen},{addEndListener:f,appear:p=!0,children:h,container:g,direction:y="down",easing:v=s,in:S,onEnter:j,onEntered:w,onEntering:x,onExit:_,onExited:T,onExiting:C,style:N,timeout:M=c,TransitionComponent:O=$n}=r,G=$e(r,Gj),A=R.useRef(null),E=la(qs(h),A,o),z=re=>pe=>{re&&(pe===void 0?re(A.current):re(A.current,pe))},D=z((re,pe)=>{us(y,re,g),Jy(re),j&&j(re,pe)}),B=z((re,pe)=>{const U=ws({timeout:M,style:N,easing:v},{mode:"enter"});re.style.webkitTransition=l.transitions.create("-webkit-transform",ee({},U)),re.style.transition=l.transitions.create("transform",ee({},U)),re.style.webkitTransform="none",re.style.transform="none",x&&x(re,pe)}),q=z(w),X=z(C),ne=z(re=>{const pe=ws({timeout:M,style:N,easing:v},{mode:"exit"});re.style.webkitTransition=l.transitions.create("-webkit-transform",pe),re.style.transition=l.transitions.create("transform",pe),us(y,re,g),_&&_(re)}),oe=z(re=>{re.style.webkitTransition="",re.style.transition="",T&&T(re)}),Z=re=>{f&&f(A.current,re)},le=R.useCallback(()=>{A.current&&us(y,A.current,g)},[y,g]);return R.useEffect(()=>{if(S||y==="down"||y==="right")return;const re=AR(()=>{A.current&&us(y,A.current,g)}),pe=Hl(A.current);return pe.addEventListener("resize",re),()=>{re.clear(),pe.removeEventListener("resize",re)}},[y,S,g]),R.useEffect(()=>{S||le()},[S,le]),m.jsx(O,ee({nodeRef:A,onEnter:D,onEntered:q,onEntering:B,onExit:ne,onExited:oe,onExiting:X,addEndListener:Z,appear:p,in:S,timeout:M},G,{children:(re,pe)=>R.cloneElement(h,ee({ref:E,style:ee({visibility:re==="exited"&&!S?"hidden":void 0},N,h.props.style)},pe))}))});function Kj(n){return Vt("MuiDrawer",n)}At("MuiDrawer",["root","docked","paper","paperAnchorLeft","paperAnchorRight","paperAnchorTop","paperAnchorBottom","paperAnchorDockedLeft","paperAnchorDockedRight","paperAnchorDockedTop","paperAnchorDockedBottom","modal"]);const Qj=["BackdropProps"],Zj=["anchor","BackdropProps","children","className","elevation","hideBackdrop","ModalProps","onClose","open","PaperProps","SlideProps","TransitionComponent","transitionDuration","variant"],eb=(n,r)=>{const{ownerState:o}=n;return[r.root,(o.variant==="permanent"||o.variant==="persistent")&&r.docked,r.modal]},Ij=n=>{const{classes:r,anchor:o,variant:l}=n,s={root:["root"],docked:[(l==="permanent"||l==="persistent")&&"docked"],modal:["modal"],paper:["paper",`paperAnchor${qe(o)}`,l!=="temporary"&&`paperAnchorDocked${qe(o)}`]};return Wt(s,Kj,r)},Wj=ct(Yj,{name:"MuiDrawer",slot:"Root",overridesResolver:eb})(({theme:n})=>({zIndex:(n.vars||n).zIndex.drawer})),Mv=ct("div",{shouldForwardProp:Ld,name:"MuiDrawer",slot:"Docked",skipVariantsResolver:!1,overridesResolver:eb})({flex:"0 0 auto"}),Jj=ct(Wy,{name:"MuiDrawer",slot:"Paper",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.paper,r[`paperAnchor${qe(o.anchor)}`],o.variant!=="temporary"&&r[`paperAnchorDocked${qe(o.anchor)}`]]}})(({theme:n,ownerState:r})=>ee({overflowY:"auto",display:"flex",flexDirection:"column",height:"100%",flex:"1 0 auto",zIndex:(n.vars||n).zIndex.drawer,WebkitOverflowScrolling:"touch",position:"fixed",top:0,outline:0},r.anchor==="left"&&{left:0},r.anchor==="top"&&{top:0,left:0,right:0,height:"auto",maxHeight:"100%"},r.anchor==="right"&&{right:0},r.anchor==="bottom"&&{top:"auto",left:0,bottom:0,right:0,height:"auto",maxHeight:"100%"},r.anchor==="left"&&r.variant!=="temporary"&&{borderRight:`1px solid ${(n.vars||n).palette.divider}`},r.anchor==="top"&&r.variant!=="temporary"&&{borderBottom:`1px solid ${(n.vars||n).palette.divider}`},r.anchor==="right"&&r.variant!=="temporary"&&{borderLeft:`1px solid ${(n.vars||n).palette.divider}`},r.anchor==="bottom"&&r.variant!=="temporary"&&{borderTop:`1px solid ${(n.vars||n).palette.divider}`})),tb={left:"right",right:"left",top:"down",bottom:"up"};function e5(n){return["left","right"].indexOf(n)!==-1}function t5({direction:n},r){return n==="rtl"&&e5(r)?tb[r]:r}const n5=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiDrawer"}),s=Pd(),c=GR(),f={enter:s.transitions.duration.enteringScreen,exit:s.transitions.duration.leavingScreen},{anchor:p="left",BackdropProps:h,children:g,className:y,elevation:v=16,hideBackdrop:S=!1,ModalProps:{BackdropProps:j}={},onClose:w,open:x=!1,PaperProps:_={},SlideProps:T,TransitionComponent:C=Fj,transitionDuration:N=f,variant:M="temporary"}=l,O=$e(l.ModalProps,Qj),G=$e(l,Zj),A=R.useRef(!1);R.useEffect(()=>{A.current=!0},[]);const E=t5({direction:c?"rtl":"ltr"},p),D=ee({},l,{anchor:p,elevation:v,open:x,variant:M},G),B=Ij(D),q=m.jsx(Jj,ee({elevation:M==="temporary"?v:0,square:!0},_,{className:ke(B.paper,_.className),ownerState:D,children:g}));if(M==="permanent")return m.jsx(Mv,ee({className:ke(B.root,B.docked,y),ownerState:D,ref:o},G,{children:q}));const X=m.jsx(C,ee({in:x,direction:tb[E],timeout:N,appear:A.current},T,{children:q}));return M==="persistent"?m.jsx(Mv,ee({className:ke(B.root,B.docked,y),ownerState:D,ref:o},G,{children:X})):m.jsx(Wj,ee({BackdropProps:ee({},h,j,{transitionDuration:N}),className:ke(B.root,B.modal,y),open:x,ownerState:D,onClose:w,hideBackdrop:S,ref:o},G,O,{children:X}))}),bl=R.createContext({});function a5(n){return Vt("MuiList",n)}At("MuiList",["root","padding","dense","subheader"]);const r5=["children","className","component","dense","disablePadding","subheader"],i5=n=>{const{classes:r,disablePadding:o,dense:l,subheader:s}=n;return Wt({root:["root",!o&&"padding",l&&"dense",s&&"subheader"]},a5,r)},l5=ct("ul",{name:"MuiList",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,!o.disablePadding&&r.padding,o.dense&&r.dense,o.subheader&&r.subheader]}})(({ownerState:n})=>ee({listStyle:"none",margin:0,padding:0,position:"relative"},!n.disablePadding&&{paddingTop:8,paddingBottom:8},n.subheader&&{paddingTop:0})),o5=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiList"}),{children:s,className:c,component:f="ul",dense:p=!1,disablePadding:h=!1,subheader:g}=l,y=$e(l,r5),v=R.useMemo(()=>({dense:p}),[p]),S=ee({},l,{component:f,dense:p,disablePadding:h}),j=i5(S);return m.jsx(bl.Provider,{value:v,children:m.jsxs(l5,ee({as:f,className:ke(j.root,c),ref:o,ownerState:S},y,{children:[g,s]}))})});function s5(n){return Vt("MuiListItem",n)}const ai=At("MuiListItem",["root","container","focusVisible","dense","alignItemsFlexStart","disabled","divider","gutters","padding","button","secondaryAction","selected"]),u5=At("MuiListItemButton",["root","focusVisible","dense","alignItemsFlexStart","disabled","divider","gutters","selected"]);function c5(n){return Vt("MuiListItemSecondaryAction",n)}At("MuiListItemSecondaryAction",["root","disableGutters"]);const f5=["className"],d5=n=>{const{disableGutters:r,classes:o}=n;return Wt({root:["root",r&&"disableGutters"]},c5,o)},p5=ct("div",{name:"MuiListItemSecondaryAction",slot:"Root",overridesResolver:(n,r)=>{const{ownerState:o}=n;return[r.root,o.disableGutters&&r.disableGutters]}})(({ownerState:n})=>ee({position:"absolute",right:16,top:"50%",transform:"translateY(-50%)"},n.disableGutters&&{right:0})),nb=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiListItemSecondaryAction"}),{className:s}=l,c=$e(l,f5),f=R.useContext(bl),p=ee({},l,{disableGutters:f.disableGutters}),h=d5(p);return m.jsx(p5,ee({className:ke(h.root,s),ownerState:p,ref:o},c))});nb.muiName="ListItemSecondaryAction";const h5=["className"],m5=["alignItems","autoFocus","button","children","className","component","components","componentsProps","ContainerComponent","ContainerProps","dense","disabled","disableGutters","disablePadding","divider","focusVisibleClassName","secondaryAction","selected","slotProps","slots"],g5=(n,r)=>{const{ownerState:o}=n;return[r.root,o.dense&&r.dense,o.alignItems==="flex-start"&&r.alignItemsFlexStart,o.divider&&r.divider,!o.disableGutters&&r.gutters,!o.disablePadding&&r.padding,o.button&&r.button,o.hasSecondaryAction&&r.secondaryAction]},v5=n=>{const{alignItems:r,button:o,classes:l,dense:s,disabled:c,disableGutters:f,disablePadding:p,divider:h,hasSecondaryAction:g,selected:y}=n;return Wt({root:["root",s&&"dense",!f&&"gutters",!p&&"padding",h&&"divider",c&&"disabled",o&&"button",r==="flex-start"&&"alignItemsFlexStart",g&&"secondaryAction",y&&"selected"],container:["container"]},s5,l)},y5=ct("div",{name:"MuiListItem",slot:"Root",overridesResolver:g5})(({theme:n,ownerState:r})=>ee({display:"flex",justifyContent:"flex-start",alignItems:"center",position:"relative",textDecoration:"none",width:"100%",boxSizing:"border-box",textAlign:"left"},!r.disablePadding&&ee({paddingTop:8,paddingBottom:8},r.dense&&{paddingTop:4,paddingBottom:4},!r.disableGutters&&{paddingLeft:16,paddingRight:16},!!r.secondaryAction&&{paddingRight:48}),!!r.secondaryAction&&{[`& > .${u5.root}`]:{paddingRight:48}},{[`&.${ai.focusVisible}`]:{backgroundColor:(n.vars||n).palette.action.focus},[`&.${ai.selected}`]:{backgroundColor:n.vars?`rgba(${n.vars.palette.primary.mainChannel} / ${n.vars.palette.action.selectedOpacity})`:Lt.alpha(n.palette.primary.main,n.palette.action.selectedOpacity),[`&.${ai.focusVisible}`]:{backgroundColor:n.vars?`rgba(${n.vars.palette.primary.mainChannel} / calc(${n.vars.palette.action.selectedOpacity} + ${n.vars.palette.action.focusOpacity}))`:Lt.alpha(n.palette.primary.main,n.palette.action.selectedOpacity+n.palette.action.focusOpacity)}},[`&.${ai.disabled}`]:{opacity:(n.vars||n).palette.action.disabledOpacity}},r.alignItems==="flex-start"&&{alignItems:"flex-start"},r.divider&&{borderBottom:`1px solid ${(n.vars||n).palette.divider}`,backgroundClip:"padding-box"},r.button&&{transition:n.transitions.create("background-color",{duration:n.transitions.duration.shortest}),"&:hover":{textDecoration:"none",backgroundColor:(n.vars||n).palette.action.hover,"@media (hover: none)":{backgroundColor:"transparent"}},[`&.${ai.selected}:hover`]:{backgroundColor:n.vars?`rgba(${n.vars.palette.primary.mainChannel} / calc(${n.vars.palette.action.selectedOpacity} + ${n.vars.palette.action.hoverOpacity}))`:Lt.alpha(n.palette.primary.main,n.palette.action.selectedOpacity+n.palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:n.vars?`rgba(${n.vars.palette.primary.mainChannel} / ${n.vars.palette.action.selectedOpacity})`:Lt.alpha(n.palette.primary.main,n.palette.action.selectedOpacity)}}},r.hasSecondaryAction&&{paddingRight:48})),b5=ct("li",{name:"MuiListItem",slot:"Container",overridesResolver:(n,r)=>r.container})({position:"relative"}),ti=R.forwardRef(function(r,o){const l=Xt({props:r,name:"MuiListItem"}),{alignItems:s="center",autoFocus:c=!1,button:f=!1,children:p,className:h,component:g,components:y={},componentsProps:v={},ContainerComponent:S="li",ContainerProps:{className:j}={},dense:w=!1,disabled:x=!1,disableGutters:_=!1,disablePadding:T=!1,divider:C=!1,focusVisibleClassName:N,secondaryAction:M,selected:O=!1,slotProps:G={},slots:A={}}=l,E=$e(l.ContainerProps,h5),z=$e(l,m5),D=R.useContext(bl),B=R.useMemo(()=>({dense:w||D.dense||!1,alignItems:s,disableGutters:_}),[s,D.dense,w,_]),q=R.useRef(null);_l(()=>{c&&q.current&&q.current.focus()},[c]);const X=R.Children.toArray(p),ne=X.length&&MR(X[X.length-1],["ListItemSecondaryAction"]),oe=ee({},l,{alignItems:s,autoFocus:c,button:f,dense:B.dense,disabled:x,disableGutters:_,disablePadding:T,divider:C,hasSecondaryAction:ne,selected:O}),Z=v5(oe),le=la(q,o),re=A.root||y.Root||y5,pe=G.root||v.root||{},U=ee({className:ke(Z.root,pe.className,h),disabled:x},z);let ie=g||"li";return f&&(U.component=g||"div",U.focusVisibleClassName=ke(ai.focusVisible,N),ie=Hd),ne?(ie=!U.component&&!g?"div":ie,S==="li"&&(ie==="li"?ie="div":U.component==="li"&&(U.component="div")),m.jsx(bl.Provider,{value:B,children:m.jsxs(b5,ee({as:S,className:ke(Z.container,j),ref:le,ownerState:oe},E,{children:[m.jsx(re,ee({},pe,!dd(re)&&{as:ie,ownerState:ee({},oe,pe.ownerState)},U,{children:X})),X.pop()]}))})):m.jsx(bl.Provider,{value:B,children:m.jsxs(re,ee({},pe,{as:ie,ref:le},!dd(re)&&{ownerState:ee({},oe,pe.ownerState)},U,{children:[X,M&&m.jsx(nb,{children:M})]}))})}),kv=()=>{const{checkContext:n}=R.useContext(mn),r=()=>{const o=window.location.pathname;if(o!=="/login"){const l=window.location.origin+o;localStorage.setItem("logoutReturnPath",l)}xt.get("/logout").then(({data:l})=>{l.success?n():console.log("LOGOUT: Logout failed")}).catch(l=>{console.error("LOGOUT: Error during logout:",l)})};return m.jsx(Ba,{color:"inherit",sx:{textTransform:"none",color:"#3874CB",fontFamily:"Inter",fontWeight:450,fontSize:"14px",textDecoration:"underline"},onClick:r,disableRipple:!0,children:"Logout"})},Dv=()=>m.jsx(Ba,{color:"inherit",component:Rs,to:"/about",sx:{textTransform:"none",color:"#000000",fontFamily:"Inter",fontWeight:450,fontSize:"14px"},disableRipple:!0,children:"About"}),zv=()=>m.jsx(Ba,{color:"inherit",component:Rs,to:"/account",sx:{textTransform:"none",color:"#000000",fontFamily:"Inter",fontWeight:450,fontSize:"14px"},disableRipple:!0,children:"My Labs"}),x5=()=>{const n=pi(),r=o=>{n.pathname==="/"&&(o.preventDefault(),window.location.reload())};return m.jsxs(Ba,{component:Rs,to:"/",onClick:r,disableRipple:!0,children:[m.jsx("img",{src:"/assets/logos/paperclip.png",alt:"ylabs-logo",className:"mr-2",style:{width:"31.65px",height:"27px"}}),m.jsx("img",{src:"/assets/logos/ylabs-blue.png",alt:"ylabs-logo",style:{width:"65.17px",height:"27px"}})]})},S5=()=>m.jsx(Ba,{color:"inherit",component:Rs,to:"/",sx:{textTransform:"none",color:"#000000",fontFamily:"Inter",fontWeight:450,fontSize:"14px"},disableRipple:!0,children:"Find Labs"}),Lv=()=>{const n=()=>{window.location.reload()};return m.jsxs(Ba,{onClick:n,disableRipple:!0,children:[m.jsx("img",{src:"/assets/logos/paperclip.png",alt:"ylabs-logo",className:"mr-2",style:{width:"31.65px",height:"27px"}}),m.jsx("img",{src:"/assets/logos/ylabs-blue.png",alt:"ylabs-logo",style:{width:"65.17px",height:"27px"}})]})},w5=()=>m.jsx(Ba,{component:"a",href:"https://docs.google.com/forms/d/e/1FAIpQLSf2BE6MBulJHWXhDDp3y4Nixwe6EH0Oo9X1pTo976-KrJKv5g/viewform?usp=dialog",target:"_blank",rel:"noopener noreferrer",color:"inherit",sx:{textTransform:"none",color:"#000000",fontFamily:"Inter",fontWeight:450,fontSize:"14px"},disableRipple:!0,children:"Feedback"}),E5=zd({breakpoints:{values:{xs:0,sm:640,md:768,lg:1024,xl:1280}}}),C5="768px",R5=()=>m.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:"4px",padding:"2px"},children:[m.jsx("div",{style:{width:"18px",height:"2px",backgroundColor:"black"}}),m.jsx("div",{style:{width:"18px",height:"2px",backgroundColor:"black"}}),m.jsx("div",{style:{width:"18px",height:"2px",backgroundColor:"black"}})]});function T5(){const{isAuthenticated:n}=R.useContext(mn),[r,o]=R.useState(!1);_R(`(max-width:${C5})`);const l=c=>f=>{f.type==="keydown"&&(f.key==="Tab"||f.key==="Shift")||o(c)},s=()=>{const c={"& .MuiButton-root":{paddingLeft:1,justifyContent:"flex-start",width:"100%"}};return m.jsx(Xf,{sx:{width:250},role:"presentation",onClick:l(!1),onKeyDown:l(!1),children:m.jsx(o5,{children:n?m.jsxs(m.Fragment,{children:[m.jsx(ti,{sx:c,children:m.jsx(S5,{})}),m.jsx(ti,{sx:c,children:m.jsx(zv,{})}),m.jsx(ti,{sx:c,children:m.jsx(Dv,{})}),m.jsx(ti,{sx:c,children:m.jsx(w5,{})}),m.jsx(ti,{sx:c,children:m.jsx(kv,{})})]}):m.jsx(ti,{sx:c,children:m.jsx(Lv,{})})})})};return m.jsx(VT,{theme:E5,children:m.jsx(Xf,{sx:{flexGrow:1},children:m.jsx(YT,{position:"fixed",sx:{backgroundColor:"#FFFFFF",height:{xs:"64px",sm:"64px"},"& .MuiToolbar-root":{minHeight:"64px !important",height:"64px !important",paddingLeft:{lg:"85px"},paddingRight:{lg:"85px"},transition:"padding 0.3s ease"},boxShadow:"0px 1px 5px rgba(0, 0, 0, 0.2)"},children:m.jsxs(WT,{sx:{height:"64px"},children:[n?m.jsx(x5,{}):m.jsx(Lv,{}),m.jsx(ij,{variant:"h6",component:"div",sx:{flexGrow:1}}),n&&m.jsxs(m.Fragment,{children:[m.jsxs(Xf,{sx:{display:{xs:"none",md:"flex"},gap:"14px"},children:[m.jsx(zv,{}),m.jsx(Dv,{}),m.jsx(kv,{})]}),m.jsx(fj,{size:"large",edge:"start",color:"inherit","aria-label":"menu",onClick:l(!0),sx:{marginLeft:"18px",borderRadius:"4px",padding:"8px","&:hover":{backgroundColor:"rgba(0, 0, 0, 0.04)",borderRadius:"4px"}},children:m.jsx(R5,{})}),m.jsx(n5,{anchor:"right",open:r,onClose:l(!1),children:s()})]})]})})})})}const j5=()=>m.jsxs(OS,{children:[m.jsx(T5,{}),m.jsxs(SS,{children:[m.jsx(Na,{path:"/",element:m.jsx(ul,{Component:Pg,unknownBlocked:!0})}),m.jsx(Na,{path:"/about",element:m.jsx(ul,{Component:ST,unknownBlocked:!0})}),m.jsx(Na,{path:"/account",element:m.jsx(ul,{Component:MT,unknownBlocked:!0})}),m.jsx(Na,{path:"/login",element:m.jsx(mT,{})}),m.jsx(Na,{path:"/login-error",element:m.jsx(kS,{Component:DT})}),m.jsx(Na,{path:"/unknown",element:m.jsx(ul,{Component:kT,knownBlocked:!0})}),m.jsx(Na,{path:"/*",element:m.jsx(ul,{Component:Pg,unknownBlocked:!0})})]})]}),O5=({children:n})=>{const[r,o]=R.useState(!0),[l,s]=R.useState(!1),[c,f]=R.useState(),p=R.useCallback(()=>{o(!0),xt.get("/check",{withCredentials:!0}).then(({data:h})=>{h.auth?(s(!0),f(h.user)):(s(!1),f(void 0)),o(!1)}).catch(h=>{console.error("Auth check failed:",h),s(!1),f(void 0),o(!1),Fe({text:"Something went wrong while checking authentication status.",icon:"warning"})})},[]);return R.useEffect(()=>{p()},[p]),m.jsx(mn.Provider,{value:{isLoading:r,isAuthenticated:l,user:c,checkContext:p},children:n})},ab=document.getElementById("root");if(!ab)throw new Error("Root container missing in index.html");const N5=_1.createRoot(ab);N5.render(m.jsx(zt.StrictMode,{children:m.jsx(O5,{children:m.jsx(j5,{})})})); diff --git a/client/src/components/accounts/ListingCard.tsx b/client/src/components/accounts/ListingCard.tsx index 2b6cd9a..89cb5fc 100644 --- a/client/src/components/accounts/ListingCard.tsx +++ b/client/src/components/accounts/ListingCard.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; -import { NewListing } from '../../types/types'; +import { Listing } from '../../types/types'; import ListingForm from './ListingForm'; import { departmentCategories } from '../../utils/departmentNames'; import { createListing } from '../../utils/apiCleaner'; @@ -9,15 +9,14 @@ import { useContext } from "react"; import UserContext from "../../contexts/UserContext"; interface ListingCardProps { - listing: NewListing; + listing: Listing; favListingsIds: string[]; - updateFavorite: (listing: NewListing, listingId: string, favorite: boolean) => void; - updateListing: (newListing: NewListing) => void; - postListing: (newListing: NewListing) => void; - postNewListing: (newListing: NewListing) => void; + updateFavorite: (listing: Listing, listingId: string, favorite: boolean) => void; + updateListing: (listing: Listing) => void; + postListing: (listing: Listing) => void; clearCreatedListing: () => void; - deleteListing: (listing: NewListing) => void; - openModal: (listing: NewListing) => void; + deleteListing: (listing: Listing) => void; + openModal: (listing: Listing) => void; globalEditing: boolean; setGlobalEditing: (editing: boolean) => void; editable: boolean; @@ -30,7 +29,6 @@ const ListingCard = ({ updateFavorite, updateListing, postListing, - postNewListing, clearCreatedListing, deleteListing, openModal, @@ -51,7 +49,7 @@ const ListingCard = ({ const canDelete = user && (user.netId === listing.ownerId); // Store the original listing before editing begins. - const originalListingRef = useRef(null); + const originalListingRef = useRef(null); const departmentColors = [ "bg-blue-200", @@ -170,11 +168,11 @@ const ListingCard = ({ e.stopPropagation(); if (archived) { setArchived(false); - axios.put(`/newListings/${listing.id}/unarchive`, { withCredentials: true }) + axios.put(`/listings/${listing.id}/unarchive`, { withCredentials: true }) .then((response) => { const responseListing = response.data.listing; - const newListing = createListing(responseListing); - updateListing(newListing); + const listing = createListing(responseListing); + updateListing(listing); }) .catch((error) => { setArchived(true); @@ -189,11 +187,11 @@ const ListingCard = ({ }); } else { setArchived(true); - axios.put(`/newListings/${listing.id}/archive`, { withCredentials: true }) + axios.put(`/listings/${listing.id}/archive`, { withCredentials: true }) .then((response) => { const responseListing = response.data.listing; - const newListing = createListing(responseListing); - updateListing(newListing); + const listing = createListing(responseListing); + updateListing(listing); }) .catch((error) => { setArchived(false); @@ -388,8 +386,8 @@ const ListingCard = ({ setEditing(false); setGlobalEditing(false); }} - onCreate={(newListing) => { - postNewListing(newListing); + onCreate={(listing) => { + postListing(listing); setEditing(false); setGlobalEditing(false); }} diff --git a/client/src/components/accounts/ListingForm/index.tsx b/client/src/components/accounts/ListingForm/index.tsx index ce60fbd..2bc1f8c 100644 --- a/client/src/components/accounts/ListingForm/index.tsx +++ b/client/src/components/accounts/ListingForm/index.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { NewListing } from '../../../types/types'; +import { Listing } from '../../../types/types'; import { departmentNames } from '../../../utils/departmentNames'; import swal from "sweetalert"; import axios from '../../../utils/axios'; @@ -17,12 +17,12 @@ import { useContext } from "react"; import UserContext from "../../../contexts/UserContext"; interface ListingFormProps { - listing: NewListing; + listing: Listing; isCreated: boolean; - onLoad: (updatedListing: NewListing, success: boolean) => void; + onLoad: (updatedListing: Listing, success: boolean) => void; onCancel?: () => void; - onSave?: (updatedListing: NewListing) => void; - onCreate?: (newListing: NewListing) => void; + onSave?: (updatedListing: Listing) => void; + onCreate?: (listing: Listing) => void; } const ListingForm = ({ listing, isCreated, onLoad, onCancel, onSave, onCreate }: ListingFormProps) => { @@ -61,31 +61,31 @@ const ListingForm = ({ listing, isCreated, onLoad, onCancel, onSave, onCreate }: useEffect(() => { if (!isCreated) { setLoading(true); - axios.get(`/newListings/${listing.id}`, { withCredentials: true }).then((response) => { + axios.get(`/listings/${listing.id}`, { withCredentials: true }).then((response) => { if (!response.data.listing) { console.error(`Response, but no listing ${listing.id}:`, response.data); onLoad(listing, false); return; } - const newListing = createListing(response.data.listing); + const listing = createListing(response.data.listing); // Update state with new listing data - setTitle(newListing.title); - setProfessorNames([...newListing.professorNames]); - setOwnerName(`${newListing.ownerFirstName} ${newListing.ownerLastName}`); - setDepartments([...newListing.departments]); - setEmails([...newListing.emails]); - setOwnerEmail(newListing.ownerEmail); - setWebsites(newListing.websites ? [...newListing.websites] : []); - setDescription(newListing.description); - setKeywords(newListing.keywords ? [...newListing.keywords] : []); - setEstablished(newListing.established || ''); - setHiringStatus(newListing.hiringStatus); - setArchived(newListing.archived); + setTitle(listing.title); + setProfessorNames([...listing.professorNames]); + setOwnerName(`${listing.ownerFirstName} ${listing.ownerLastName}`); + setDepartments([...listing.departments]); + setEmails([...listing.emails]); + setOwnerEmail(listing.ownerEmail); + setWebsites(listing.websites ? [...listing.websites] : []); + setDescription(listing.description); + setKeywords(listing.keywords ? [...listing.keywords] : []); + setEstablished(listing.established || ''); + setHiringStatus(listing.hiringStatus); + setArchived(listing.archived); - onLoad(newListing, true); + onLoad(listing, true); setAvailableDepartments( - departmentNames.filter(dept => !newListing.departments.includes(dept)).sort() + departmentNames.filter(dept => !listing.departments.includes(dept)).sort() ); setLoading(false); }).catch((error) => { @@ -102,7 +102,7 @@ const ListingForm = ({ listing, isCreated, onLoad, onCancel, onSave, onCreate }: // Live update preview when editing or creating a listing useEffect(() => { - const updatedListing: NewListing = { + const updatedListing: Listing = { ...listing, title, professorNames, @@ -142,7 +142,7 @@ const ListingForm = ({ listing, isCreated, onLoad, onCancel, onSave, onCreate }: // Only proceed if no errors if (Object.keys(filteredErrors).length === 0) { - const updatedListing: NewListing = { + const updatedListing: Listing = { ...listing, title, professorIds, diff --git a/client/src/components/accounts/ListingModal.tsx b/client/src/components/accounts/ListingModal.tsx index a08ed93..e67df84 100644 --- a/client/src/components/accounts/ListingModal.tsx +++ b/client/src/components/accounts/ListingModal.tsx @@ -1,14 +1,14 @@ import React, { useEffect, useState, useContext } from 'react'; -import { NewListing } from '../../types/types'; +import { Listing } from '../../types/types'; import { departmentCategories } from '../../utils/departmentNames'; import UserContext from "../../contexts/UserContext"; interface ListingModalProps { isOpen: boolean; onClose: () => void; - listing: NewListing; + listing: Listing; favListingsIds: string[]; - updateFavorite: (listing: NewListing, listingId: string, favorite: boolean) => void; + updateFavorite: (listing: Listing, listingId: string, favorite: boolean) => void; } const ListingModal = ({ isOpen, onClose, listing, favListingsIds, updateFavorite }: ListingModalProps) => { diff --git a/client/src/components/home/ListingCard.tsx b/client/src/components/home/ListingCard.tsx index 2340d6a..5afbe5e 100644 --- a/client/src/components/home/ListingCard.tsx +++ b/client/src/components/home/ListingCard.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; -import { NewListing } from '../../types/types'; +import { Listing } from '../../types/types'; import { departmentCategories } from '../../utils/departmentNames'; import axios from "../../utils/axios"; import swal from "sweetalert"; @@ -7,10 +7,10 @@ import { useContext } from "react"; import UserContext from "../../contexts/UserContext"; interface ListingCardProps { - listing: NewListing; + listing: Listing; favListingsIds: string[]; updateFavorite: (listingId: string, favorite: boolean) => void; - openModal: (listing: NewListing) => void; + openModal: (listing: Listing) => void; } const ListingCard = ({ listing, favListingsIds, updateFavorite, openModal }: ListingCardProps) => { @@ -129,7 +129,7 @@ const ListingCard = ({ listing, favListingsIds, updateFavorite, openModal }: Lis const handleListingClick = () => { if (!viewed) { - axios.put(`newListings/${listing.id}/addView`, {withCredentials: true}).catch((error) => { + axios.put(`listings/${listing.id}/addView`, {withCredentials: true}).catch((error) => { console.log('Could not add view for listing'); listing.views = listing.views - 1; }) diff --git a/client/src/components/home/ListingModal.tsx b/client/src/components/home/ListingModal.tsx index c0c9f8b..e4a5ef5 100644 --- a/client/src/components/home/ListingModal.tsx +++ b/client/src/components/home/ListingModal.tsx @@ -1,12 +1,12 @@ import React, { useEffect, useState, useContext } from 'react'; -import { NewListing } from '../../types/types'; +import { Listing } from '../../types/types'; import { departmentCategories } from '../../utils/departmentNames'; import UserContext from '../../contexts/UserContext'; interface ListingModalProps { isOpen: boolean; onClose: () => void; - listing: NewListing; + listing: Listing; favListingsIds: string[]; updateFavorite: (listingId: string, favorite: boolean) => void; } diff --git a/client/src/components/home/ListingsCardList.tsx b/client/src/components/home/ListingsCardList.tsx index e2d012e..c1d3340 100644 --- a/client/src/components/home/ListingsCardList.tsx +++ b/client/src/components/home/ListingsCardList.tsx @@ -3,13 +3,13 @@ import ListingsModal from '../home/ListingModal'; import ListingCard from './ListingCard'; import SortDropdown from './SortDropdown'; import PulseLoader from "react-spinners/PulseLoader"; -import { NewListing } from '../../types/types'; +import { Listing } from '../../types/types'; type ListingsCardListProps = { loading: Boolean; searchExhausted: Boolean; setPage: React.Dispatch>; - listings: NewListing[]; + listings: Listing[]; sortableKeys: string[]; sortBy: string; setSortBy: (sortBy: string) => void; @@ -73,7 +73,7 @@ export default function ListingsCardList({ {value: 'title', label: 'Sort by: Lab Title'} ]; - const openModalForListing = (listing: NewListing) => { + const openModalForListing = (listing: Listing) => { setSelectedListingId(listing.id); setModalOpen(true); }; diff --git a/client/src/components/home/SearchHub.tsx b/client/src/components/home/SearchHub.tsx index e7c1c75..17c81bb 100644 --- a/client/src/components/home/SearchHub.tsx +++ b/client/src/components/home/SearchHub.tsx @@ -1,5 +1,5 @@ import React, { useState, useRef, KeyboardEvent, useEffect } from 'react'; -import { NewListing } from '../../types/types'; +import { Listing } from '../../types/types'; import axios from 'axios'; import swal from 'sweetalert'; import { createListing } from '../../utils/apiCleaner'; @@ -8,8 +8,8 @@ import SortDropdown from './SortDropdown'; interface SearchHubProps { allDepartments: string[]; - resetListings: (newListings: NewListing[]) => void; - addListings: (newListings: NewListing[]) => void; + resetListings: (listings: Listing[]) => void; + addListings: (listings: Listing[]) => void; setIsLoading: React.Dispatch>; sortBy: string; sortOrder: number; @@ -203,11 +203,11 @@ const SearchHub = ({ if (sortBy === 'default') { url = backendBaseURL + - `/newListings/search?query=${formattedQuery}&page=${page}&pageSize=${pageSize}`; + `/listings/search?query=${formattedQuery}&page=${page}&pageSize=${pageSize}`; } else { url = backendBaseURL + - `/newListings/search?query=${formattedQuery}&sortBy=${sortBy}&sortOrder=${sortOrder}&page=${page}&pageSize=${pageSize}`; + `/listings/search?query=${formattedQuery}&sortBy=${sortBy}&sortOrder=${sortOrder}&page=${page}&pageSize=${pageSize}`; } if (formattedDepartments) { @@ -219,7 +219,7 @@ const SearchHub = ({ axios .get(url, { withCredentials: true }) .then((response) => { - const responseListings: NewListing[] = response.data.results.map(function ( + const responseListings: Listing[] = response.data.results.map(function ( elem: any ) { return createListing(elem); diff --git a/client/src/pages/account.tsx b/client/src/pages/account.tsx index 82fe57e..e56ecb1 100644 --- a/client/src/pages/account.tsx +++ b/client/src/pages/account.tsx @@ -1,5 +1,5 @@ import {useState, useEffect} from "react"; -import {NewListing} from '../types/types' +import {Listing} from '../types/types' import {createListing} from '../utils/apiCleaner'; import ListingCard from '../components/accounts/ListingCard' import ListingModal from "../components/accounts/ListingModal"; @@ -13,14 +13,14 @@ import CreateButton from "../components/accounts/CreateButton"; import YoutubeVideo from "../components/accounts/YoutubeVideo"; const Account = () => { - const [ownListings, setOwnListings] = useState([]); - const [favListings, setFavListings] = useState([]); + const [ownListings, setOwnListings] = useState([]); + const [favListings, setFavListings] = useState([]); const [favListingsIds, setFavListingsIds] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const [isEditing, setIsEditing] = useState(false); const [isCreating, setIsCreating] = useState(false); - const [selectedListing, setSelectedListing] = useState(null); + const [selectedListing, setSelectedListing] = useState(null); const {user} = useContext(UserContext); useEffect(() => { @@ -51,10 +51,10 @@ const Account = () => { setIsLoading(true); await axios.get('/users/listings', {withCredentials: true}).then((response) => { - const responseOwnListings : NewListing[] = response.data.ownListings.map(function(elem: any){ + const responseOwnListings : Listing[] = response.data.ownListings.map(function(elem: any){ return createListing(elem); }) - const responseFavListings : NewListing[] = response.data.favListings.map(function(elem: any){ + const responseFavListings : Listing[] = response.data.favListings.map(function(elem: any){ return createListing(elem); }) setOwnListings(responseOwnListings); @@ -87,7 +87,7 @@ const Account = () => { }; // Function to open modal with a specific listing - const openModal = (listing: NewListing) => { + const openModal = (listing: Listing) => { setSelectedListing(listing); setIsModalOpen(true); }; @@ -98,7 +98,7 @@ const Account = () => { setSelectedListing(null); }; - const updateFavorite = (listing: NewListing, listingId: string, favorite: boolean) => { + const updateFavorite = (listing: Listing, listingId: string, favorite: boolean) => { const prevFavListings = favListings; const prevFavListingsIds = favListingsIds; @@ -133,41 +133,18 @@ const Account = () => { } }; - const updateListing = (newListing: NewListing) => { - setOwnListings((prevOwnListings) => prevOwnListings.map((listing) => listing.id === newListing.id ? newListing : listing)); - setFavListings((prevFavListings) => prevFavListings.map((listing) => listing.id === newListing.id ? newListing : listing)); + const updateListing = (listing: Listing) => { + setOwnListings((prevOwnListings) => prevOwnListings.map((listing) => listing.id === listing.id ? listing : listing)); + setFavListings((prevFavListings) => prevFavListings.map((listing) => listing.id === listing.id ? listing : listing)); }; - const filterHiddenListings = (listings: NewListing[]) => { + const filterHiddenListings = (listings: Listing[]) => { return listings.filter((listing) => listing.confirmed && !listing.archived); } - const postListing = async (newListing: NewListing) => { + const postListing = (listing: Listing) => { setIsLoading(true); - axios.put(`/newListings/${newListing.id}`, {withCredentials: true, data: newListing}).then((response) => { - reloadListings(); - }).catch((error) => { - console.error('Error saving listing:', error); - - if(error.response.data.incorrectPermissions) { - swal({ - text: "You no longer have permission to edit this listing", - icon: "warning", - }) - reloadListings(); - } else { - swal({ - text: "Unable to update listing", - icon: "warning", - }) - reloadListings(); - } - }); - } - - const postNewListing = (listing: NewListing) => { - setIsLoading(true); - axios.post('/newListings', {withCredentials: true, data: listing}).then((response) => { + axios.post('/listings', {withCredentials: true, data: listing}).then((response) => { reloadListings(); setIsEditing(false); setIsLoading(false); @@ -192,9 +169,9 @@ const Account = () => { setIsCreating(false); }; - const deleteListing = (listing: NewListing) => { + const deleteListing = (listing: Listing) => { setIsLoading(true); - axios.delete(`/newListings/${listing.id}`, {withCredentials: true}).then((response) => { + axios.delete(`/listings/${listing.id}`, {withCredentials: true}).then((response) => { reloadListings(); setIsLoading(false); }).catch((error) => { @@ -210,7 +187,7 @@ const Account = () => { }; const onCreate = () => { - axios.get('/newListings/skeleton', {withCredentials: true}).then((response) => { + axios.get('/listings/skeleton', {withCredentials: true}).then((response) => { const skeletonListing = createListing(response.data.listing); setOwnListings((prevOwnListings) => [...prevOwnListings, skeletonListing]); @@ -252,7 +229,6 @@ const Account = () => { updateFavorite={updateFavorite} updateListing={updateListing} postListing={postListing} - postNewListing={postNewListing} clearCreatedListing={clearCreatedListing} deleteListing={deleteListing} openModal={openModal} @@ -281,7 +257,6 @@ const Account = () => { updateFavorite={updateFavorite} updateListing={updateListing} postListing={postListing} - postNewListing={postNewListing} clearCreatedListing={clearCreatedListing} deleteListing={deleteListing} openModal={openModal} diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index e2efe54..191e743 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -5,14 +5,14 @@ import { departmentCategories } from "../utils/departmentNames"; import axios from "../utils/axios"; import styled from "styled-components"; -import {NewListing} from '../types/types'; +import {Listing} from '../types/types'; import swal from "sweetalert"; // Remove all archived from search results on backend const Home = () => { - const [listings, setListings] = useState([]); + const [listings, setListings] = useState([]); const [isLoading, setIsLoading] = useState(false); const [searchExhausted, setSearchExhausted] = useState(false); const [page, setPage] = useState(1); @@ -51,14 +51,14 @@ const Home = () => { reloadFavorites(); }, []); - const addListings = (newListings: NewListing[]) => { - setListings((oldListings) => [...oldListings, ...newListings]); - setSearchExhausted(newListings.length < pageSize); + const addListings = (listings: Listing[]) => { + setListings((oldListings) => [...oldListings, ...listings]); + setSearchExhausted(listings.length < pageSize); }; - const resetListings = (newListings: NewListing[]) => { - setListings(newListings); - setSearchExhausted(newListings.length < pageSize); + const resetListings = (listings: Listing[]) => { + setListings(listings); + setSearchExhausted(listings.length < pageSize); }; const updateFavorite = (listingId: string, favorite: boolean) => { diff --git a/client/src/types/types.tsx b/client/src/types/types.tsx index b7938eb..a671a25 100644 --- a/client/src/types/types.tsx +++ b/client/src/types/types.tsx @@ -1,6 +1,6 @@ //Listings -export type NewListing = { +export type Listing = { id: string; ownerId: string; ownerFirstName: string; @@ -36,16 +36,6 @@ export type UserData = { userConfirmed: boolean; } -export type Listing = { - id: number; - departments: string; - email: string; - website: string; - description: string; - keywords: string; - lastUpdated: string; - name: string; -}; //Developer diff --git a/server/src/controllers/listingController.ts b/server/src/controllers/listingController.ts new file mode 100644 index 0000000..3614a22 --- /dev/null +++ b/server/src/controllers/listingController.ts @@ -0,0 +1,178 @@ +import { Request, Response } from "express"; +import mongoose from 'mongoose'; +import { + archiveListing, + createListing, + deleteListing, + readListing, + unarchiveListing, + updateListing, + getSkeletonListing, + addView +} from '../services/listingService'; +import { readUser } from '../services/userService'; +import { Listing } from "../models"; + +export const searchListings = async (request: Request, response: Response) => { + try { + const { query, sortBy, sortOrder, departments, page = 1, pageSize = 10 } = request.query; + + const order = (sortBy === "updatedAt" || sortBy === "createdAt") + ? sortOrder === "1" ? -1 : 1 + : sortOrder === "1" ? 1 : -1; + + const pipeline: mongoose.PipelineStage[] = []; + + if (query) { + pipeline.push({ + $search: { + index: 'default', + text: { + query: query as string, + path: { + wildcard: '*' + }, + }, + }, + }); + + pipeline.push({ + $set: { + searchScore: { $meta: 'searchScore' }, + }, + }); + } + + if (departments) { + const departmentList = (departments as string).split(','); + + pipeline.push({ + $match: { + departments: { $in: departmentList }, + }, + }); + } + + // Filter out archived and unconfirmed listings + pipeline.push({ + $match: { + archived: false, + confirmed: true + }, + }); + + pipeline.push({ + $sort: sortBy + ? { [sortBy as string]: order, _id: 1 } + : { searchScore: -1, updatedAt: -1, _id: 1 }, + }); + + pipeline.push( + { $skip: (Number(page) - 1) * Number(pageSize) }, + { $limit: Number(pageSize) } + ); + + const results = await Listing.aggregate(pipeline); + + response.json({ results, page: Number(page), pageSize: Number(pageSize) }); + } catch (error) { + console.error("Error executing search:", error); + response.status(500).json({ error: "Internal server error" }); + } +}; + +export const createListingForCurrentUser = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const user = await readUser(currentUser.netId); + const listing = await createListing(request.body.data, user); + response.status(201).json({ listing }); + } catch (error) { + console.log(error.message); + response.status(400).json({ error: error.message }); + } +}; + +export const getSkeletonListingForCurrentUser = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const listing = await getSkeletonListing(currentUser.netId); + response.status(201).json({ listing }); + } catch (error) { + console.log(error.message); + response.status(400).json({ error: error.message }); + } +}; + +export const getListingById = async (request: Request, response: Response) => { + try { + const listing = await readListing(request.params.id); + response.status(200).json({ listing }); + } catch (error) { + throw error; + } +}; + +export const updateListingForCurrentUser = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const listing = await updateListing(request.params.id, currentUser.netId, request.body.data); + response.status(200).json({ listing }); + } catch (error) { + throw error; + } +}; + +export const archiveListingForCurrentUser = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const listing = await archiveListing(request.params.id, currentUser.netId); + response.status(200).json({ listing }); + } catch (error) { + throw error; + } +}; + +export const unarchiveListingForCurrentUser = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const listing = await unarchiveListing(request.params.id, currentUser.netId); + response.status(200).json({ listing }); + } catch (error) { + throw error; + } +}; + +export const addViewToListing = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const listing = await addView(request.params.id, currentUser.netId); + response.status(200).json({ listing }); + } catch (error) { + throw error; + } +}; + +export const deleteListingForCurrentUser = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const currentListing = await readListing(request.params.id); + if (currentUser.netId !== currentListing.ownerId) { + const error: any = new Error(`User with id ${currentUser.netId} does not have permission to delete listing with id ${request.params.id}`); + error.status = 403; + throw error; + } + + const deletedListing = await deleteListing(request.params.id); + response.status(200).json({ deletedListing }); + } catch (error) { + throw error; + } +}; \ No newline at end of file diff --git a/server/src/controllers/userController.ts b/server/src/controllers/userController.ts new file mode 100644 index 0000000..e51a1bb --- /dev/null +++ b/server/src/controllers/userController.ts @@ -0,0 +1,333 @@ +import { Request, Response } from "express"; +import mongoose from 'mongoose'; +import { readListings } from '../services/listingService'; +import { + createUser as createUserService, + readAllUsers, + readUser, + updateUser, + deleteUser as deleteUserService, + addDepartments as addDepartmentsService, + deleteDepartments as deleteDepartmentsService, + clearDepartments as clearDepartmentsService, + addOwnListings as addOwnListingsService, + deleteOwnListings as deleteOwnListingsService, + clearOwnListings as clearOwnListingsService, + addFavListings as addFavListingsService, + deleteFavListings as deleteFavListingsService, + clearFavListings as clearFavListingsService, + confirmUser, + unconfirmUser +} from '../services/userService'; + +// ==================== ADMIN ROUTES (COMMENTED) ==================== + +// // Confirm user and update listings +// export const confirmUserById = async (request: Request, response: Response) => { +// try { +// const user = await confirmUser(request.params.id); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// // Unconfirm user and update listings +// export const unconfirmUserById = async (request: Request, response: Response) => { +// try { +// const user = await unconfirmUser(request.params.id); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// // Add departments by ObjectId or NetId +// export const addDepartments = async (request: Request, response: Response) => { +// try { +// const departmentsArray = Array.isArray(request.body.departments) +// ? request.body.departments +// : [request.body.departments]; +// +// const user = await addDepartmentsService(request.params.id, departmentsArray); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// // Remove departments by ObjectId or NetId +// export const removeDepartments = async (request: Request, response: Response) => { +// try { +// const departmentsArray = Array.isArray(request.body.departments) +// ? request.body.departments +// : [request.body.departments]; +// +// const user = await deleteDepartmentsService(request.params.id, departmentsArray); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// // Clear departments by ObjectId or NetId +// export const clearDepartments = async (request: Request, response: Response) => { +// try { +// const user = await clearDepartmentsService(request.params.id); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// // Add ownListings by ObjectId or NetId +// export const addOwnListings = async (request: Request, response: Response) => { +// try { +// const ownListingsArray = Array.isArray(request.body.ownListings) +// ? request.body.ownListings +// : [request.body.ownListings]; +// +// const user = await addOwnListingsService(request.params.id, ownListingsArray); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// // Remove ownListings by ObjectId or NetId +// export const removeOwnListings = async (request: Request, response: Response) => { +// try { +// const ownListingsArray = Array.isArray(request.body.ownListings) +// ? request.body.ownListings +// : [request.body.ownListings]; +// +// const user = await deleteOwnListingsService(request.params.id, ownListingsArray); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// // Clear ownListings by ObjectId or NetId +// export const clearOwnListings = async (request: Request, response: Response) => { +// try { +// const user = await clearOwnListingsService(request.params.id); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// ==================== FAV LISTINGS ROUTES ==================== + +// Get favListings id's for current user +export const getFavListingsIds = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const user = await readUser(currentUser.netId); + response.status(200).json({ favListingsIds: user.favListings }); + } catch (error) { + throw error; + } +}; + +// // Add favListings by ObjectId or NetId (Admin) +// export const addFavListingsByUserId = async (request: Request, response: Response) => { +// try { +// const favListingsArray = Array.isArray(request.body.favListings) +// ? request.body.favListings +// : [request.body.favListings]; +// +// const user = await addFavListingsService(request.params.id, favListingsArray); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// Add favListings for the user currently logged in +export const addFavListings = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + if (!request.body.data.favListings) { + const error: any = new Error('No favListings provided'); + error.status = 400; + throw error; + } + + const favListingsArray = Array.isArray(request.body.data.favListings) + ? request.body.data.favListings + : [request.body.data.favListings]; + + const user = await addFavListingsService(currentUser.netId, favListingsArray); + response.status(200).json({ user }); + } catch (error) { + throw error; + } +}; + +// // Remove favListings by ObjectId or NetId (Admin) +// export const removeFavListingsByUserId = async (request: Request, response: Response) => { +// try { +// const favListingsArray = Array.isArray(request.body.favListings) +// ? request.body.favListings +// : [request.body.favListings]; +// +// const user = await deleteFavListingsService(request.params.id, favListingsArray); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// Remove favListings for the user currently logged in +export const removeFavListings = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + if (!request.body.favListings) { + const error: any = new Error('No favListings provided'); + error.status = 400; + throw error; + } + + console.log(request.body); + + const favListingsArray = Array.isArray(request.body.favListings) + ? request.body.favListings + : [request.body.favListings]; + + const user = await deleteFavListingsService(currentUser.netId, favListingsArray); + response.status(200).json({ user }); + } catch (error) { + throw error; + } +}; + +// // Clear favListings by ObjectId or NetId (Admin) +// export const clearFavListings = async (request: Request, response: Response) => { +// try { +// const user = await clearFavListingsService(request.params.id); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// ==================== USER CRUD ROUTES (ADMIN - COMMENTED) ==================== + +// // Create new user +// export const createUser = async (request: Request, response: Response) => { +// try { +// const user = await createUserService(request.body); +// response.status(201).json({ user }); +// } catch (error) { +// console.log(error.message); +// response.status(400).json({ error: error.message }); +// } +// }; + +// // Read all users +// export const getAllUsers = async (request: Request, response: Response) => { +// try { +// const users = await readAllUsers(); +// response.status(200).json({ users }); +// } catch (error) { +// throw error; +// } +// }; + +// // Return all listings data for a specific user by ObjectId or NetId +// export const getUserListingsById = async (request: Request, response: Response) => { +// try { +// const user = await readUser(request.params.id); +// const ownListings = await readListings(user.ownListings); +// const favListings = await readListings(user.favListings); +// response.status(200).json({ ownListings, favListings }); +// } catch (error) { +// throw error; +// } +// }; + +// ==================== USER PROFILE ROUTES (CURRENT USER) ==================== + +// Return all listings data for the user currently logged in (for reload on accounts page, so also returns relevant user data) +export const getUserListings = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + const user = await readUser(currentUser.netId); + const ownListings = await readListings(user.ownListings); + const favListings = await readListings(user.favListings); + + // Clean listings to remove those that no longer exist + let ownIds: mongoose.Types.ObjectId[] = []; + for (const listing of ownListings) { + ownIds.push(listing._id); + } + + let favIds: mongoose.Types.ObjectId[] = []; + for (const listing of favListings) { + favIds.push(listing._id); + } + + await updateUser(currentUser.netId, { ownListings: ownIds, favListings: favIds }); + + response.status(200).json({ ownListings: ownListings, favListings: favListings }); + } catch (error) { + throw error; + } +}; + +// // Read specific user by ObjectId or NetId +// export const getUserById = async (request: Request, response: Response) => { +// try { +// const user = await readUser(request.params.id); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// // Update data for a specific user by ObjectId or NetId +// export const updateUserById = async (request: Request, response: Response) => { +// try { +// const user = await updateUser(request.params.id, request.body); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; + +// Update data for user currently logged in +export const updateCurrentUser = async (request: Request, response: Response) => { + try { + const currentUser = request.user as { netId?: string, userType: string, userConfirmed: boolean }; + + // Handle user confirmation status + if (request.body.data.userConfirmed !== undefined) { + if (request.body.data.userConfirmed) { + await confirmUser(currentUser.netId); + } else { + await unconfirmUser(currentUser.netId); + } + } + + const user = await updateUser(currentUser.netId, request.body.data); + response.status(200).json({ user }); + } catch (error) { + throw error; + } +}; + +// // Delete user by ObjectId or NetId +// export const deleteUserById = async (request: Request, response: Response) => { +// try { +// const user = await deleteUserService(request.params.id); +// response.status(200).json({ user }); +// } catch (error) { +// throw error; +// } +// }; \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index d60b5b9..2153a8c 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -15,6 +15,7 @@ const startApp = async () => { if (process.env.MONGODBURL_TEST && (process.env.API_MODE == 'test')) { console.log("Using test MongoDB database 🔬") } else { + console.log(mongoUri) console.log("Using production MongoDB database 🚀") } }); diff --git a/server/src/middleware/auth.ts b/server/src/middleware/auth.ts new file mode 100644 index 0000000..061efb9 --- /dev/null +++ b/server/src/middleware/auth.ts @@ -0,0 +1,92 @@ +import express from "express"; + +/** + * Middleware to check if user is authenticated + */ +export const isAuthenticated = (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (req.user) { + return next(); + } + res.status(401).json({ error: "Unauthorized" }); +}; + +/** + * Middleware to check if user is trustworthy (confirmed admin/professor/faculty) + */ +export const isTrustworthy = (req: express.Request, res: express.Response, next: express.NextFunction) => { + const user = req.user as { netId?: string, userType?: string, userConfirmed?: boolean }; + + if (user && user.userConfirmed && (user.userType === "admin" || user.userType === "professor" || user.userType === "faculty")) { + return next(); + } + res.status(403).json({ error: "Forbidden" }); +}; + +/** + * Middleware to check if user has permission to create listings + */ +export const canCreateListing = (req: express.Request, res: express.Response, next: express.NextFunction) => { + const currentUser = req.user as { netId?: string, userType?: string, userConfirmed?: boolean }; + + if (!currentUser) { + return res.status(401).json({ error: 'Unauthorized' }); + } + + const allowedTypes = ['admin', 'professor', 'faculty']; + if (!allowedTypes.includes(currentUser.userType)) { + return res.status(403).json({ error: 'User does not have permission to create listings' }); + } + + next(); +}; + +/** + * Middleware to check if user is an admin + */ +export const isAdmin = (req: express.Request, res: express.Response, next: express.NextFunction) => { + const currentUser = req.user as { netId?: string, userType?: string, userConfirmed?: boolean }; + + if (!currentUser) { + return res.status(401).json({ error: 'Unauthorized' }); + } + + if (currentUser.userType !== 'admin') { + return res.status(403).json({ error: 'Admin privileges required' }); + } + + next(); +}; + +/** + * Middleware to check if user is a professor + */ +export const isProfessor = (req: express.Request, res: express.Response, next: express.NextFunction) => { + const currentUser = req.user as { netId?: string, userType?: string, userConfirmed?: boolean }; + + if (!currentUser) { + return res.status(401).json({ error: 'Unauthorized' }); + } + + if (currentUser.userType !== 'professor' && currentUser.userType !== 'admin') { + return res.status(403).json({ error: 'Professor privileges required' }); + } + + next(); +}; + +/** + * Middleware to check if user account is confirmed + */ +export const isConfirmed = (req: express.Request, res: express.Response, next: express.NextFunction) => { + const currentUser = req.user as { netId?: string, userType?: string, userConfirmed?: boolean }; + + if (!currentUser) { + return res.status(401).json({ error: 'Unauthorized' }); + } + + if (!currentUser.userConfirmed) { + return res.status(403).json({ error: 'Account must be confirmed' }); + } + + next(); +}; \ No newline at end of file diff --git a/server/src/middleware/errorHandler.ts b/server/src/middleware/errorHandler.ts new file mode 100644 index 0000000..74e84f7 --- /dev/null +++ b/server/src/middleware/errorHandler.ts @@ -0,0 +1,78 @@ +import { Request, Response, NextFunction } from 'express'; +import { NotFoundError, ObjectIdError, IncorrectPermissionsError } from '../utils/errors'; + +/** + * Global error handler middleware + * This should be added LAST in your middleware chain + */ +export const errorHandler = ( + error: Error, + req: Request, + res: Response, + next: NextFunction +) => { + console.error('Error:', error.message); + console.error('Stack:', error.stack); + + // Handle custom errors + if (error instanceof NotFoundError) { + return res.status(error.status).json({ error: error.message }); + } + + if (error instanceof ObjectIdError) { + return res.status(error.status).json({ error: error.message }); + } + + if (error instanceof IncorrectPermissionsError) { + return res.status(error.status).json({ + error: error.message, + incorrectPermissions: true + }); + } + + // Handle Mongoose validation errors + if (error.name === 'ValidationError') { + return res.status(400).json({ + error: 'Validation error', + details: error.message + }); + } + + // Handle Mongoose cast errors (invalid ObjectId format) + if (error.name === 'CastError') { + return res.status(400).json({ error: 'Invalid ID format' }); + } + + // Handle duplicate key errors + if (error.name === 'MongoServerError' && (error as any).code === 11000) { + return res.status(409).json({ error: 'Duplicate key error' }); + } + + // Default to 500 server error + res.status(500).json({ + error: 'Internal server error', + message: process.env.NODE_ENV === 'development' ? error.message : undefined + }); +}; + +/** + * 404 Not Found handler + * This should be added after all routes but before errorHandler + */ +export const notFoundHandler = (req: Request, res: Response, next: NextFunction) => { + res.status(404).json({ + error: 'Not found', + path: req.path + }); +}; + +/** + * Async error wrapper + * Wraps async route handlers to catch errors and pass to error handler + * Usage: router.get('/', asyncHandler(async (req, res) => {...})) + */ +export const asyncHandler = (fn: Function) => { + return (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; +}; \ No newline at end of file diff --git a/server/src/middleware/index.ts b/server/src/middleware/index.ts new file mode 100644 index 0000000..14532fb --- /dev/null +++ b/server/src/middleware/index.ts @@ -0,0 +1,27 @@ +// Auth middleware +export { + isAuthenticated, + isTrustworthy, + canCreateListing, + isAdmin, + isProfessor, + isConfirmed +} from './auth'; + +// Validation middleware +export { + validateObjectId, + requireBody, + requireFields, + validatePagination, + validateSort, + validateQuery +} from './validation'; + +// Error handling middleware +export { + errorHandler, + notFoundHandler, + asyncHandler +} from './errorHandler'; + diff --git a/server/src/middleware/validation.ts b/server/src/middleware/validation.ts new file mode 100644 index 0000000..b7bfdf3 --- /dev/null +++ b/server/src/middleware/validation.ts @@ -0,0 +1,108 @@ +import { Request, Response, NextFunction } from 'express'; +import mongoose from 'mongoose'; + +/** + * Middleware to validate MongoDB ObjectId parameters + * Usage: validateObjectId('id') or validateObjectId() for default 'id' param + */ +export const validateObjectId = (paramName: string = 'id') => { + return (req: Request, res: Response, next: NextFunction) => { + const id = req.params[paramName]; + + if (!id) { + return res.status(400).json({ error: `Missing required parameter: ${paramName}` }); + } + + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ error: `Invalid ${paramName}: ${id}` }); + } + + next(); + }; +}; + +/** + * Middleware to validate request body exists + */ +export const requireBody = (req: Request, res: Response, next: NextFunction) => { + if (!req.body || Object.keys(req.body).length === 0) { + return res.status(400).json({ error: 'Request body is required' }); + } + next(); +}; + +/** + * Middleware to validate specific fields exist in request body + * Usage: requireFields(['name', 'email']) + */ +export const requireFields = (fields: string[]) => { + return (req: Request, res: Response, next: NextFunction) => { + const missingFields = fields.filter(field => !(field in req.body)); + + if (missingFields.length > 0) { + return res.status(400).json({ + error: `Missing required fields: ${missingFields.join(', ')}` + }); + } + + next(); + }; +}; + +/** + * Middleware to validate pagination parameters + */ +export const validatePagination = (req: Request, res: Response, next: NextFunction) => { + const { page, pageSize } = req.query; + + if (page && (isNaN(Number(page)) || Number(page) < 1)) { + return res.status(400).json({ error: 'Invalid page number (must be >= 1)' }); + } + + if (pageSize && (isNaN(Number(pageSize)) || Number(pageSize) < 1 || Number(pageSize) > 100)) { + return res.status(400).json({ error: 'Invalid page size (must be between 1 and 100)' }); + } + + next(); +}; + +/** + * Middleware to validate sort parameters + */ +export const validateSort = (allowedFields: string[]) => { + return (req: Request, res: Response, next: NextFunction) => { + const { sortBy, sortOrder } = req.query; + + if (sortBy && !allowedFields.includes(sortBy as string)) { + return res.status(400).json({ + error: `Invalid sortBy field. Allowed: ${allowedFields.join(', ')}` + }); + } + + if (sortOrder && sortOrder !== '1' && sortOrder !== '-1') { + return res.status(400).json({ + error: 'Invalid sortOrder. Must be "1" (ascending) or "-1" (descending)' + }); + } + + next(); + }; +}; + +/** + * Middleware to validate query string parameters + */ +export const validateQuery = (allowedParams: string[]) => { + return (req: Request, res: Response, next: NextFunction) => { + const queryParams = Object.keys(req.query); + const invalidParams = queryParams.filter(param => !allowedParams.includes(param)); + + if (invalidParams.length > 0) { + return res.status(400).json({ + error: `Invalid query parameters: ${invalidParams.join(', ')}` + }); + } + + next(); + }; +}; \ No newline at end of file diff --git a/server/src/models/ListingBackup.ts b/server/src/models/ListingBackup.ts deleted file mode 100644 index f135721..0000000 --- a/server/src/models/ListingBackup.ts +++ /dev/null @@ -1,39 +0,0 @@ -import mongoose from 'mongoose'; - -const listingBackupSchema = new mongoose.Schema( - { - professorIds: { - type: [String], - required: true, - }, - professorNames: { - type: [String], - required: true - }, - departments: { - type: [String], - required: true, - }, - emails: { - type: [String], - required: true, - }, - websites: { - type: [String], - required: false, - }, - description: { - type: String, - required: false, - }, - keywords: { - type: [String], - required: false, - } - }, - { - timestamps: true, - } -); - -export const ListingBackup = mongoose.model('listing_backups', listingBackupSchema); \ No newline at end of file diff --git a/server/src/models/UserBackup.ts b/server/src/models/UserBackup.ts deleted file mode 100644 index 85ccd30..0000000 --- a/server/src/models/UserBackup.ts +++ /dev/null @@ -1,50 +0,0 @@ -import mongoose from 'mongoose'; - -const userBackupSchema = new mongoose.Schema( - { - netid: { - type: String, - required: true, - unique: true - }, - email: { - type: String, - required: true, - }, - isProfessor: { - type: Boolean, - default: false, - }, - fname: { - type: String, - required: true, - }, - lname: { - type: String, - required: true, - }, - website: { - type: String, - }, - bio: { - type: String, - }, - departments: { - type: [String], - default: [], - }, - ownListings: { - type: [mongoose.Schema.ObjectId], - default: [], - }, - favListings: { - type: [mongoose.Schema.ObjectId], - default: [], - } - }, - { - timestamps: true, - } -); - -export const UserBackup = mongoose.model('user_backups', userBackupSchema); \ No newline at end of file diff --git a/server/src/models/index.ts b/server/src/models/index.ts index 380435e..6a54f55 100644 --- a/server/src/models/index.ts +++ b/server/src/models/index.ts @@ -1,4 +1,2 @@ -export * from "./User"; -export * from "./UserBackup"; -export * from "./NewListing"; -export * from "./ListingBackup"; \ No newline at end of file +export * from "./user"; +export * from "./listing"; \ No newline at end of file diff --git a/server/src/models/NewListing.ts b/server/src/models/listing.ts similarity index 92% rename from server/src/models/NewListing.ts rename to server/src/models/listing.ts index cb9a1a4..1b1aa7a 100644 --- a/server/src/models/NewListing.ts +++ b/server/src/models/listing.ts @@ -2,7 +2,7 @@ import mongoose from 'mongoose'; const arrayNotEmpty = (arr: any[]) => Array.isArray(arr) && arr.length > 0; -const newListingSchema = new mongoose.Schema( +const listingSchema = new mongoose.Schema( { ownerId: { type: String, @@ -82,4 +82,4 @@ const newListingSchema = new mongoose.Schema( } ); -export const NewListing = mongoose.model('newListings', newListingSchema); \ No newline at end of file +export const Listing = mongoose.model('listings', listingSchema); \ No newline at end of file diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts index 2c5dcdd..22716a6 100644 --- a/server/src/routes/index.ts +++ b/server/src/routes/index.ts @@ -1,12 +1,10 @@ import { Router } from "express"; import UsersRoutes from "./users"; -import UserBackupsRoutes from "./userBackups"; -import NewListingsRoutes from "./newListings"; +import ListingsRoutes from "./listings"; const router = Router(); -router.use("/newListings", NewListingsRoutes); +router.use("/listings", ListingsRoutes); router.use("/users", UsersRoutes); -router.use("/userBackups", UserBackupsRoutes); export default router; \ No newline at end of file diff --git a/server/src/routes/listings.ts b/server/src/routes/listings.ts new file mode 100644 index 0000000..5194fc6 --- /dev/null +++ b/server/src/routes/listings.ts @@ -0,0 +1,34 @@ +import { Router } from "express"; +import { isAuthenticated, canCreateListing, validateObjectId, validatePagination } from '../middleware'; +import * as listingController from '../controllers/listingController'; + +const router = Router(); + +// Search listings +router.get('/search', isAuthenticated, validatePagination, listingController.searchListings); + +// Create listing +router.post("/", isAuthenticated, canCreateListing, listingController.createListingForCurrentUser); + +// Get skeleton listing +router.get('/skeleton', isAuthenticated, listingController.getSkeletonListingForCurrentUser); + +// Read specific listing +router.get('/:id', isAuthenticated, validateObjectId('id'), listingController.getListingById); + +// Update listing +router.put('/:id', isAuthenticated, validateObjectId('id'), listingController.updateListingForCurrentUser); + +// Archive listing +router.put('/:id/archive', isAuthenticated, validateObjectId('id'), listingController.archiveListingForCurrentUser); + +// Unarchive listing +router.put('/:id/unarchive', isAuthenticated, validateObjectId('id'), listingController.unarchiveListingForCurrentUser); + +// Add view to listing +router.put('/:id/addView', isAuthenticated, validateObjectId('id'), listingController.addViewToListing); + +// Delete listing +router.delete('/:id', isAuthenticated, validateObjectId('id'), listingController.deleteListingForCurrentUser); + +export default router; \ No newline at end of file diff --git a/server/src/routes/newListings.ts b/server/src/routes/newListings.ts deleted file mode 100644 index 8468df7..0000000 --- a/server/src/routes/newListings.ts +++ /dev/null @@ -1,339 +0,0 @@ -import { archiveListing, createListing, deleteListing, readAllListings, readListing, unarchiveListing, updateListing, getSkeletonListing, addView } from '../services/newListingsService'; -import { Request, Response, Router } from "express"; -import { IncorrectPermissionsError, NotFoundError, ObjectIdError } from "../utils/errors"; -import { readUser } from '../services/userService'; -import { NewListing } from '../models'; -import mongoose from 'mongoose'; -import { isAuthenticated, isTrustworthy } from '../utils/permissions'; - -const router = Router(); - -router.get('/search', isAuthenticated, async (request: Request, response: Response) => { - try { - const { query, sortBy, sortOrder, departments, page = 1, pageSize = 10 } = request.query; - - const order = (sortBy === "updatedAt" || sortBy === "createdAt") ? sortOrder === "1" ? -1: 1 : sortOrder === "1" ? 1: -1; - - const pipeline: mongoose.PipelineStage[] = []; - - if (query) { - pipeline.push({ - $search: { - index: 'default', - text: { - query: query as string, - path: { - wildcard: '*' - }, - }, - }, - }); - - pipeline.push({ - $set: { - searchScore: { $meta: 'searchScore' }, - }, - }); - } - - if (departments) { - const departmentList = (departments as string).split(','); - `` - pipeline.push({ - $match: { - departments: { $in: departmentList }, - }, - }); - } - - // Filter out archived and unconfirmed listings - pipeline.push({ - $match: { - archived: false, - confirmed: true - }, - }) - - pipeline.push({ - $sort: sortBy ? { [sortBy as string]: order, _id: 1 } : { searchScore: -1, updatedAt: -1, _id: 1 }, - }); - - pipeline.push( - { $skip: (Number(page) - 1) * Number(pageSize) }, - { $limit: Number(pageSize) } - ); - - const results = await NewListing.aggregate(pipeline); - - response.json({ results, page: Number(page), pageSize: Number(pageSize) }); - } catch (error) { - console.error("Error executing search:", error); - response.status(500).json({ error: "Internal server error" }); - } - }); - -//Add listing -router.post("/", isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - if (!(currentUser.userType === 'admin' || currentUser.userType === 'professor' || currentUser.userType === 'faculty')) { - throw new Error('User does not have permission to create listings'); - } - - const user = await readUser(currentUser.netId); - const listing = await createListing(request.body.data, user); - response.status(201).json({ listing }); - } catch (error) { - console.log(error.message); - response.status(400).json({ error: error.message }); - } -}); - -/* -//Add listing for user with specified netid -router.post("/:id", async (request: Request, response: Response) => { - try { - const user = await readUser(request.params.id); - const listing = await createListing(request.body, user); - response.status(201).json({ listing }); - } catch (error) { - console.log(error.message); - response.status(400).json({ error: error.message }); - } - }); -*/ - -//Get a skeleton listing for the current user -router.get('/skeleton', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - const listing = await getSkeletonListing(currentUser.netId); - response.status(201).json({ listing }); - } catch (error) { - console.log(error.message); - response.status(400).json({ error: error.message}) - } -}); - -/* -//Read all listings -router.get("/", async (request: Request, response: Response) => { - try { - const listings = await readAllListings(); - response.status(200).json({ listings }); - } catch (error) { - console.log(error.message); - response.status(500).json({ error: error.message }); - } -}); -*/ - -//Read specific listing by ObjectId -router.get('/:id', isAuthenticated, async (request: Request, response: Response) => { - try { - const listing = await readListing(request.params.id); - response.status(200).json({ listing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Updates for current user - -//Update listing by ObjectId (current user) -router.put('/:id', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - const listing = await updateListing(request.params.id, currentUser.netId, request.body.data); - response.status(200).json({ listing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else if (error instanceof IncorrectPermissionsError) { - response.status(error.status).json({ error: error.message, incorrectPermissions: true }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Archive listing by ObjectId (current user) -router.put('/:id/archive', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - const listing = await archiveListing(request.params.id, currentUser.netId); - response.status(200).json({ listing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else if (error instanceof IncorrectPermissionsError) { - response.status(error.status).json({ error: error.message, incorrectPermissions: true }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Unarchive listing by ObjectId (current user) -router.put('/:id/unarchive', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - const listing = await unarchiveListing(request.params.id, currentUser.netId); - response.status(200).json({ listing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else if (error instanceof IncorrectPermissionsError) { - response.status(error.status).json({ error: error.message, incorrectPermissions: true }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Add view by ObjectId (current user) -router.put('/:id/addView', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - - const listing = await addView(request.params.id, currentUser.netId); - response.status(200).json({ listing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else if (error instanceof IncorrectPermissionsError) { - response.status(error.status).json({ error: error.message, incorrectPermissions: true }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Updates for specific user - -/* -//Update listing by ObjectId (specific user) -router.put('/asUser/:netid/:id', async (request: Request, response: Response) => { - try { - const listing = await updateListing(request.params.id, request.params.netid, request.body); - response.status(200).json({ listing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else if (error instanceof IncorrectPermissionsError) { - response.status(error.status).json({ error: error.message, incorrectPermissions: true }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Archive listing by ObjectId -router.put('/asUser/:netid/:id/archive', async (request: Request, response: Response) => { - try { - const listing = await archiveListing(request.params.id, request.params.netid); - response.status(200).json({ listing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else if (error instanceof IncorrectPermissionsError) { - response.status(error.status).json({ error: error.message, incorrectPermissions: true }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Unarchive listing by ObjectId -router.put('/asUser/:netid/:id/unarchive', async (request: Request, response: Response) => { - try { - const listing = await unarchiveListing(request.params.id, request.params.netid); - response.status(200).json({ listing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else if (error instanceof IncorrectPermissionsError) { - response.status(error.status).json({ error: error.message, incorrectPermissions: true }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Delete listing by ObjectId as another user -router.delete('/asUser/:netid/:id', async (request: Request, response: Response) => { - try { - const currentListing = await readListing(request.params.id); - if (request.params.netid !== currentListing.ownerId) { - throw new IncorrectPermissionsError(`User with id ${request.params.netid} does not have permission to delete listing with id ${request.params.id}`); - } - - const deletedListing = await deleteListing(request.params.id); - response.status(200).json({ deletedListing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); -*/ - -//Delete listing by ObjectId for current user -router.delete('/:id', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - const currentListing = await readListing(request.params.id); - if (currentUser.netId !== currentListing.ownerId) { - throw new IncorrectPermissionsError(`User with id ${currentUser.netId} does not have permission to delete listing with id ${request.params.id}`); - } - - const deletedListing = await deleteListing(request.params.id); - response.status(200).json({ deletedListing }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError || error instanceof ObjectIdError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - - -export default router; \ No newline at end of file diff --git a/server/src/routes/userBackups.ts b/server/src/routes/userBackups.ts deleted file mode 100644 index 9b6fc5d..0000000 --- a/server/src/routes/userBackups.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { createUserBackup, deleteUserBackup, readAllUserBackups, readUserBackup, updateUserBackup } from "../services/userBackupService"; -import { NotFoundError } from "../utils/errors"; -import { Request, Response, Router } from "express"; - -const router = Router(); - -/* -//Create new user backup -router.post("/", async (request: Request, response: Response) => { - try { - const user = await createUserBackup(request.body); - response.status(201).json({ user }); - } catch (error) { - console.log(error.message); - response.status(400).json({ error: error.message }); - } -}); - -//Read all user backups -router.get("/", async (request: Request, response: Response) => { - try { - const users = await readAllUserBackups(); - response.status(200).json({ users }); - } catch (error) { - console.log(error.message); - response.status(500).json({ error: error.message }); - } -}); - -//Read specific user backup by ObjectId or NetId -router.get('/:id', async (request: Request, response: Response) => { - try { - const user = await readUserBackup(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Update data for a specific user backup by ObjectId or NetId -router.put('/:id', async (request: Request, response: Response) => { - try { - const user = await updateUserBackup(request.params.id, request.body); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Delete user backup by ObjectId or NetId -router.delete('/:id', async (request: Request, response: Response) => { - try { - const user = await deleteUserBackup(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -});*/ - -export default router; \ No newline at end of file diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts index cf23781..5eafb4e 100644 --- a/server/src/routes/users.ts +++ b/server/src/routes/users.ts @@ -1,405 +1,69 @@ -import mongoose from 'mongoose'; -import { readListings } from '../services/newListingsService'; -import { createUser, readAllUsers, readUser, updateUser, deleteUser, addDepartments, deleteDepartments, clearDepartments, addOwnListings, deleteOwnListings, clearOwnListings, addFavListings, deleteFavListings, clearFavListings, confirmUser, unconfirmUser } from '../services/userService'; -import { NotFoundError } from "../utils/errors"; -import { Request, Response, Router } from "express"; -import { isAuthenticated } from '../utils/permissions'; +import { Router } from "express"; +import { isAuthenticated, isAdmin} from '../middleware'; +import * as userController from '../controllers/userController'; const router = Router(); +// ==================== ADMIN ROUTES (COMMENTED) ==================== -//User confirmation routes +// User confirmation routes (Admin only) +// router.put('/:id/confirm', isAuthenticated, isAdmin, validateObjectId('id'), userController.confirmUserById); +// router.put('/:id/unconfirm', isAuthenticated, isAdmin, validateObjectId('id'), userController.unconfirmUserById); -/* -//Confirm user and update listings -router.put('/:id/confirm', async (request: Request, response: Response) => { - try { - const user = await confirmUser(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// Department routes (Admin only) +// router.put('/:id/departments', isAuthenticated, isAdmin, validateObjectId('id'), userController.addDepartments); +// router.delete('/:id/departments', isAuthenticated, isAdmin, validateObjectId('id'), userController.removeDepartments); +// router.delete('/:id/departments/all', isAuthenticated, isAdmin, validateObjectId('id'), userController.clearDepartments); -//Unconfirm user and update listings -router.put('/:id/unconfirm', async (request: Request, response: Response) => { - try { - const user = await unconfirmUser(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// Own listings routes (Admin only) +// router.put('/:id/ownListings', isAuthenticated, isAdmin, validateObjectId('id'), userController.addOwnListings); +// router.delete('/:id/ownListings', isAuthenticated, isAdmin, validateObjectId('id'), userController.removeOwnListings); +// router.delete('/:id/ownListings/all', isAuthenticated, isAdmin, validateObjectId('id'), userController.clearOwnListings); -//Departments level routes +// ==================== FAV LISTINGS ROUTES ==================== -//Add departments by ObjectId or NetId -router.put('/:id/departments', async (request: Request, response: Response) => { - try { - const user = await addDepartments(request.params.id, Array.isArray(request.body.departments) ? request.body.departments : [request.body.departments]); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// Get favListings id's for current user +router.get('/favListingsIds', isAuthenticated, userController.getFavListingsIds); -//Remove departments by ObjectId or NetId -router.delete('/:id/departments', async (request: Request, response: Response) => { - try { - const user = await deleteDepartments(request.params.id, Array.isArray(request.body.departments) ? request.body.departments : [request.body.departments]); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// Favorite listings routes (for specific user - Admin only - COMMENTED) +// router.put('/:id/favListings', isAuthenticated, isAdmin, validateObjectId('id'), userController.addFavListingsByUserId); -//Clear departments by ObjectId or NetId -router.delete('/:id/departments/all', async (request: Request, response: Response) => { - try { - const user = await clearDepartments(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// Add favListings for the user currently logged in +router.put('/favListings', isAuthenticated, userController.addFavListings); -//Own listings level routes +// Favorite listings routes (for specific user - Admin only - COMMENTED) +// router.delete('/:id/favListings', isAuthenticated, isAdmin, validateObjectId('id'), userController.removeFavListingsByUserId); -//Add ownListings by ObjectId or NetId -router.put('/:id/ownListings', async (request: Request, response: Response) => { - try { - const user = await addOwnListings(request.params.id, Array.isArray(request.body.ownListings) ? request.body.ownListings : [request.body.ownListings]); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// Remove favListings for the user currently logged in +router.delete('/favListings', isAuthenticated, userController.removeFavListings); -//Remove ownListings by ObjectId or NetId -router.delete('/:id/ownListings', async (request: Request, response: Response) => { - try { - const user = await deleteOwnListings(request.params.id, Array.isArray(request.body.ownListings) ? request.body.ownListings : [request.body.ownListings]); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// Favorite listings routes (for specific user - Admin only - COMMENTED) +// router.delete('/:id/favListings/all', isAuthenticated, isAdmin, validateObjectId('id'), userController.clearFavListings); -//Clear ownListings by ObjectId or NetId -router.delete('/:id/ownListings/all', async (request: Request, response: Response) => { - try { - const user = await clearOwnListings(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -});*/ +// ==================== USER CRUD ROUTES (ADMIN - COMMENTED) ==================== -//Fav listings level routes +// Create new user +// router.post("/", isAuthenticated, isAdmin, userController.createUser); -//Get favListings id's for current user -router.get('/favListingsIds', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - const user = await readUser(currentUser.netId); - response.status(200).json({ favListingsIds: user.favListings }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// Read all users +// router.get("/", isAuthenticated, isAdmin, userController.getAllUsers); -/* -//Add favListings by ObjectId or NetId -router.put('/:id/favListings', async (request: Request, response: Response) => { - try { - const user = await addFavListings(request.params.id, Array.isArray(request.body.favListings) ? request.body.favListings : [request.body.favListings]); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -});*/ +// Return all listings data for a specific user by ObjectId or NetId +// router.get('/:id/listings', isAuthenticated, isAdmin, validateObjectId('id'), userController.getUserListingsById); -//Add favListings for the user currently logged in -router.put('/favListings', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - if (!request.body.data.favListings) { - throw new Error('No favListings provided'); - } - const user = await addFavListings(currentUser.netId, Array.isArray(request.body.data.favListings) ? request.body.data.favListings : [request.body.data.favListings]); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// ==================== USER PROFILE ROUTES (CURRENT USER) ==================== -/* -//Remove favListings by ObjectId or NetId -router.delete('/:id/favListings', async (request: Request, response: Response) => { - try { - const user = await deleteFavListings(request.params.id, Array.isArray(request.body.favListings) ? request.body.favListings : [request.body.favListings]); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -});*/ +// Return all listings data for the user currently logged in +router.get('/listings', isAuthenticated, userController.getUserListings); -//Remove favListings for the user currently logged in -router.delete('/favListings', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - if (!request.body.favListings) { - throw new Error('No favListings provided'); - } - console.log(request.body); - const user = await deleteFavListings(currentUser.netId, Array.isArray(request.body.favListings) ? request.body.favListings : [request.body.favListings]); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); +// User CRUD routes (Admin only - COMMENTED) +// router.get('/:id', isAuthenticated, isAdmin, validateObjectId('id'), userController.getUserById); +// router.put('/:id', isAuthenticated, isAdmin, validateObjectId('id'), userController.updateUserById); -/* -//Clear favListings by ObjectId or NetId -router.delete('/:id/favListings/all', async (request: Request, response: Response) => { - try { - const user = await clearFavListings(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -});*/ +// Update data for user currently logged in +router.put('/', isAuthenticated, userController.updateCurrentUser); -//User level routes - -/* -//Create new user -router.post("/", async (request: Request, response: Response) => { - try { - const user = await createUser(request.body); - response.status(201).json({ user }); - } catch (error) { - console.log(error.message); - response.status(400).json({ error: error.message }); - } -}); - -//Read all users -router.get("/", async (request: Request, response: Response) => { - try { - const users = await readAllUsers(); - response.status(200).json({ users }); - } catch (error) { - console.log(error.message); - response.status(500).json({ error: error.message }); - } -}); - -//Return all listings data for a specific user by ObjectId or NetId -router.get('/:id/listings', async (request: Request, response: Response) => { - try { - const user = await readUser(request.params.id); - const ownListings = await readListings(user.ownListings); - const favListings = await readListings(user.favListings); - response.status(200).json({ ownListings: ownListings, favListings: favListings }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -});*/ - -//Return all listings data for the user currently logged in (for reload on accounts page, so also returns relevant user data) -router.get('/listings', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - const user = await readUser(currentUser.netId); - const ownListings = await readListings(user.ownListings); - const favListings = await readListings(user.favListings); - - //Clean listings to remove those that no longer exist - let ownIds: mongoose.Types.ObjectId[] = []; - for (const listing of ownListings) { - ownIds.push(listing._id); - } - - let favIds: mongoose.Types.ObjectId[] = []; - for (const listing of favListings) { - favIds.push(listing._id); - } - - await updateUser(currentUser.netId, { ownListings: ownIds, favListings: favIds }); - - response.status(200).json({ ownListings: ownListings, favListings: favListings }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -/* -//Read specific user by ObjectId or NetId -router.get('/:id', async (request: Request, response: Response) => { - try { - const user = await readUser(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -//Update data for a specific user by ObjectId or NetId -router.put('/:id', async (request: Request, response: Response) => { - try { - const user = await updateUser(request.params.id, request.body); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -});*/ - -//Update data for user currently logged in -router.put('/', isAuthenticated, async (request: Request, response: Response) => { - try { - const currentUser = request.user as { netId? : string, userType: string, userConfirmed: boolean}; - if (!currentUser) { - throw new Error('User not logged in'); - } - - if(request.body.data.userConfirmed !== undefined) { - if(request.body.data.userConfirmed) { - await confirmUser(currentUser.netId); - } else { - await unconfirmUser(currentUser.netId); - } - } - - const user = await updateUser(currentUser.netId, request.body.data); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); - -/* -//Delete user by ObjectId or NetId -router.delete('/:id', async (request: Request, response: Response) => { - try { - const user = await deleteUser(request.params.id); - response.status(200).json({ user }); - } catch (error) { - console.log(error.message); - if (error instanceof NotFoundError) { - response.status(error.status).json({ error: error.message }); - } else { - response.status(500).json({ error: error.message }); - } - } -}); -*/ +// Delete user by ObjectId or NetId (Admin only - COMMENTED) +// router.delete('/:id', isAuthenticated, isAdmin, validateObjectId('id'), userController.deleteUserById); export default router; \ No newline at end of file diff --git a/server/src/services/listingBackupServices.ts b/server/src/services/listingBackupServices.ts deleted file mode 100644 index ce6886d..0000000 --- a/server/src/services/listingBackupServices.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { ListingBackup } from "../models"; -import { NotFoundError, ObjectIdError } from "../utils/errors"; -import mongoose from "mongoose"; - -export const createListingBackup = async (data: any) => { - try { - const listing = new ListingBackup(data); - await listing.save(); - return listing.toObject(); - } catch (error) { - throw new Error(error.message); - } -}; - -export const readAllListingBackups = async () => { - try { - const listings = await ListingBackup.find(); - return listings.map(listing => listing.toObject()); - } catch (error) { - throw new Error(error.message); - } -}; - -export const readListingBackup = async(id: any) => { - if (mongoose.Types.ObjectId.isValid(id)) { - const listing = await ListingBackup.findById(id); - if (!listing) { - throw new NotFoundError(`Listing backup not found with ObjectId: ${id}`); - } - return listing.toObject(); - } else { - throw new ObjectIdError("Did not received expected id type ObjectId"); - } -}; - -export const listingBackupExists = async(id: any) => { - if (mongoose.Types.ObjectId.isValid(id)) { - const listing = await ListingBackup.findById(id); - if (!listing) { - return false; - } - return true; - } else { - throw new ObjectIdError("Did not received expected id type ObjectId"); - } -} - -export const updateListingBackup = async(id: any, data: any) => { - if (mongoose.Types.ObjectId.isValid(id)) { - const listing = await ListingBackup.findByIdAndUpdate(id, data, - { new: true, runValidators: true} - ); - if (!listing) { - throw new NotFoundError(`Listing backup not found with ObjectId: ${id}`); - } - return listing.toObject(); - } else { - throw new ObjectIdError("Did not received expected id type ObjectId"); - } -}; - -export const deleteListingBackup = async(id: any) => { - if (mongoose.Types.ObjectId.isValid(id)) { - const user = await ListingBackup.findByIdAndDelete(id); - if (!user) { - throw new NotFoundError(`Listing backup not found with ObjectId: ${id}`); - } - return user.toObject(); - } else { - throw new ObjectIdError("Did not received expected id type ObjectId"); - } -} - -//Restore listing backup (with addition of ownListings) -//Clear outdated \ No newline at end of file diff --git a/server/src/services/newListingsService.ts b/server/src/services/listingService.ts similarity index 87% rename from server/src/services/newListingsService.ts rename to server/src/services/listingService.ts index 25b657a..243f76f 100644 --- a/server/src/services/newListingsService.ts +++ b/server/src/services/listingService.ts @@ -1,9 +1,7 @@ -import { NewListing } from "../models"; +import { Listing } from "../models"; import { IncorrectPermissionsError, NotFoundError, ObjectIdError } from "../utils/errors"; -import { createListingBackup } from "./listingBackupServices"; import { addOwnListings, deleteOwnListings, userExists, createUser, readUser } from "./userService"; import { fetchYalie } from "./yaliesService"; -import { User } from "../models"; import mongoose from "mongoose"; export const createListing = async (data: any, owner: any) => { @@ -11,7 +9,7 @@ export const createListing = async (data: any, owner: any) => { throw new Error('Incomplete user data for owner'); } - const listing = new NewListing({...data, ownerId: owner.netid, ownerEmail: owner.email, ownerFirstName: owner.fname, ownerLastName: owner.lname, confirmed: owner.userConfirmed}); + const listing = new Listing({...data, ownerId: owner.netid, ownerEmail: owner.email, ownerFirstName: owner.fname, ownerLastName: owner.lname, confirmed: owner.userConfirmed}); // Add listing id to ownListings of all professors associated with the listing const listingId = listing._id; @@ -41,13 +39,13 @@ export const createListing = async (data: any, owner: any) => { }; export const readAllListings = async () => { - const listings = await NewListing.find(); + const listings = await Listing.find(); return listings.map(listing => listing.toObject()); }; export const readListing = async(id: any) => { if (mongoose.Types.ObjectId.isValid(id)) { - const listing = await NewListing.findById(id); + const listing = await Listing.findById(id); if (!listing) { throw new NotFoundError(`Listing not found with ObjectId: ${id}`); } @@ -75,7 +73,7 @@ export const readListings = async(ids: any[]) => { let listings = []; for (const id of ids) { if (mongoose.Types.ObjectId.isValid(id)) { - const listing = await NewListing.findById(id); + const listing = await Listing.findById(id); if (listing) { listings.push(listing.toObject()); } @@ -86,7 +84,7 @@ export const readListings = async(ids: any[]) => { export const listingExists = async(id: any) => { if (mongoose.Types.ObjectId.isValid(id)) { - const listing = await NewListing.findById(id); + const listing = await Listing.findById(id); if (!listing) { return false; } @@ -114,7 +112,7 @@ export const listingExists = async(id: any) => { export const updateListing = async(id: any, userId: string, data: any, noAuth: boolean = false, useTimestamps: boolean = true) => { if (mongoose.Types.ObjectId.isValid(id)) { - const oldListing = await NewListing.findById(id); + const oldListing = await Listing.findById(id); if (!oldListing) { throw new NotFoundError(`Listing not found with ObjectId: ${id}`); @@ -150,7 +148,7 @@ export const updateListing = async(id: any, userId: string, data: any, noAuth: b throw new IncorrectPermissionsError(`User with id ${userId} does not have permission to update listing with id ${id}`); } - const listing = await NewListing.findByIdAndUpdate(id, data, + const listing = await Listing.findByIdAndUpdate(id, data, { new: true, runValidators: true, timestamps: useTimestamps } ); @@ -235,19 +233,13 @@ export const removeFavorite = async(id: any, userId: string) => { export const deleteListing = async(id: any) => { if (mongoose.Types.ObjectId.isValid(id)) { - const listing = await NewListing.findById(id); + const listing = await Listing.findById(id); if (!listing) { throw new NotFoundError(`Listing not found with ObjectId: ${id}`); } const {professorIds, professorNames, departments, emails, websites, description, keywords} = listing; - const listingBackupData = Object.fromEntries( - Object.entries({professorIds, professorNames, departments, emails, websites, description, keywords}) - .filter(([_, value]) => value !== undefined) - ); - - const backup = await createListingBackup(listingBackupData); - await NewListing.findByIdAndDelete(id); + await Listing.findByIdAndDelete(id); // Remove listing id from ownListings of all professors associated with the listing const oldListingId = listing._id; @@ -258,8 +250,6 @@ export const deleteListing = async(id: any) => { await deleteOwnListings(id, [oldListingId]); } } - - return backup; } else { throw new ObjectIdError("Did not received expected id type ObjectId"); } diff --git a/server/src/services/userBackupService.ts b/server/src/services/userBackupService.ts deleted file mode 100644 index 538b3c9..0000000 --- a/server/src/services/userBackupService.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { UserBackup } from "../models"; -import { NotFoundError } from "../utils/errors"; -import mongoose from "mongoose"; - -export const createUserBackup = async (userData: any) => { - try { - const user = new UserBackup(userData); - await user.save(); - return user.toObject(); - } catch (error) { - throw new Error(error.message); - } -}; - -export const readAllUserBackups = async () => { - try { - const users = await UserBackup.find(); - return users.map(user => user.toObject()); - } catch (error) { - throw new Error(error.message); - } -}; - -export const readUserBackup = async(id: any) => { - if (mongoose.Types.ObjectId.isValid(id)) { - const user = await UserBackup.findById(id); - if (!user) { - throw new NotFoundError(`User backup not found with ObjectId: ${id}`); - } - return user.toObject(); - } else { - const user = await UserBackup.findOne({ netid: { $regex: `^${id}$`, $options: 'i'} }); - if (!user) { - throw new NotFoundError(`User backup not found with NetId: ${id}`); - } - return user.toObject(); - } -}; - -export const userBackupExists = async(id: any) => { - if (mongoose.Types.ObjectId.isValid(id)) { - const user = await UserBackup.findById(id); - if (!user) { - return false; - } - return true; - } else { - const user = await UserBackup.findOne({ netid: { $regex: `^${id}$`, $options: 'i'} }); - if (!user) { - return false; - } - return true; - } -} - -export const updateUserBackup = async(id: any, data: any) => { - if (mongoose.Types.ObjectId.isValid(id)) { - const user = await UserBackup.findByIdAndUpdate(id, data, - { new: true, runValidators: true} - ); - if (!user) { - throw new NotFoundError(`User backup not found with ObjectId: ${id}`); - } - return user.toObject(); - } else { - const user = await UserBackup.findOneAndUpdate( - { netid: { $regex: `^${id}$`, $options: 'i'} }, - data, - { new: true, runValidators: true } - ); - if (!user) { - throw new NotFoundError(`User backup not found with NetId: ${id}`); - } - return user.toObject(); - } -}; - -export const deleteUserBackup = async(id: any) => { - if (mongoose.Types.ObjectId.isValid(id)) { - const user = await UserBackup.findByIdAndDelete(id); - if (!user) { - throw new NotFoundError(`User backup not found with ObjectId: ${id}`); - } - return user.toObject(); - } else { - const user = await UserBackup.findOneAndDelete({ netid: { $regex: `^${id}$`, $options: 'i'} }); - if (!user) { - throw new NotFoundError(`User backup not found with NetId: ${id}`); - } - return user.toObject(); - } -} \ No newline at end of file diff --git a/server/src/services/userService.ts b/server/src/services/userService.ts index 80ed481..e521d65 100644 --- a/server/src/services/userService.ts +++ b/server/src/services/userService.ts @@ -1,7 +1,6 @@ import { User } from "../models"; import { NotFoundError } from "../utils/errors"; -import { createUserBackup, updateUserBackup, userBackupExists } from "./userBackupService"; -import { readListing, confirmListing, unconfirmListing, addFavorite, removeFavorite } from "./newListingsService"; +import { readListing, confirmListing, unconfirmListing, addFavorite, removeFavorite } from "./listingService"; import mongoose from "mongoose"; export const createUser = async (userData: any) => { @@ -114,17 +113,6 @@ export const deleteUser = async(id: any) => { throw new NotFoundError(`User not found with ObjectId: ${id}`); } - const {netid, email, userType, userConfirmed, fname, lname, website, bio, departments, ownListings, favListings} = user; - const userBackupData = Object.fromEntries( - Object.entries({netid, email, userType, userConfirmed, fname, lname, website, bio, departments, ownListings, favListings}) - .filter(([_, value]) => value !== undefined) - ); - - if (await userBackupExists(netid)) { - await updateUserBackup(netid, userBackupData); - } else { - await createUserBackup(userBackupData); - } await User.findByIdAndDelete(id); return user.toObject(); @@ -133,23 +121,7 @@ export const deleteUser = async(id: any) => { if (!user) { throw new NotFoundError(`User not found with NetId: ${id}`); } - - const {netid, email, userType, userConfirmed, fname, lname, website, bio, departments, ownListings, favListings} = user; - const userBackupData = Object.fromEntries( - Object.entries({netid, email, userType, userConfirmed, fname, lname, website, bio, departments, ownListings, favListings}) - .filter(([_, value]) => value !== undefined) - ); - - let backup; - - if (await userBackupExists(netid)) { - backup = await updateUserBackup(id, userBackupData); - } else { - backup = await createUserBackup(userBackupData); - } await User.findOneAndDelete({ netid: { $regex: `^${id}$`, $options: 'i'} }); - - return backup; } } @@ -186,10 +158,10 @@ export const clearDepartments = async(id: any) => { }; //Add own listings -export const addOwnListings = async(id: any, newListings: [mongoose.Types.ObjectId]) => { +export const addOwnListings = async(id: any, Listings: [mongoose.Types.ObjectId]) => { let user = await readUser(id); - user.ownListings.unshift(...newListings); + user.ownListings.unshift(...Listings); user.ownListings = Array.from(new Set(user.ownListings.map(listing => listing.toString()))).map(listing => new mongoose.Types.ObjectId(listing)); const newUser = await updateUser(id, {"ownListings": user.ownListings}); @@ -218,15 +190,15 @@ export const clearOwnListings = async(id: any) => { }; //Add fav listings -export const addFavListings = async(id: any, newListings: [mongoose.Types.ObjectId]) => { +export const addFavListings = async(id: any, Listings: [mongoose.Types.ObjectId]) => { let user = await readUser(id); - user.favListings.unshift(...newListings); + user.favListings.unshift(...Listings); user.favListings = Array.from(new Set(user.favListings.map(listing => listing.toString()))).map(listing => new mongoose.Types.ObjectId(listing)); const newUser = await updateUser(id, {"favListings": user.favListings}); - for (const listingId of newListings) { + for (const listingId of Listings) { await addFavorite(listingId.toString(), id); } diff --git a/server/src/utils/permissions.ts b/server/src/utils/permissions.ts deleted file mode 100644 index 98ff8da..0000000 --- a/server/src/utils/permissions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import express from "express"; - -const isAuthenticated = (req: express.Request, res: express.Response, next: express.NextFunction) => { - if (req.user) { - return next(); - } - res.status(401).json({ error: "Unauthorized" }); -} - -const isTrustworthy = (req: express.Request, res: express.Response, next: express.NextFunction) => { - const user = req.user as { netId? : string, userType? : string, userConfirmed? : boolean}; - - if (user && user.userConfirmed && (user.userType === "admin" || user.userType === "professor" || user.userType === "faculty")) { - return next(); - } - res.status(403).json({ error: "Forbidden" }); -} -export { isAuthenticated, isTrustworthy }; \ No newline at end of file