diff --git a/src/App.tsx b/src/App.tsx index aad23375..8b5f2eb2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,22 +2,20 @@ import React from "react"; import { Routes, Route } from "react-router-dom"; import "./style/App.css"; -import Opportunities from "./opportunities/pages/opportunities.tsx"; - import Home from "./shared/pages/Home.tsx"; import PageNotFound from "./shared/pages/404.tsx"; import MainNavigation from "./shared/components/Navigation/MainNavigation.tsx"; -import Jobs from "./opportunities/pages/Jobs.tsx"; +import StickyFooter from "./shared/components/Navigation/StickyFooter.tsx"; +import ProfilePage from "./shared/pages/Profile.tsx"; import Departments from "./staff/pages/Departments.tsx"; import StaffPage from "./staff/pages/Staff.tsx"; import Department from "./staff/pages/Department.tsx"; import CreatePost from "./staff/pages/CreatePost.tsx"; -import IndividualPost from "./opportunities/pages/IndividualPost.tsx"; -import ProfilePage from "./shared/pages/Profile.tsx"; import LoginRedirection from "./auth/Login.tsx"; import LogoutRedirection from "./auth/Logout.tsx"; -import StickyFooter from "./shared/components/Navigation/StickyFooter.tsx"; import Token from "./auth/Token.tsx"; +import Opportunities from "./opportunities/pages/Opportunities.tsx"; +import IndividualPost from "./opportunities/pages/IndividualPost.tsx"; import { HelmetProvider } from 'react-helmet-async'; import { AuthProvider } from './context/AuthContext.tsx'; @@ -37,10 +35,8 @@ function App() { } /> } /> } /> - - } /> - } /> + } /> } /> { - +interface FiltersFieldProps { + resetFilters: () => void; + deleteFilter: (filter: string) => void; + filters: string[]; + setPopUpMenu: () => void; +} + +export default function FiltersField({ resetFilters, deleteFilter, filters, setPopUpMenu }: FiltersFieldProps) { return (

@@ -17,15 +22,15 @@ const FiltersField = ({ resetFilters, deleteFilter, filters, setPopUpMenu }) => - + Change Filters - + {/* Fix rendering with new filters = [ [],[],[] ]*/} - {filters[1].map((filter) => { - return( + {filters.map((filter) => { + return ( } @@ -47,12 +52,3 @@ const FiltersField = ({ resetFilters, deleteFilter, filters, setPopUpMenu }) =>
); }; - -FiltersField.propTypes = { - resetFilters: PropTypes.func.isRequired, - deleteFilter: PropTypes.func.isRequired, - filters: PropTypes.arrayOf(PropTypes.array), - setPopUpMenu: PropTypes.func.isRequired, -}; - -export default FiltersField; diff --git a/src/opportunities/components/OpportunitiesDetails.tsx b/src/opportunities/components/OpportunitiesDetails.tsx new file mode 100644 index 00000000..a1f98282 --- /dev/null +++ b/src/opportunities/components/OpportunitiesDetails.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { OpportunityList } from "../../types/opportunities.ts"; + +interface OpportunitiesListProps { + opportunities: OpportunityList[]; +} + +export default function OpportunitiesList({ opportunities }: OpportunitiesListProps) { + return ( +
+
+ + + {/* Column Headers */} + + + + + + + + + + + + + + {/* Info about the opportunities */} + {opportunities.length > 0 ? ( + opportunities.map((opportunity) => ( + + + + + + + + + + + + )) + ) : ( + + + + )} + +
PositionDescriptionLocationPayCreditsLab ManagersTermViewSave
{opportunity.name}{opportunity.description}{opportunity.location}{opportunity.pay ? `$${opportunity.pay}/hr` : ""}{opportunity.credits}{opportunity.lab_managers} + {opportunity.semester} {opportunity.year} + + + + +
+ No results found. +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/opportunities/components/PopUpMenu.tsx b/src/opportunities/components/PopUpMenu.tsx new file mode 100644 index 00000000..d88aed34 --- /dev/null +++ b/src/opportunities/components/PopUpMenu.tsx @@ -0,0 +1,211 @@ +import React, { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import CheckBox from "../../shared/components/Checkbox.tsx"; +import Input from "../../staff/components/Input"; +import { Filters } from "../../types/opportunities.ts"; + +interface PopUpMenuProps { + setFunction: () => void; + reset: () => void; + filters: Filters; + setFilters: (activeFilters: string[], filterMap: Filters) => void; +} + +interface Major { + code: string; + name: string; +} + +export default function PopUpMenu({ setFunction, reset, filters, setFilters }: PopUpMenuProps) { + const [majors, setMajors] = useState(); + const [validYears, setValidYears] = useState([]); + + const checkboxes: [string, string[], "years" | "credits"][] = [ + ["Class Year", validYears, "years"], + ["Credits", ["1", "2", "3", "4"], "credits"] + ]; + + useEffect(() => { + const fetchMajors = async () => { + const url = `${process.env.REACT_APP_BACKEND_SERVER}/majors`; + const response = await fetch(url); + if (!response.ok) { + console.log("Error fetching majors"); + } else { + const data = await response.json(); + setMajors(data); + } + } + fetchMajors(); + }, []); + + useEffect(() => { + const fetchYears = async () => { + const url = `${process.env.REACT_APP_BACKEND_SERVER}/years`; + const response = await fetch(url); + if (!response.ok) { + console.log("Error fetching valid years"); + } else { + const data = await response.json(); + setValidYears(data); + } + } + fetchYears(); + }, []); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + years: [], + credits: [], + hourlyPay: filters.hourlyPay ?? 0, + majors: [] + }, + }); + + interface FormData { + years: string[], + credits: string[], + hourlyPay: number, + majors: string[] + } + + function formatCredits(credits: string[]): string | null { + if (credits.length === 4) { + return "1-4 Credits"; + } else if (credits.length === 1) { + return `${credits[0]} ${credits[0] === "1" ? "Credit" : "Credits"}`; + } else if (JSON.stringify(credits) === JSON.stringify(["1", "2", "3"])) { + return "1-3 Credits"; + } else if (JSON.stringify(credits) === JSON.stringify(["2", "3", "4"])) { + return "2-4 Credits"; + } else if (credits.length === 0) { + return null; + } else { + return `${credits.join(", ")} Credits`; + } + } + + function submitHandler(data: FormData) { + const { years, credits, hourlyPay, majors } = data; + const newFilterMap: Filters = { + years: years.map(Number), + credits: credits, + hourlyPay: Number(hourlyPay), + majors: majors + } + + const activeFilters: string[] = [ + ...years, + ...(formatCredits(credits) ? [formatCredits(credits)!] : []), + ...(Number(hourlyPay) > 0 ? [`$${Number(hourlyPay).toFixed(2)}/hr+`] : []), + ...majors + ]; + setFilters(activeFilters, newFilterMap); + setFunction() + }; + + console.log("Filters: ", filters); + + return ( +
+ +
+
+
+
+
Filters
+
+
{ + submitHandler(data); + })} + className="form-container" + >
{/* Added max-height and overflow-y-auto */} +
+ {checkboxes.map((filter) => ( +
+ +
+ ))} +
+
+ value >= 0 || "Hourly pay must be greater or equal to 0", + pattern: { + value: /^[0-9]\d*$/, + message: "Hourly pay must be a positive integer" + } + }) + }} + type="number" + options={[]} + placeHolder="Enter minimum hourly pay" + /> +
+ +
+

Majors

+
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/opportunities/components/Posts.tsx b/src/opportunities/components/Posts.tsx index 959c8ab7..6b0c8357 100644 --- a/src/opportunities/components/Posts.tsx +++ b/src/opportunities/components/Posts.tsx @@ -1,260 +1,170 @@ -import React from "react"; import FiltersField from "./FiltersField.tsx"; -import PostsField from "./PostsField.tsx"; -import { useReducer } from "react"; -import { useCallback } from "react"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import CheckBox from "../../staff/components/Checkbox.tsx"; -import PropTypes from "prop-types"; -import Input from "../../staff/components/Input"; +import React, { useReducer, useCallback, useEffect, useState } from "react"; +import OpportunitiesList from "./OpportunitiesDetails.tsx"; +import PopUpMenu from "./PopUpMenu.tsx"; +import { Filters, OpportunityList } from "../../types/opportunities.ts"; -const PopUpMenu = ( {setFunction, validYears, clear, add, reset} ) => { - const checkboxes = [["Semester",["Summer","Fall","Spring"],"semesters"], - ["Eligible Years",validYears,"years"], - ["Credits", ["1","2","3","4"],"credits"]] - const majors = [["ARCH", "LGHT", "BMED", "CHME", "CIVL", "ECSE", "ENGR", "ENVE", "ECSI", "ISYE"], - ["MANE", "MTLE", "ARTS", "COMM", "IHSS", "INQR", "LANG", "LITR", "PHIL"], - ["STDO", "WRIT", "COGS", "ECON", "GSAS", "PSYC", "ITWS", "MGMT", "ASTR"], - ["BCBP", "BIOL", "CHEM", "CSCI", "ISCI", "ERTH", "MATH", "MATP", "PHYS"]] - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - defaultValues: { - semesters: [], - years: [], - credits: [], - hourlyPay: 0, - majors: [] - }, - }); - - interface FormData { - semesters: string[], - years: string[], - credits: string[], - hourlyPay: string, - majors: string[] - } - - function submitHandler(data: FormData) { - const { semesters, years, credits, hourlyPay, majors} = data; - clear(); - add(semesters) - add(years) - add(credits) - if (hourlyPay == "0") { - add([]) - } else { - add([hourlyPay]) - } - add(majors) - setFunction() - }; - - return ( -
- -
-
-
-
-
Filters
-
-
{ - submitHandler(data); - })} - className="form-container" - >
{/* Added max-height and overflow-y-auto */} -
- { checkboxes.map((filter) => ( -
- -
- )) } -
-
- -
- -
-

Majors

-
- { majors.map((list, index) => ( -
- -
- )) } -
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
-
-
-
- ); -} - -PopUpMenu.propTypes = { - setFunction: PropTypes.func.isRequired, - validYears: PropTypes.arrayOf(PropTypes.string), - clear: PropTypes.func.isRequired, - add: PropTypes.func.isRequired, - reset: PropTypes.func.isRequired -}; -const Posts = ( {years} ) => { - const [popUpMenu, setPopUpMenu] = React.useState(false); +const Posts = () => { + const [popUpMenu, setPopUpMenu] = useState(false); const date = new Date(); - const month = date.getMonth(); - const currSem = (0 <= month && month <= 5) ? "Spring" : (5 < month && month <= 8) ? "Summer" : "Fall"; const currYr = date.getFullYear(); + // replace currYr with user year + + interface OpportunityState { + filters: { + activeFilters: string[]; + filterMap: Filters; + }; + activeId: string; + opportunities: OpportunityList[]; + } + + type OpportunityAction = + | { type: "SET_FILTERS"; activeFilters: string[]; filterMap: Filters } + | { type: "RESET_FILTERS" } + | { type: "REMOVE_FILTER"; filter: string } + | { type: "SET_OPPORTUNITIES"; opportunities: OpportunityList[] }; - const reducer = (state, action) => { + const reducer: React.Reducer = (state, action) => { switch (action.type) { - case "CLEAR_FILTERS": - state.filters = [[],[]]; - return { ...state }; - case "RESET_FILTERS": - state.filters = [[[currSem],[currYr],[],[],[]],[currSem, currYr]]; - return { ...state }; - case "REMOVE_FILTER": - if (action.filter) { - state.filters[1] = state.filters[1].filter((item) => item !== action.filter); - state.filters[0].map((list, index) => { - state.filters[0][index] = list.filter((item) => item !== action.filter); - }) - } - return { ...state }; - case "ADD_FILTER": - if (action.filter) { - state.filters[0] = [...state.filters[0], action.filter]; - state.filters[1] = [...state.filters[1], ...action.filter]; - console.log(state.filters) - } - return { ...state }; - case "SET_ACTIVE_ID": - if (action.id) { - if (state.jobs.find((job) => job.id === action.id)) { - state.activeId = action.id; - } - } - return { ...state }; - case "SET_JOBS": - if (action.jobs) { - state.jobs = action.jobs; - } - return { ...state }; + case "SET_FILTERS": + if (!action.activeFilters || !action.filterMap) return state; + + return { + ...state, + filters: { + activeFilters: action.activeFilters, + filterMap: action.filterMap, + }, + }; + + case "RESET_FILTERS": { + return { + ...state, + filters: { + activeFilters: [currYr.toString()], + filterMap: { + years: [currYr], + credits: [], + hourlyPay: 0, + majors: [], + }, + }, + }; + } + + case "REMOVE_FILTER": { + if (!action.filter) return state; + + const newActiveFilters = state.filters.activeFilters.filter((filter) => filter !== action.filter); + const newFilterMap = { + ...state.filters.filterMap, + years: state.filters.filterMap.years.filter((year) => year !== parseInt(action.filter)), + credits: action.filter.includes("Credit") ? [] : state.filters.filterMap.credits, + majors: state.filters.filterMap.majors.filter((major) => major !== action.filter), + hourlyPay: action.filter.includes("$") ? 0 : state.filters.filterMap.hourlyPay, + }; + + return { + ...state, + filters: { + activeFilters: newActiveFilters, + filterMap: newFilterMap, + }, + }; + } + + case "SET_OPPORTUNITIES": + if (!action.opportunities) return state; + + return { + ...state, + opportunities: action.opportunities, + }; + default: return state; } }; - const [jobState, dispatch] = useReducer(reducer, { - filters: [[[currSem],[currYr],[],[],[]],[currSem, currYr]], + const [opportunityState, dispatch] = useReducer(reducer, { + filters: { + activeFilters: [currYr.toString()], + filterMap: { + years: [currYr], + credits: [], + hourlyPay: 0, + majors: [], + }, + }, activeId: "", - jobs: [], + opportunities: [], }); - const clearFilters = useCallback(() => { - dispatch({ type: "CLEAR_FILTERS"}); - }, []); - + // Action dispatchers const resetFilters = useCallback(() => { - dispatch({ type: "RESET_FILTERS"}); + dispatch({ type: "RESET_FILTERS" }); }, []); - const removeFilter = useCallback((name) => { + const removeFilter = useCallback((name: string) => { dispatch({ type: "REMOVE_FILTER", filter: name }); }, []); - const addFilter = useCallback((name) => { - dispatch({ type: "ADD_FILTER", filter: name }); + const setFilters = useCallback((activeFilters: string[], filterMap: Filters) => { + dispatch({ type: "SET_FILTERS", activeFilters, filterMap }); }, []); - const setActiveId = useCallback((val) => { - dispatch({ type: "SET_ACTIVE_ID", id: val }); - }, []); const fetchOpportunities = async () => { - const url = `${process.env.REACT_APP_BACKEND_SERVER}/getOpportunityCards`; - - const response = await fetch(url); - - if (!response.ok) { - console.log("Error fetching opportunities"); - } else { - let data = await response.json(); - data = data.data; - dispatch({ type: "SET_JOBS", jobs: data }); - console.log(jobState.jobs); + const queryParams = new URLSearchParams( + Object.entries(opportunityState.filters.filterMap) + .filter(([, value]) => { + if (Array.isArray(value)) return value.length > 0; + return value !== 0 && value !== null && value !== undefined; + }) + .reduce((acc, [key, value]) => { + if (Array.isArray(value)) { + acc[key] = value.join(","); + } else { + acc[key] = value.toString(); + } + return acc; + }, {} as Record) + ); + + try { + const response = await fetch(`${process.env.REACT_APP_BACKEND_SERVER}/opportunity/filter?${queryParams.toString()}`, { + method: "GET", + credentials: "include", + }); + + if (!response.ok) { + console.log("Error fetching opportunities", response.status); + dispatch({ type: "SET_OPPORTUNITIES", opportunities: [] }); + } else { + const data = await response.json(); + dispatch({ type: "SET_OPPORTUNITIES", opportunities: data }); + console.log(data); + } + } catch (error) { + console.error("Error fetching opportunities:", error); + dispatch({ type: "SET_OPPORTUNITIES", opportunities: [] }); } }; useEffect(() => { fetchOpportunities(); - }, []); + }, [opportunityState.filters.filterMap]); - console.log() return (
- setPopUpMenu(!popUpMenu)} /> - {popUpMenu && setPopUpMenu(!popUpMenu)} validYears={years} clear={clearFilters} add={addFilter} reset={resetFilters}/>} - + setPopUpMenu(!popUpMenu)} /> + {popUpMenu && setPopUpMenu(!popUpMenu)} filters={opportunityState.filters.filterMap} reset={resetFilters} setFilters={setFilters} />} +
); }; -Posts.propTypes = { - years: PropTypes.arrayOf(PropTypes.string), -}; - export default Posts; diff --git a/src/opportunities/components/PostsField.tsx b/src/opportunities/components/PostsField.tsx deleted file mode 100644 index c155c3ef..00000000 --- a/src/opportunities/components/PostsField.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useEffect } from "react"; -import { useState } from "react"; -import JobPost from "./JobPost"; -import JobDetails from "./JobDetails"; -import PropTypes from "prop-types"; - - -const PostsField = ({ activeId, setActive, opportunities }) => { - const [activeOpportunity, setActiveOpportunity] = useState(null); - - const fetchOpportunity = async (id) => { - const url = `${process.env.REACT_APP_BACKEND_SERVER}/getOpportunity/${id}`; - const response = await fetch(url); - if (!response.ok) { - console.log("Error fetching opportunity"); - setActiveOpportunity(null); - } else { - let data = await response.json(); - data = data.data; - setActiveOpportunity(data); - } - }; - - useEffect(() => { - fetchOpportunity(activeId); - }, [activeId]); - - return ( -
-
- {opportunities && - opportunities.map((job) => { - return ( - - ); - })} -
- {activeId != "" && activeOpportunity && ( - - )} - {(activeId === "" || !activeOpportunity) && "Opportunity not found."} -
- ); -}; - -PostsField.propTypes = { - activeId: PropTypes.string, - setActive: PropTypes.func.isRequired, - opportunities: PropTypes.array, -} - -export default PostsField; diff --git a/src/opportunities/components/SavedJobs.js b/src/opportunities/components/SavedJobs.js deleted file mode 100644 index b3fe34c7..00000000 --- a/src/opportunities/components/SavedJobs.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; - -const SavedJobs = () => { - - return

Saved Jobs

; -} - -export default SavedJobs; \ No newline at end of file diff --git a/src/opportunities/components/opportunitiesDetails.tsx b/src/opportunities/components/opportunitiesDetails.tsx deleted file mode 100644 index c2ca633a..00000000 --- a/src/opportunities/components/opportunitiesDetails.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import React from "react"; - - -//Created a new interface to make displaying an opportunity easier, contains 9 fields - -export interface Opportunity { - name: string; - description: string; - recommended_experience: string; - pay: number; - semester: string; - year: number; - application_due: Date; - location: string; - professor: string - // Add any other relevant information about an opportunity -} - - -// List of sample opportunities of type Opportunity - -const sampleOpportunities: Opportunity[] = [ - { - name: "Research Assistant - Machine Learning Lab", - description: "Work on cutting-edge ML projects", - recommended_experience: "Python, Machine Learning basics", - pay: 15.00, - semester: "Fall", - year: 2024, - application_due: new Date("2024-08-01"), - location: "DCC", - professor: "Dr. Emily Chen" - }, - { - name: "Data Visualization Intern - Data Science Center", - description: "Create compelling visualizations for real-world datasets.", - recommended_experience: "D3.js, Tableau, or Matplotlib", - pay: 12.50, - semester: "Spring", - year: 2025, - application_due: new Date("2025-01-15"), - location: "EMPAC", - professor: "Dr. Alan Green" - }, - { - name: "Undergraduate Researcher - Renewable Energy Lab", - description: "Analyze energy efficiency of solar panel setups.", - recommended_experience: "R, Excel, or energy systems knowledge", - pay: 14.00, - semester: "Summer", - year: 2025, - application_due: new Date("2025-04-30"), - location: "Jonsson Engineering Center", - professor: "Dr. Maria Santos" - }, - { - name: "AI in Healthcare Research Assistant", - description: "Develop and test AI models for diagnostic tools.", - recommended_experience: "Python, TensorFlow, basic healthcare knowledge", - pay: 16.00, - semester: "Fall", - year: 2024, - application_due: new Date("2024-07-20"), - location: "Biotech Center", - professor: "Dr. Raj Patel" - }, - { - name: "Human-Computer Interaction (HCI) Researcher", - description: "Study user interfaces to improve accessibility.", - recommended_experience: "HTML, CSS, JavaScript, Usability Testing", - pay: 13.00, - semester: "Spring", - year: 2025, - application_due: new Date("2025-01-10"), - location: "Carnegie Building", - professor: "Dr. Susan Miller" - }, - { - name: "Climate Modeling Research Intern", - description: "Simulate climate patterns using advanced modeling techniques.", - recommended_experience: "Python, MATLAB, or climate science coursework", - pay: 14.50, - semester: "Summer", - year: 2025, - application_due: new Date("2025-03-15"), - location: "Troy Building", - professor: "Dr. John Reynolds" - } -]; - - -// This component returns a 'list' of all the opportunities - -const OpportunitiesList = () => { - return ( -
-
- - - {/* Column Headers */} - - - - - - - - - - - - {/* Info about the opportunities */} - {sampleOpportunities.map((opportunity, index) => ( - - - - - - - - - - ))} - -
PositionDescriptionLocationPayProfessorTermAction
{opportunity.name}{opportunity.description}{opportunity.location}${opportunity.pay}/hr{opportunity.professor} - {opportunity.semester} {opportunity.year} - - -
-
-
- ); -}; - -export default OpportunitiesList; diff --git a/src/opportunities/pages/Jobs.tsx b/src/opportunities/pages/Jobs.tsx deleted file mode 100644 index fa582b18..00000000 --- a/src/opportunities/pages/Jobs.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; - -import Posts from "../components/Posts"; - -import PageNavigation from "../../shared/components/Navigation/PageNavigation"; - -import usePageNavigation from "../../shared/hooks/page-navigation-hook"; - -import OpportunitiesList from "../components/opportunitiesDetails.tsx"; - -interface PageNavigationType { - activePage: string; - pages: string[]; -} - -const Jobs: React.FC = () => { - - // navigation bar - const [pages, switchPage] = usePageNavigation(["Search", "Saved"], "Search") as [ - PageNavigationType, - (page: string) => void - ]; - - // displaying opportunities list component - return ( -
-
-
- - - {pages.activePage === "Search" && } - -
-
- -
- - - ); -}; - -export default Jobs; diff --git a/src/opportunities/pages/Opportunities.tsx b/src/opportunities/pages/Opportunities.tsx new file mode 100644 index 00000000..beb8f98f --- /dev/null +++ b/src/opportunities/pages/Opportunities.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import Posts from "../components/Posts"; +import usePageNavigation from "../../shared/hooks/page-navigation-hook.ts"; + +interface PageNavigationType { + activePage: string; + pages: string[]; +} + +const Opportunities: React.FC = () => { + + // navigation bar + const [pages, switchPage] = usePageNavigation(["Search", "Saved"], "Search") as [ + PageNavigationType, + (page: string) => void + ]; + + const activeLink = "active-link"; + const normalLink = "normal-link hover:border-b-2 hover:text-black"; + + // displaying opportunities list component + return ( +
+
+
+
+

Opportunities

+ + +
+ + {pages.activePage === "Search" && } + +
+
+
+ ); +}; + +export default Opportunities; diff --git a/src/opportunities/pages/opportunities.tsx b/src/opportunities/pages/opportunities.tsx deleted file mode 100644 index cd5c9b10..00000000 --- a/src/opportunities/pages/opportunities.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import OpportunitiesList from "../components/opportunitiesDetails.tsx"; - - -export default function Opportunities() { - - - // returning opportunities component on (currently not in use) opportunities page - return ( -
- -
- ); -}; diff --git a/src/staff/components/Checkbox.tsx b/src/shared/components/Checkbox.tsx similarity index 56% rename from src/staff/components/Checkbox.tsx rename to src/shared/components/Checkbox.tsx index a43760ff..605b625e 100644 --- a/src/staff/components/Checkbox.tsx +++ b/src/shared/components/Checkbox.tsx @@ -1,7 +1,26 @@ import React from "react"; -import PropTypes from "prop-types"; +import { FieldErrors, UseFormRegisterReturn } from "react-hook-form"; +import { Filters } from "../../types/opportunities.ts"; -const CheckBox = ({ +interface CheckBoxProps { + formHook: UseFormRegisterReturn; + errors: FieldErrors<{ + semesters: never[]; + years: never[]; + credits: never[]; + hourlyPay: string; + majors: never[]; + }>; + errorMessage: string; + name: string; + label: string; + options: string[]; + type?: "checkbox" | "radio"; + filters?: Filters +}; + + +export default function CheckBox({ formHook, errors, errorMessage, @@ -9,14 +28,15 @@ const CheckBox = ({ label, options, type, -}) => { + filters, +}: CheckBoxProps) { return (
{label}
- {errors && errors[name] && ( + {errors && errors[name as keyof typeof errors] && (

{errorMessage}

)} @@ -33,6 +53,7 @@ const CheckBox = ({ {...formHook} id={item} className={type === "radio" ? "radio" : "checkbox"} + defaultChecked={name === "semesters" && filters?.semesters?.includes(item) || name === "years" && filters?.years?.includes(item) || name === "credits" && filters?.credits?.includes(item) ? true : false} />
@@ -43,14 +64,3 @@ const CheckBox = ({
); }; -CheckBox.propTypes = { - formHook: PropTypes.object, - errors: PropTypes.object, - errorMessage: PropTypes.string, - name: PropTypes.string.isRequired, - label: PropTypes.string, - options: PropTypes.arrayOf(PropTypes.string), - type: PropTypes.string, -}; - -export default CheckBox; diff --git a/src/shared/components/Navigation/MainNavigation.tsx b/src/shared/components/Navigation/MainNavigation.tsx index 62131eea..990494d6 100644 --- a/src/shared/components/Navigation/MainNavigation.tsx +++ b/src/shared/components/Navigation/MainNavigation.tsx @@ -11,7 +11,7 @@ export default function MainNavigation() { const location = useLocation().pathname; const routes = auth.isAuthenticated ? [ - { name: "Jobs", href: "/jobs", current: true }, + { name: "Opportunities", href: "/opportunities", current: true }, { name: "Create", href: "/create", current: false }, { name: "Staff", href: "/staff", current: false }, { name: "Profile", href: "/profile", current: false }, diff --git a/src/shared/components/Navigation/PageNavigation.js b/src/shared/components/Navigation/PageNavigation.js deleted file mode 100644 index 9441d69e..00000000 --- a/src/shared/components/Navigation/PageNavigation.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; -/* -pages = { - pages: [Saved, Search] - active: "Saved" -} - -usePageNavigation([Saved, Search, Bookmarks], active) - -=> switchPage(string) - activePage - -*/ - -const PageNavigation = ({ title, pages, switchPage }) => { - const activeLink = "active-link"; - const normalLink = "normal-link hover:border-b-2 hover:text-black"; - - return ( -
-

{title}

- - -
- ); -}; - -export default PageNavigation; diff --git a/src/shared/components/Navigation/StickyFooter.tsx b/src/shared/components/Navigation/StickyFooter.tsx index 4ba8341b..444ba11d 100644 --- a/src/shared/components/Navigation/StickyFooter.tsx +++ b/src/shared/components/Navigation/StickyFooter.tsx @@ -8,12 +8,12 @@ export default function StickyFooter() { const routes = auth.isAuthenticated ? [ - { name: "Jobs", href: "/jobs", current: true }, - { name: "Create", href: "/create", current: false }, - { name: "Staff", href: "/staff", current: false }, - { name: "Profile", href: "/profile", current: false }, - { name: "Sign Out", href: "/signout", current: false }, - ] + { name: "Opportunities", href: "/opportunities", current: true }, + { name: "Create", href: "/create", current: false }, + { name: "Staff", href: "/staff", current: false }, + { name: "Profile", href: "/profile", current: false }, + { name: "Sign Out", href: "/signout", current: false }, + ] : [{ name: "Sign In", href: "/signin", current: false }]; return ( diff --git a/src/shared/hooks/page-navigation-hook.js b/src/shared/hooks/page-navigation-hook.js deleted file mode 100644 index cd0e5dfd..00000000 --- a/src/shared/hooks/page-navigation-hook.js +++ /dev/null @@ -1,15 +0,0 @@ -import { useState } from "react"; - -const usePageNavigation = (views, activeView) => { - var [pages, setPages] = useState({ pages: views, activePage: activeView }); - - const switchPage = (view) => { - setPages(() => { - return { ...pages, activePage: view }; - }); - }; - - return [pages, switchPage]; -}; - -export default usePageNavigation; diff --git a/src/shared/hooks/page-navigation-hook.ts b/src/shared/hooks/page-navigation-hook.ts new file mode 100644 index 00000000..d910c4b7 --- /dev/null +++ b/src/shared/hooks/page-navigation-hook.ts @@ -0,0 +1,22 @@ +import { useState } from "react"; + +interface PagesState { + pages: string[]; + activePage: string; +} + +type SwitchPage = (view: string) => void; + +const usePageNavigation = (views: string[], activeView: string): [PagesState, SwitchPage] => { + const [pages, setPages] = useState({ pages: views, activePage: activeView }); + + const switchPage: SwitchPage = (view) => { + setPages(() => { + return { ...pages, activePage: view }; + }); + }; + + return [pages, switchPage]; +}; + +export default usePageNavigation; diff --git a/src/shared/pages/Home.tsx b/src/shared/pages/Home.tsx index 9e130c7f..f58fe1cb 100644 --- a/src/shared/pages/Home.tsx +++ b/src/shared/pages/Home.tsx @@ -5,7 +5,7 @@ import SEO from "../components/SEO.tsx"; const Home = () => { const aboutSectionRef = useRef(null); - + // State for the Contact Us form const [contactForm, setContactForm] = useState({ name: "", @@ -45,10 +45,10 @@ const Home = () => {

If you are a student, go to the{" "} - Jobs + Opportunities {" "} tab to view currently available research opportunities.
diff --git a/src/staff/components/CreationForms.tsx b/src/staff/components/CreationForms.tsx index 1c516235..c6b3360d 100644 --- a/src/staff/components/CreationForms.tsx +++ b/src/staff/components/CreationForms.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { useForm } from "react-hook-form"; import { useEffect } from "react"; -import CheckBox from "./Checkbox.tsx"; +import CheckBox from "../../shared/components/Checkbox.tsx"; import Input from "./Input"; import { useParams } from "react-router"; import { Locations } from "../../shared/data/locations.ts"; diff --git a/src/types/opportunities.ts b/src/types/opportunities.ts new file mode 100644 index 00000000..229fd275 --- /dev/null +++ b/src/types/opportunities.ts @@ -0,0 +1,21 @@ +export type Filters = { + years: number[]; + credits: string[]; + hourlyPay: number; + majors: string[]; +}; + +export type OpportunityList = { + id: number; + name: string; + description: string; + recommended_experience: string; + pay: number; + credits: string; + semester: string; + year: number; + application_due: Date; + location: string; + lab_managers: string + saved: boolean; +} \ No newline at end of file