diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 25e6bc5c..4dba9d21 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -7,6 +7,8 @@ on: - "**.jsx" - "**.ts" - "**.tsx" + - "package.json" + - "package-lock.json" jobs: build: diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index d1dc50a3..a2e7a67d 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -23,6 +23,6 @@ jobs: - name: Run EsLint uses: sibiraj-s/action-eslint@v3 with: - eslint-args: "src/**/*.{js,jsx,ts,tsx} --max-warnings=0" - extensions: "js,jsx,ts,tsx" + eslint-args: "src/**/*.{ts,tsx} --max-warnings=0" + extensions: "ts,tsx" annotations: true diff --git a/.gitignore b/.gitignore index 09a458ff..fe1ae567 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules build .env +.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9ebced5e..559657b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 node:22-alpine3.20 +FROM --platform=linux/amd64 node:22.9.0-alpine3.20 WORKDIR /app @@ -6,7 +6,7 @@ COPY build /app/build RUN npm install -g serve HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://0.0.0.0:3000 || exit 1 + CMD wget --no-verbose --tries=1 --spider http://0.0.0.0:3000/health || exit 1 EXPOSE 3000 diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..68ac1bcf --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright Copyright 2023 Rafael Cenzano + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/docs/react_hooks.txt b/docs/react_hooks.txt new file mode 100644 index 00000000..f364551b --- /dev/null +++ b/docs/react_hooks.txt @@ -0,0 +1,125 @@ +Introduction to React Hooks + +Notes based on https://www.youtube.com/watch?v=LlvBzyy-558 + +UseState + const [counter, setCounter] = useState(initial_val); + + -counter is a variable that is immutable unless setCounter is used + -i.e. setCounter(counter + 1) tied to an onClick button increments counter when button is pressed + -this updates the webpage when counter is changed instead of only rendering the var whern first loaded + + +UseReducer + const [state, dispatch] = useReducer(reducer, {var1: val, var2: val... etc.}); + const reducer = (state, action) => { + switch(action.type) { + case X: + code; + return {var1: state.var1, var2: state.var2...} + case Y: + code; + return {var1: state.var1, var2: state.var2...} + default: + return state; + } + } + + -useReducer is used as a multivariable useState, reducer is a function that changes the variables + -state.varX = current value of varX, returns in reducer return every variable even if unchanged + -can call case actions in reducer by using: dispath({type: "case"}) where case is the action name + + +UseEffect + useEffect(( =>) { + function; + },[list of states to listen to]); + + -useEffect is called whenever the page rerenders, including refreshes and state changes + -useful with useState for sending response out + -i.e. data, setData useState and data gets set to response in useEffect + + +UseRef + const nameRef - useRef(null); + + -adding ref={nameRef} as an attr of an input lets you access the current input + -useful for accessing attributes of the element + -can access current value of the input is accessed with: nameRef.current.value; + -can focus on an input field using: nameRef.current.focus(); + -can empty input field using: inputRef.current.value = ""; + -useful for input submissions, signIn page? + + +UseLayoutEffect + useLayoutEffect(( =>) { + function; + },[list of states to listen to]); + + -called before page renders + -can be useful for changes layout of application before being presented to user + -otherwise similar to useEffect + + +UseImperativeHandle + CHILD.js + import React, { forwardRef, useImperativeHandle } from "react"; + const Child = forwardRef((ref) => { + useImperativeHandle(ref, () => ({ + FUNC_NAME() { + function; + }, + })); + + code; + }) + + PARENT.js + function Parent() { + const childRef = useRef(null); + return ( + + - ) - )} - - - - - - - -
- {navigation.map((item) => ( - - {item.name} - - ))} -
-
- - )} - - ); -} diff --git a/src/shared/components/Navigation/MainNavigation.tsx b/src/shared/components/Navigation/MainNavigation.tsx new file mode 100644 index 00000000..62131eea --- /dev/null +++ b/src/shared/components/Navigation/MainNavigation.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { Disclosure } from "@headlessui/react"; +import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline"; +import { Link, NavLink, useLocation } from "react-router-dom"; +import { useAuth } from "../../../context/AuthContext.tsx"; + +export default function MainNavigation() { + + const { auth } = useAuth(); + + const location = useLocation().pathname; + 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: "Sign In", href: "/signin", current: false }]; + + return ( + + {({ open }) => ( + <> +
+
+
+ {/* Mobile menu button*/} + + + Open main menu + {open ? ( + +
+
+
+ + LabConnect + +
+
+
+ {routes.map((item) => ( + + {item.name} + + ))} +
+
+
+
+
+ + +
+ {routes.map((item) => ( + + {item.name} + + ))} +
+
+ + )} +
+ ); +} diff --git a/src/shared/components/Navigation/StickyFooter.js b/src/shared/components/Navigation/StickyFooter.js deleted file mode 100644 index 67f4ce96..00000000 --- a/src/shared/components/Navigation/StickyFooter.js +++ /dev/null @@ -1,47 +0,0 @@ -import { Link } from "react-router-dom"; -import logo from "../../../images/LabConnect_Logo.png"; -console.log(logo); - -const StickyFooter = () => { - return ( -
-

- Made by RCOS -

-
-
- Logo -
- -
-

- Contact Us -

- Discord
- GitHub
-

-

-
-
- Student Pages -

- Jobs
- Staff
- Sign In
-

-
-
- Staff Pages -

- Profile
- Create
- Jobs
- Sign In
-

-
-
-
- ) -}; - -export default StickyFooter; diff --git a/src/shared/components/Navigation/StickyFooter.tsx b/src/shared/components/Navigation/StickyFooter.tsx new file mode 100644 index 00000000..196bb07d --- /dev/null +++ b/src/shared/components/Navigation/StickyFooter.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import logo from "../../../images/LabConnect_Logo.webp"; +import { useAuth } from "../../../context/AuthContext.tsx"; + +export default function StickyFooter() { + + const { auth } = useAuth(); + + 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: "Sign In", href: "/signin", current: false }]; + + return ( +
+

+ Made by{" "} + + RCOS + +

+
+
+ LabConnect +
+ +
+
+ Contact Us +

+ + Discord + +
+ + GitHub + +
+

+
+
+
+ Resources +
+ {routes.map((item) => ( + + + {item.name} + +
+
+ ))} +
+
+
+ ); +} diff --git a/src/shared/components/UIElements/ProfileAvatar.js b/src/shared/components/Profile/ProfileAvatar.tsx similarity index 71% rename from src/shared/components/UIElements/ProfileAvatar.js rename to src/shared/components/Profile/ProfileAvatar.tsx index d6976e4e..a3e80742 100644 --- a/src/shared/components/UIElements/ProfileAvatar.js +++ b/src/shared/components/Profile/ProfileAvatar.tsx @@ -1,6 +1,6 @@ import React from "react"; -const ProfileAvatar = ({image, name}) => { +const ProfileAvatar: React.FC<{ image: string, name: string }> = ({ image, name }) => { return (
diff --git a/src/shared/components/Profile/ProfileComponents.tsx b/src/shared/components/Profile/ProfileComponents.tsx new file mode 100644 index 00000000..d09c1e1a --- /dev/null +++ b/src/shared/components/Profile/ProfileComponents.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import ProfileAvatar from "./ProfileAvatar.tsx"; +import ProfileDescription from "./ProfileDescription.tsx"; +import ProfileOpportunities from "./ProfileOpportunities.tsx"; +import SEO from "..//SEO.tsx"; +import Breadcrumb from "../UIElements/Breadcrumb.tsx"; + +interface Profile { + name: string; + image: string; + department: string; + description: string; + website?: string; +} + +const ProfileComponents = ({ profile, id, staff }: { profile: Profile, id: string, staff: boolean }) => { + return ( + <> + + {staff && } +
+
+ + +
+ {id && } +
+ + ); +} + +export default ProfileComponents; diff --git a/src/shared/components/Profile/ProfileDescription.tsx b/src/shared/components/Profile/ProfileDescription.tsx new file mode 100644 index 00000000..fa25d1ad --- /dev/null +++ b/src/shared/components/Profile/ProfileDescription.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Link } from "react-router-dom"; + +const ProfileDescription = ({ name, department, description, website }) => { + return ( +
+

{name}

+
{department}
+

{description}

+ {website && website.length && ( + + {website} + + )} +
+ ); +}; + +ProfileDescription.propTypes = { + name: PropTypes.string.isRequired, + department: PropTypes.string, + description: PropTypes.string.isRequired, + website: PropTypes.string, +}; + +export default ProfileDescription; diff --git a/src/shared/components/Profile/ProfileOpportunities.js b/src/shared/components/Profile/ProfileOpportunities.js deleted file mode 100644 index 1ff23373..00000000 --- a/src/shared/components/Profile/ProfileOpportunities.js +++ /dev/null @@ -1,167 +0,0 @@ -import React from "react"; -import OpportunityActionCard from "./OpportunityActionCard"; -import { useState, useEffect } from "react"; - -const DUMMY_DATA = { - d1: [ - { - title: "Software Intern", - body: "Posted February 8, 2024", - attributes: ["Remote", "Paid", "Credits"], - activeStatus: true, - id: "o1", - }, - // create dummy data for the opportunities - { - title: "Biology Intern", - body: "Due February 2, 2024", - attributes: ["Paid", "Credits"], - activeStatus: true, - id: "o2", - }, - { - title: "Physics Intern", - body: "Due February 6, 2024", - attributes: ["Remote", "Paid", "Credits"], - activeStatus: true, - id: "o3", - }, - { - title: "Chemistry Intern", - body: "Due February 15, 2023", - attributes: ["Remote", "Paid", "Credits"], - activeStatus: true, - id: "o4", - }, - { - title: - "Mathematics Intern For the Sciences and Engineering Mathematics Intern For the Sciences and Engineering", - body: "Due February 1, 2024", - attributes: ["Remote", "Paid", "Credits"], - activeStatus: true, - id: "o5", - }, - ], -}; - -const ProfileOpportunities = ({ id }) => { - var [opportunities, setOpportunities] = useState(false); - - const fetchOpportunities = async (key) => { - // Consider moving the base URL to a configuration - const baseURL = `${process.env.REACT_APP_BACKEND_SERVER}`; - const url = `${baseURL}/getProfileOpportunities/${key}`; - - const response = await fetch(url); - - if (!response.ok) { - return false; - } - - const data = await response.json(); - console.log(data); - return data["data"]; - }; - - async function setData(key) { - const response = await fetchOpportunities(key); - response && setOpportunities(response); - response || setOpportunities("no response"); - } - - async function changeOpportunityActiveStatus(opportunityId, activeStatus) { - // send a request to the backend to deactivate the opportunity - // if the request is successful, then deactivate the opportunity from the list - const url = `${process.env.REACT_APP_BACKEND_SERVER}/changeActiveStatus`; - console.log(opportunities); - - const jsonData = { - oppID: opportunityId, - setStatus: !activeStatus, - authToken: "authTokenHere", - }; - - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(jsonData), - }); - - if (response.ok) { - const data = await response.json(); - - const newOpportunities = opportunities.map((opportunity) => - opportunity.id === opportunityId - ? { ...opportunity, activeStatus: data.activeStatus } // Spread operator for update - : opportunity, - ); - - setOpportunities(newOpportunities); - } - } - - async function deleteOpportunity(opportunityId) { - // send a request to the backend to delete the opportunity - // if the request is successful, then delete the opportunity from the list - - const url = `${process.env.REACT_APP_BACKEND_SERVER}/deleteOpportunity`; - - const jsonData = { id: opportunityId }; - - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(jsonData), - }); - - if (response) { - opportunities = opportunities.filter( - (opportunity) => opportunity.id !== opportunityId, - ); - } else { - alert("Failed to delete opportunity"); - } - - setOpportunities(opportunities); - console.log(opportunities); - } - - useEffect(() => { - setData(id); - }, []); - - var opportuntityList = ( -
-

Posted Opportunties

-
- {opportunities && - opportunities.map((opportunity) => ( - - ))} -
-
- ); - - return opportunities ? ( - opportuntityList - ) : opportunities === "no response" ? ( - "No opportunities found" - ) : ( - - ); -}; - -export default ProfileOpportunities; diff --git a/src/shared/components/Profile/ProfileOpportunities.tsx b/src/shared/components/Profile/ProfileOpportunities.tsx new file mode 100644 index 00000000..cc659444 --- /dev/null +++ b/src/shared/components/Profile/ProfileOpportunities.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import LargeTextCard from "../UIElements/LargeTextCard.tsx"; +import { useState, useEffect } from "react"; +import { useAuth } from "../../../context/AuthContext.tsx"; + +export default function ProfileOpportunities({ id, staff }: { id: string, staff: boolean }) { + const { auth } = useAuth(); + const [opportunities, setOpportunities] = useState | null | "no response">(null); + + useEffect(() => { + async function setData() { + const response = await fetch( + `${process.env.REACT_APP_BACKEND_SERVER}/${staff ? "staff" : "profile"}/opportunities/${id}`, { + headers: { + Authorization: `Bearer ${auth.token}`, + }, + } + ); + + if (response.ok) { + const data = await response.json(); + setOpportunities(data); + } else { + setOpportunities("no response"); + } + } + + setData(); + }, [auth.token, id, staff]); + + const opportunityList = ( +
+ {id && + Array.isArray(opportunities) && + opportunities.map((opportunity) => ( + + ))} +
+ ); + + return ( +
+

Posted Opportunties

+ {opportunities !== null ? opportunityList : "Loading..."} + {opportunities === "no response" && "No Opportunities Found"} +
+ ); +}; diff --git a/src/shared/components/SEO.tsx b/src/shared/components/SEO.tsx new file mode 100644 index 00000000..f1b45f6e --- /dev/null +++ b/src/shared/components/SEO.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Helmet } from 'react-helmet-async'; + +import PropTypes from 'prop-types'; + +export default function SEO({ title, description }) { + return ( + + { /* Standard metadata tags */} + {title} + + + + + + { /* Facebook tags */} + + + { /* Twitter tags */} + + + { /* Google+ tags */} + + + + ) +} + +SEO.propTypes = { + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/src/shared/components/UIElements/Avatar.js b/src/shared/components/UIElements/Avatar.tsx similarity index 69% rename from src/shared/components/UIElements/Avatar.js rename to src/shared/components/UIElements/Avatar.tsx index 6392f84f..a9939a90 100644 --- a/src/shared/components/UIElements/Avatar.js +++ b/src/shared/components/UIElements/Avatar.tsx @@ -1,12 +1,11 @@ import React from "react"; -const Avatar = ({ img, name, role, className }) => { +const Avatar: React.FC<{ img: string, name: string, className?: string }> = ({ img, name, className = "" }) => { return (
{name}
{name}
-
{role}
); diff --git a/src/shared/components/UIElements/AvatarCard.js b/src/shared/components/UIElements/AvatarCard.js deleted file mode 100644 index 06a726fb..00000000 --- a/src/shared/components/UIElements/AvatarCard.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import Avatar from "./Avatar"; - -const AvatarCard = ({ img, name, role, className }) => { - return ( -
- -
- ); -}; - -export default AvatarCard; diff --git a/src/shared/components/UIElements/AvatarCard.tsx b/src/shared/components/UIElements/AvatarCard.tsx new file mode 100644 index 00000000..6504030a --- /dev/null +++ b/src/shared/components/UIElements/AvatarCard.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import Avatar from "./Avatar.tsx"; + +const AvatarCard: React.FC<{ img: string, name: string, className?: string }> = ({ img, name, className }) => { + return ( +
+ +
+ ); +}; + +export default AvatarCard; diff --git a/src/shared/components/UIElements/Breadcrumb.js b/src/shared/components/UIElements/Breadcrumb.tsx similarity index 59% rename from src/shared/components/UIElements/Breadcrumb.js rename to src/shared/components/UIElements/Breadcrumb.tsx index 255511f5..6981b854 100644 --- a/src/shared/components/UIElements/Breadcrumb.js +++ b/src/shared/components/UIElements/Breadcrumb.tsx @@ -1,16 +1,6 @@ import React from "react"; import { Link } from "react-router-dom"; - -const LINK_TREE = [ - { - link: "/staff", - title: "Staff", - }, - { - link: "/center/CBIS", - title: "Staff", - }, -]; +import PropTypes from "prop-types"; const Breadcrumb = ({ tree }) => { return ( @@ -18,7 +8,7 @@ const Breadcrumb = ({ tree }) => {
    {tree.map((item) => { return ( -
  • +
  • {item.title}
  • ); @@ -27,5 +17,13 @@ const Breadcrumb = ({ tree }) => {
); }; +Breadcrumb.propTypes = { + tree: PropTypes.arrayOf( + PropTypes.shape({ + link: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + }) + ).isRequired, +}; export default Breadcrumb; diff --git a/src/shared/components/UIElements/Error.tsx b/src/shared/components/UIElements/Error.tsx new file mode 100644 index 00000000..58fe0665 --- /dev/null +++ b/src/shared/components/UIElements/Error.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +const ErrorComponent: React.FC<{ message: string }> = ({ message }) => { + return ( +
+

{message}

+

If this issue persists contact the site administrators

+ Go back to the main page +
+ ); +} + +export default ErrorComponent; diff --git a/src/shared/components/UIElements/LargeTextCard.tsx b/src/shared/components/UIElements/LargeTextCard.tsx new file mode 100644 index 00000000..df6158c9 --- /dev/null +++ b/src/shared/components/UIElements/LargeTextCard.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import PropTypes from "prop-types"; + +const LargeTextCard = ({ to, title, due, pay, credits }) => { + return ( + +
+
+

100 ? "text-sm" : "text-lg font-bold" + } p-0 m-0`} + > + {title} +

+

Due {due}

+ {pay &&

Pay ${pay}

} + {credits &&

{credits}

} +
+
+ + ); +}; + +LargeTextCard.propTypes = { + to: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + due: PropTypes.string.isRequired, + pay: PropTypes.string, + credits: PropTypes.string, +}; + +export default LargeTextCard; diff --git a/src/shared/data/locations.ts b/src/shared/data/locations.ts new file mode 100644 index 00000000..4cdd4c45 --- /dev/null +++ b/src/shared/data/locations.ts @@ -0,0 +1,25 @@ +export const Locations = [ + "TBD", + "Amos Eaton", + "Carnegie", + "Center for Biotechnology and Interdisciplinary Studies", + "Center for Computational Innovations", + "Low Center for Industrial Innovation (CII)", + "Cogswell Laboratory", + "Darrin Communications Center", + "Experimental Media and Performing Arts Center", + "Greene Library", + "Jonsson Engineering Center", + "Jonsson-Rowland Science Center", + "Lally Hall", + "LINAC Facility (Gaerttner Laboratory)", + "Materials Research Center", + "Pittsburgh Building", + "Ricketts Building", + "Russell Sage Laboratory", + "Voorhees Computing Center", + "Walker Laboratory", + "West Hall", + "Winslow Building", + "Remote" +] \ No newline at end of file diff --git a/src/shared/pages/404.js b/src/shared/pages/404.js deleted file mode 100644 index a72d63be..00000000 --- a/src/shared/pages/404.js +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const PageNotFound = () => { - return

Error: Page Not Found

; -} - -export default PageNotFound \ No newline at end of file diff --git a/src/shared/pages/404.tsx b/src/shared/pages/404.tsx new file mode 100644 index 00000000..87716390 --- /dev/null +++ b/src/shared/pages/404.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import SEO from "../components/SEO.tsx"; + +const PageNotFound = () => { + return ( + <> + +

Error: Page Not Found

+ + ); +}; + +export default PageNotFound; diff --git a/src/shared/pages/EditProfile.js b/src/shared/pages/EditProfile.js index 92143951..f4a6b102 100644 --- a/src/shared/pages/EditProfile.js +++ b/src/shared/pages/EditProfile.js @@ -1,16 +1,34 @@ import React from "react"; -import ProfileAvatar from "../components/UIElements/ProfileAvatar"; +import ProfileAvatar from "../components/UIElements/ProfileAvatar.tsx"; import EditInformation from "../components/Profile/EditInformation"; -const EditProfile = ({id, name, department, researchCenter, description, email, role, image}) => { - - return
+const EditProfile = ({ + id, + name, + department, + researchCenter, + description, + email, + role, + image, +}) => { + return ( +
- +
-
+ ); }; -export default EditProfile; \ No newline at end of file +export default EditProfile; diff --git a/src/shared/pages/Home.js b/src/shared/pages/Home.js deleted file mode 100644 index 855f03fd..00000000 --- a/src/shared/pages/Home.js +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useEffect } from "react"; -import useAuthActions from "../../context/global/authActions"; -import { Link } from "react-router-dom"; -import StickyFooter from "../components/Navigation/StickyFooter.js"; - -import logo from "../../images/LabConnect_Logo.png"; -console.log(logo); - -const Home = ({signOut, signIn}) => { - - const {login, logout} = useAuthActions(); - - useEffect(() => { - if (signOut) { - logout(); - } - - if (signIn) { - login(); - } - }, []); - - return ( -
-
-


-
- Logo - -
- -



- -

- Welcome to LabConnect! -

-

- If you are a student, go to the Jobs tab to view currently available research opportunities.
- If you are a professor or staff member, Sign In and then go to Create to start posting
- opportunities or Profile to view and edit your current posts. -

-


-
-
- ); -}; - -export default Home; diff --git a/src/shared/pages/Home.tsx b/src/shared/pages/Home.tsx new file mode 100644 index 00000000..3535883a --- /dev/null +++ b/src/shared/pages/Home.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import logo from "../../images/LabConnect_Logo2.webp"; +import SEO from "../components/SEO.tsx"; + +const Home = () => { + return ( +
+ +
+
+
+
+
+ LabConnect +
+ +
+
+
+
+ +

Welcome to LabConnect!

+
+

+ If you are a student, go to the{" "} + + Jobs + {" "} + tab to view currently available research opportunities. +
+ If you are a professor or staff member,{" "} + + Sign In + {" "} + and then go to{" "} + + Create + {" "} + to start posting
+ opportunities or{" "} + + Profile + {" "} + to view and edit your current posts. +

+
+
+
+
+
+ ); +}; + +export default Home; diff --git a/src/shared/pages/Profile.js b/src/shared/pages/Profile.js deleted file mode 100644 index 2a7c59b8..00000000 --- a/src/shared/pages/Profile.js +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useEffect } from "react"; -import { useState } from "react"; -import ProfileAvatar from "../components/UIElements/ProfileAvatar"; -import ProfileDescription from "../../staff/components/ProfileDescription"; -import ProfileOpportunities from "../components/Profile/ProfileOpportunities"; -import EditProfile from "./EditProfile"; -import useGlobalContext from "../../context/global/useGlobalContext"; -import StickyFooter from "../components/Navigation/StickyFooter.js"; - -const PROFILES = { - d1: { - name: "Peter Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - researchCenter: "Computational Fake Center", - department: "Computer Science", - email: "johnp@rpi.edu", - role: "admin", - description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut - pharetra sit amet aliquam id diam maecenas ultricies mi. Montes - nascetur ridiculus mus mauris vitae ultricies leo. Porttitor massa - id neque aliquam. Malesuada bibendum arcu vitae elementum. Nulla - aliquet porrsus mattis molestie aiaculis at erat pellentesque. - At risus viverra adipiscing at. - Tincidunt tortor aliquam nulla facilisi cras fermentum odio eu - feugiat. Eget fUt eu sem integer vitae justo - eget magna fermentum. Lobortis feugiat vivamus at augue eget arcu - dictum. Et tortor at risus viverra adipiscing at in tellus. - Suspendisse sed nisi lacus sed viverra tellus. Potenti nullam ac - tortor vitae. Massa id neque aliquam vestibulum. Ornare arcu odio ut - sem nulla pharetra. Quam id leo in vitae turpis massa. Interdum - velit euismod in pellentesque massa placerat duis ultricies lacus. - Maecenas sed enim ut sem viverra aliquet eget sit amet. Amet - venenatis urna cursus eget nunc scelerisque viverra mauris. Interdum - varius sit amet mattis. Aliquet nec ullamcorper sit amet risus - nullam. Aliquam faucibus purus in massa tempor nec feugiat. Vitae - turpis massa sed elementum tempus. Feugiat in ante metus dictum at - tempor. Malesuada nunc vel risus commodo viverra maecenas accumsan. - Integer vitae justo.`, - }, -}; - -const ProfilePage = () => { - const [editMode, setEditMode] = useState(false); - const [profileFound, setProfileFound] = useState(false); - const [profile, setProfile] = useState(null); - - const state = useGlobalContext(); - const { loggedIn } = state; - - const changeEditMode = () => { - setEditMode(!editMode); - }; - - const { id } = state; - - const fetchProfile = async () => { - const response = await fetch( - `${process.env.REACT_APP_BACKEND_SERVER}/getProfessorProfile/${id}`, - ); - - if (response) { - let data = await response.json(); - setProfile(data); - setProfileFound(true); - } else { - setProfileFound(false); - } - }; - - useEffect(() => { - if (id) { - fetchProfile(); - } - }, []); - - var editButton = ( - - ); - - const profilePage = ( -
-
- - -
- -
- ); - - return ( -
-
- {!loggedIn ? ( - "Please log in to view your profile" - ) : profileFound ? ( - <> - {loggedIn && editButton} - {loggedIn && editMode && } - {loggedIn && !editMode && profilePage} - - ) : ( - "Profile not found" - )} -
-
-
-
-
-
-
-
-
- ); -}; - -export default ProfilePage; diff --git a/src/shared/pages/Profile.tsx b/src/shared/pages/Profile.tsx new file mode 100644 index 00000000..5859efaf --- /dev/null +++ b/src/shared/pages/Profile.tsx @@ -0,0 +1,66 @@ +import React, { useEffect } from "react"; +import { useState } from "react"; +import ProfileComponents from "../components/Profile/ProfileComponents.tsx"; +import { useAuth } from "../../context/AuthContext.tsx"; +// import EditProfile from "./EditProfile"; + +export default function ProfilePage() { + const { auth } = useAuth(); + + if (!auth.isAuthenticated) { + window.location.href = "/login"; + } + + // const [editMode, setEditMode] = useState(false); + interface Profile { + id: string; + name: string; + image: string; + department: string; + description: string; + website?: string; + } + + const [profile, setProfile] = useState(null); + + // const changeEditMode = () => { + // setEditMode(!editMode); + // }; + + useEffect(() => { + const fetchProfile = async () => { + const response = await fetch( + `${process.env.REACT_APP_BACKEND_SERVER}/profile`, { + headers: { + Authorization: `Bearer ${auth.token}`, + }, + } + ); + + if (response.ok) { + const data = await response.json(); + // if (data.lab_manager) { + // window.location.href = "/staff/" + data.id; + // } + setProfile(data); + } else { + setProfile(false); + } + }; + fetchProfile(); + }, [auth.token]); + + // const editButton = ( + // + // ); + + return ( +
+ {profile === null && "Loading..."} + {profile && typeof profile === "object" && } + {profile === false && "Profile not found"} +
+ ); +}; diff --git a/src/shared/pages/SignIn.js b/src/shared/pages/SignIn.js deleted file mode 100644 index 15de1214..00000000 --- a/src/shared/pages/SignIn.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useEffect } from "react"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import Input from "../../staff/components/Input.js"; -import StickyFooter from "../components/Navigation/StickyFooter.js"; - -import logo from "../../images/LabConnect_Logo2.png"; -console.log(logo); - -const SignIn = () => { - const [loading, setLoading] = useState(false); - - const { - register, - handleSubmit, - formState: { errors }, - reset, - } = useForm({ - defaultValues: { - rcsid: "", - password: "", - }, - }); - - var forms = ( -
-
-
-
- Logo -
-
-
{ - console.log(data); - })} - className="gap-2 px-96" - > - - - - -
-
- - -
-
-
-
-
-
-
-
-
-
-
-
- ); - - return !loading ? ( - forms - ) : loading === "no response" ? ( -

There was no response

- ) : ( - - ); -}; - -export default SignIn; diff --git a/src/staff/components/BrowseItems.js b/src/staff/components/BrowseItems.js deleted file mode 100644 index 60cf5462..00000000 --- a/src/staff/components/BrowseItems.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import LargeImageCard from "./LargeImageCard"; - -const BrowseItems = ({to, items}) => { - return ( -
- {items.map((item) => { - return ( - - ); - })} -
- ); -}; - -export default BrowseItems; diff --git a/src/staff/components/CenterHero.js b/src/staff/components/CenterHero.js deleted file mode 100644 index 877f59ac..00000000 --- a/src/staff/components/CenterHero.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import FeaturedImage from "./FeaturedImage"; -import ProfileDescription from "./ProfileDescription"; - -const CenterHero = () => { - return ( -
-
- - -
-
- ); -}; - -export default CenterHero; diff --git a/src/staff/components/CenterStaff.js b/src/staff/components/CenterStaff.js deleted file mode 100644 index e9f6727a..00000000 --- a/src/staff/components/CenterStaff.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; -import AvatarCard from "../../shared/components/UIElements/AvatarCard"; -import StaffSection from "./StaffSection"; - -const DUMMY_SECTION = { - "Artificial Intelligence": [ - { - id: "p1", - name: "Peter Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - }, - { - id: "p2", - name: "Peter Not Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - }, - ], - "Cybersecurity": [ - { - id: "p3", - name: "Peter Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - }, - { - id: "p4", - name: "Peter Not Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - }, - { - id: "p43t4", - name: "Peter Not Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - }, - ], - "Artificial Intelligence": [ - { - id: "p5", - name: "Peter Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - }, - { - id: "p6", - name: "Peter Not Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - }, - ], -}; - -const CenterStaff = () => { - return ( -
- {Object.keys(DUMMY_SECTION).map((key) => { - return ( - - ); - })} -
- ); -}; - -export default CenterStaff; diff --git a/src/staff/components/Checkbox.js b/src/staff/components/Checkbox.tsx similarity index 79% rename from src/staff/components/Checkbox.js rename to src/staff/components/Checkbox.tsx index cc8537d5..da90f437 100644 --- a/src/staff/components/Checkbox.js +++ b/src/staff/components/Checkbox.tsx @@ -1,4 +1,5 @@ import React from "react"; +import PropTypes from "prop-types"; const CheckBox = ({ formHook, @@ -9,10 +10,6 @@ const CheckBox = ({ options, type, }) => { - // if (!formHook) { - // return

FormHook Not Given

; - // } - return (
@@ -46,5 +43,14 @@ 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/staff/components/CreationForms.js b/src/staff/components/CreationForms.js deleted file mode 100644 index 83c32615..00000000 --- a/src/staff/components/CreationForms.js +++ /dev/null @@ -1,333 +0,0 @@ -import React from "react"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { useEffect } from "react"; -import CheckBox from "./Checkbox"; -import Input from "./Input"; -import { useParams } from "react-router"; -import useGlobalContext from "../../context/global/useGlobalContext"; - -const DUMMY_DATA = { - d1: { - id: "d1", - title: "Software Intern", - department: "Computer Science", - location: "Remote", - application_due: "2024-02-08", - upfrontPay: 0, - salary: 0, - credits: 0, - description: "This is a software internship", - years: ["Freshman", "Junior", "Senior"], - }, -}; - -const CreationForms = () => { - const { postID } = useParams(); - const [loading, setLoading] = useState(false); - const state = useGlobalContext(); - const { loggedIn } = state; - const { id: authorId } = state; - - async function fetchDetails(key) { - const url = - `${process.env.REACT_APP_BACKEND_SERVER}/getOpportunityMeta/` + key; - - const response = await fetch(url); - if (!response.ok) { - return false; - } else { - const data = await response.json(); - return data.data; - } - } - - const { - register, - handleSubmit, - formState: { errors }, - reset, - getValues, - } = useForm({ - defaultValues: { - id: "", - name: "", - //department: "", - location: "", - application_due: "", - active: true, - credits: [], - description: "", - recommended_experience: "", - semester: [], - pay: 0, - years: [], - year: 2024, - }, - }); - - async function fetchData(key) { - const response = await fetchDetails(key); - response && reset(response); - - response ? setLoading(false) : setLoading("no response"); - console.log(getValues()); - } - - useEffect(() => { - postID && setLoading(true); - postID && fetchData(postID); - }, []); - - const createOpportunity = async (data) => { - const url = `${process.env.REACT_APP_BACKEND_SERVER}/createOpportunity`; - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - console.log("Failed to create opportunity"); - } else { - console.log("Opportunity created"); - - // redirect to the profile page - window.location.href = "/profile"; - } - }; - - const updateOpportunity = async (data) => { - const url = `${process.env.REACT_APP_BACKEND_SERVER}/editOpportunity`; - - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - console.log("Failed to edit opportunity"); - } else { - console.log("Opportunity edited"); - - // redirect to the profile page - window.location.href = "/profile"; - } - }; - - const submitHandler = (data) => { - // convert pay and credits to numbers - const { id } = state; - - data.pay = +data.pay; - - data.active = true; - - console.log({ ...data, authorID: id }); - - // send data to the backend - !postID && createOpportunity({ ...data, authorID: id }); - postID && updateOpportunity({ ...data, authorID: id }); - }; - - var forms = ( -
{ - submitHandler(data); - })} - className="flex2 gap-2" - > - {/* */} - - - - - - - - - - - - {/* */} - - - - - - - - - - - - -
- -
- - ); - - return !loading ? ( - forms - ) : loading === "no response" ? ( -

There was no response

- ) : ( - - ); -}; - -export default CreationForms; diff --git a/src/staff/components/CreationForms.tsx b/src/staff/components/CreationForms.tsx new file mode 100644 index 00000000..3a202853 --- /dev/null +++ b/src/staff/components/CreationForms.tsx @@ -0,0 +1,324 @@ +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { useEffect } from "react"; +import CheckBox from "./Checkbox.tsx"; +import Input from "./Input"; +import { useParams } from "react-router"; +import { useAuth } from "../../context/AuthContext.tsx"; +import { Locations } from "../../shared/data/locations.ts"; + + +interface CreationFormsProps { + edit: boolean; +} + +export default function CreationForms({ edit }: CreationFormsProps) { + const { auth } = useAuth(); + const { postID } = useParams(); + const [loading, setLoading] = useState(false); + const [compensationType, setCompensationType] = useState("For Pay"); // Manage the state for "For Pay" or "For Credit" + const [years, setYears] = useState([]); + + async function fetchYears() { + const response = await fetch(`${process.env.REACT_APP_BACKEND_SERVER}/years`); + + if (response.ok) { + const data = await response.json(); + setYears(data); + } else { + console.log("No response for years"); + setLoading("no response"); + } + } + + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm({ + defaultValues: { + id: "", + title: "", + application_due: "", + type: "For Pay", // Default to "For Pay" + hourlyPay: 0, + credits: [], + description: "", + recommended_experience: "", + location: "", + years: [""], + }, + }); + + interface FormData { + id: string; + title: string; + application_due: string; + type: string; + hourlyPay: number; + credits: string[]; + description: string; + recommended_experience: string; + location: string; + years: string[]; + } + + function submitHandler(data: FormData) { + console.log({ ...data }); + if (edit) { + fetch(`${process.env.REACT_APP_BACKEND_SERVER}/editOpportunity/${postID}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${auth.token}`, + }, + body: JSON.stringify({ ...data }), + }).then((response) => { + if (response.ok) { + alert("Successfully updated"); + window.location.href = `/opportunity/${postID}`; + } else { + alert("Failed to update"); + } + }); + } else { + fetch(`${process.env.REACT_APP_BACKEND_SERVER}/createOpportunity`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${auth.token}`, + }, + body: JSON.stringify({ ...data }), + }).then((response) => { + if (response.ok) { + alert("Successfully created"); + response.json().then((data_response) => { + window.location.href = `/opportunity/${data_response["id"]}`; + }); + } else { + alert("Failed to create"); + console.log(response); + } + }); + } + }; + + useEffect(() => { + async function fetchEditData() { + const response = await fetch( + `${process.env.REACT_APP_BACKEND_SERVER}/editOpportunity/${postID}`, { + headers: { + Authorization: `Bearer ${auth.token}`, + }, + } + ); + if (response.ok) { + const { id, title, application_due, type, hourlyPay, credits, description, recommended_experience, location, years } = await response.json(); + await Promise.all([fetchYears()]); + reset({ + id, + title, + application_due, + type, + hourlyPay, + credits, + description, + recommended_experience, + location, + years, + }); + setLoading(false); + } else { + console.log("No response"); + setLoading("no response"); + } + } + + fetchYears(); + if (edit) { + fetchEditData(); + } else { + setLoading(false); + } + }, [edit, auth.token, postID, reset]); + + return loading === false && years != null ? ( +
{ + submitHandler(data); + })} + className="form-container" // Form container for vertical layout + > + {/* Group 1: Horizontal layout for Title, Location, Due Date */} +
+ + + + + +
+ + {/* Compensation Type Section with Rectangular Box */} +
+ +
+ setCompensationType("For Pay")} + /> + +
+
+ setCompensationType("For Credit")} + /> + +
+
+ setCompensationType("Any")} + /> + +
+
+ + {/* Conditionally Render Pay Input or Credit Checkboxes */} +
+ {compensationType === "For Pay" || compensationType === "Any" ? ( + + ) : null} + + {compensationType === "For Credit" || compensationType === "Any" ? ( + + ) : null} +
+ + {/* Class Year and Description aligned horizontally */} +
+ + + + + +
+ + {/* Submit button */} +
+ +
+
+ ) : loading === "no response" ? ( +

There was no response

+ ) : ( +

Loading...

+ ); +}; \ No newline at end of file diff --git a/src/staff/components/DepartmentHeading.tsx b/src/staff/components/DepartmentHeading.tsx new file mode 100644 index 00000000..47b17969 --- /dev/null +++ b/src/staff/components/DepartmentHeading.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Link } from "react-router-dom"; + +const DepartmentHeading = ({ name, description, image, website }) => { + return ( +
+
+
+ {`${name} +
+

{name}

+

{description}

+ + {website} + +
+
+ ); +}; + +DepartmentHeading.propTypes = { + name: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + image: PropTypes.string.isRequired, + website: PropTypes.string, +}; +export default DepartmentHeading; diff --git a/src/staff/components/DepartmentItems.tsx b/src/staff/components/DepartmentItems.tsx new file mode 100644 index 00000000..02b07f54 --- /dev/null +++ b/src/staff/components/DepartmentItems.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import PropTypes from "prop-types"; +import LargeImageCard from "./LargeImageCard"; + +const DepartmentItems = ({ items }) => { + return ( +
+ {items.map((item) => { + return ( + + ); + })} +
+ ); +}; + +DepartmentItems.propTypes = { + items: PropTypes.arrayOf( + PropTypes.shape({ + school_id: PropTypes.string.isRequired, + department_id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + image: PropTypes.string.isRequired, + }) + ).isRequired, +}; + +export default DepartmentItems; diff --git a/src/staff/components/DepartmentStaff.tsx b/src/staff/components/DepartmentStaff.tsx new file mode 100644 index 00000000..7c258d30 --- /dev/null +++ b/src/staff/components/DepartmentStaff.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import PropTypes from "prop-types"; +import AvatarCard from "../../shared/components/UIElements/AvatarCard.tsx"; +import { Link } from "react-router-dom"; + +const DepartmentStaff = ({ staff }) => { + return ( +
+ {staff.map((staff_member) => { + return ( + + + + ); + })} +
+ ); +}; + +DepartmentStaff.propTypes = { + staff: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + image: PropTypes.string.isRequired, + }) + ).isRequired, +}; + +export default DepartmentStaff; diff --git a/src/staff/components/FeaturedImage.js b/src/staff/components/FeaturedImage.js deleted file mode 100644 index fca95861..00000000 --- a/src/staff/components/FeaturedImage.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; - -const FeaturedImage = ({ className }) => { - return ( -
- Shoes -
- ); -}; - -export default FeaturedImage; diff --git a/src/staff/components/LargeImageCard.js b/src/staff/components/LargeImageCard.js index 1c5c173f..ee086928 100644 --- a/src/staff/components/LargeImageCard.js +++ b/src/staff/components/LargeImageCard.js @@ -1,5 +1,6 @@ import React from "react"; import { Link } from "react-router-dom"; +import PropTypes from "prop-types"; const LargeImageCard = ({ to, image, title }) => { return ( @@ -16,4 +17,10 @@ const LargeImageCard = ({ to, image, title }) => { ); }; +LargeImageCard.propTypes = { + to: PropTypes.string.isRequired, + image: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, +}; + export default LargeImageCard; diff --git a/src/staff/components/LargeTextCard.js b/src/staff/components/LargeTextCard.js deleted file mode 100644 index 4c0b0aa1..00000000 --- a/src/staff/components/LargeTextCard.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; - -const LargeTextCard = ({ to, title, body, attributes }) => { - return ( - -
-
-
- {attributes && - attributes.map((attr) => ( -
{attr}
- ))} -
-

100 ? "text-sm" : "text-lg font-bold"} p-0 m-0`}>{title}

-

{body}

-
-
- - ); -}; - -export default LargeTextCard; diff --git a/src/staff/components/ProfileDescription.js b/src/staff/components/ProfileDescription.js deleted file mode 100644 index 4ccd23d1..00000000 --- a/src/staff/components/ProfileDescription.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; - -const ProfileDescription = ({className, name, researchCenter, department, description}) => { - return ( -
-

{name}

-
- {researchCenter} {researchCenter && "ยท"} {department} -
-

{description}

-
- ); -}; - - -export default ProfileDescription; \ No newline at end of file diff --git a/src/staff/components/ProfileOpportunities.js b/src/staff/components/ProfileOpportunities.js deleted file mode 100644 index 2715582c..00000000 --- a/src/staff/components/ProfileOpportunities.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from "react"; -import LargeTextCard from "./LargeTextCard"; - -import { useState, useEffect } from "react"; - -const DUMMY_DATA = { - d1: [ - { - title: "Software Intern", - body: "Posted February 8, 2024", - attributes: ["Remote", "Paid", "Credits"], - id: "o1", - }, - // create dummy data for the opportunities - { - title: "Biology Intern", - body: "Due February 2, 2024", - attributes: ["Paid", "Credits"], - id: "o2", - }, - { - title: "Physics Intern", - body: "Due February 6, 2024", - attributes: ["Remote", "Paid", "Credits"], - id: "o3", - }, - { - title: "Chemistry Intern", - body: "Due February 15, 2023", - attributes: ["Remote", "Paid", "Credits"], - id: "o4", - }, - { - title: - "Mathematics Intern For the Sciences and Engineering Mathematics Intern For the Sciences and Engineering", - body: "Due February 1, 2024", - attributes: ["Remote", "Paid", "Credits"], - id: "o5", - }, - ], -}; - -const ProfileOpportunities = ({ id }) => { - var [opportunities, setOpportunities] = useState(false); - - const fetchOpportunities = async () => { - // Consider moving the base URL to a configuration - const baseURL = `${process.env.REACT_APP_BACKEND_SERVER}`; - const url = `${baseURL}/getProfessorOpportunityCards/${id}`; - - const response = await fetch(url); - - if (!response.ok) { - throw new Error( - `Network response was not ok - Status: ${response.status}`, - ); - } - - const data = await response.json(); - return data["data"]; - }; - - async function setData() { - const response = await fetchOpportunities(); - response && setOpportunities(response); - response || setOpportunities("no response"); - } - - useEffect(() => { - setData(); - }, []); - - var opportunityList = ( -
- {id && - opportunities && - typeof opportunities === "object" && - opportunities.map((opportunity) => ( - - ))} -
- ); - - return ( -
-

Posted Opportunties

- {opportunities ? opportunityList : "Loading..."} - {opportunities === "no response" && "No Opportunities Found"} -
- ); -}; - -export default ProfileOpportunities; diff --git a/src/staff/components/StaffSection.js b/src/staff/components/StaffSection.js deleted file mode 100644 index d32b2f10..00000000 --- a/src/staff/components/StaffSection.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import AvatarCard from "../../shared/components/UIElements/AvatarCard"; - -const StaffSection = ({ title, staff }) => { - return ( -
-

{title && title}

-
- {staff && - staff.map((employee) => { - console.log(employee); - return ( - - - - ); - })} -
-
- ); -}; - -export default StaffSection; diff --git a/src/staff/pages/Browse.js b/src/staff/pages/Browse.js deleted file mode 100644 index be847052..00000000 --- a/src/staff/pages/Browse.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from "react"; -import usePageNavigation from "../../shared/hooks/page-navigation-hook"; -import PageNavigation from "../../shared/components/Navigation/PageNavigation"; -import BrowseItems from "../components/BrowseItems"; - -/**import { useEffect, useState } from 'react'; - -const FetchGetRequest = () => { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchDataForPosts = async () => { - const response = await fetch( - `https://jsonplaceholder.typicode.com/posts?_limit=8` - ); - if (!response.ok) { - setData(null); - return
- } - let postsData = await response.json(); - setData(postsData); - setLoading(false); - }; - - fetchDataForPosts(); - }, []); - - return
; - -};*/ - -const DUMMY_DATA = { - to: "/staff", - items: [ - { - id: "d1", - title: "Computer Science", - image: - "https://www.stevens.edu/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fmviowpldu823%2F3DjsfDKUSWQBfdWMEbecCQ%2F24f09c374ddb299ee332352fd69e4042%2FSES-Computer-Science-1900862161.jpg%3Fw%3D1200%26h%3D675%26q%3D80%26fit%3Dfill&w=2400&q=80", - }, - { - id: "d2", - title: "Physics", - image: - "https://www.stevens.edu/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fmviowpldu823%2F3DjsfDKUSWQBfdWMEbecCQ%2F24f09c374ddb299ee332352fd69e4042%2FSES-Computer-Science-1900862161.jpg%3Fw%3D1200%26h%3D675%26q%3D80%26fit%3Dfill&w=2400&q=80", - }, - { - id: "d3", - title: "Biology", - image: - "https://www.stevens.edu/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fmviowpldu823%2F3DjsfDKUSWQBfdWMEbecCQ%2F24f09c374ddb299ee332352fd69e4042%2FSES-Computer-Science-1900862161.jpg%3Fw%3D1200%26h%3D675%26q%3D80%26fit%3Dfill&w=2400&q=80", - }, - { - id: "d4", - title: "Engineering", - image: - "https://www.stevens.edu/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fmviowpldu823%2F3DjsfDKUSWQBfdWMEbecCQ%2F24f09c374ddb299ee332352fd69e4042%2FSES-Computer-Science-1900862161.jpg%3Fw%3D1200%26h%3D675%26q%3D80%26fit%3Dfill&w=2400&q=80", - }, - ], -}; - -const Browse = () => { - var [pages, switchPage] = usePageNavigation( - ["Research Centers", "Departments"], - "Research Centers", - ); - return ( -
- - - - {pages.activePage === "Research Centers" && ( - - )} - - {pages.activePage === "Departments" && ( - - )} -

-
- ); -}; - -export default Browse; diff --git a/src/staff/pages/Center.js b/src/staff/pages/Center.js deleted file mode 100644 index 9a84cb33..00000000 --- a/src/staff/pages/Center.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { useParams } from "react-router"; -import Breadcrumb from "../../shared/components/UIElements/Breadcrumb"; -import CenterHero from "../components/CenterHero"; -import CenterStaff from "../components/CenterStaff"; - -const Center = ({ linkTree }) => { - const { centerName } = useParams(); - return ( -
- - - -
- ); -}; - -export default Center; diff --git a/src/staff/pages/CreatePost.js b/src/staff/pages/CreatePost.js deleted file mode 100644 index b537ad4b..00000000 --- a/src/staff/pages/CreatePost.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import CreationForms from "../components/CreationForms"; - -const CreatePost = ({ edit }) => { - return ( -
-
-

- {edit === true - ? "Edit Research Opportunity" - : "Create Research Opportunity"} -

- -
-
- ); -}; - -export default CreatePost; diff --git a/src/staff/pages/CreatePost.tsx b/src/staff/pages/CreatePost.tsx new file mode 100644 index 00000000..0fad0bc5 --- /dev/null +++ b/src/staff/pages/CreatePost.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import CreationForms from "../components/CreationForms.tsx"; +import SEO from "../../shared/components/SEO.tsx"; +import { useAuth } from "../../context/AuthContext.tsx"; + +interface CreatePostProps { + edit: boolean; +} + +export default function CreatePost({ edit }: CreatePostProps) { + const { auth } = useAuth(); + + if (!auth.isAuthenticated) { + window.location.href = "/login"; + } + + return ( +
+ +

{edit === true ? "Edit Research Opportunity" : "Create Research Opportunity"}

+ +
+ ); +}; diff --git a/src/staff/pages/Department.tsx b/src/staff/pages/Department.tsx new file mode 100644 index 00000000..9f645550 --- /dev/null +++ b/src/staff/pages/Department.tsx @@ -0,0 +1,82 @@ +import React, { useState, useEffect } from "react"; +import { useParams } from "react-router"; +import Breadcrumb from "../../shared/components/UIElements/Breadcrumb.tsx"; +import DepartmentHeading from "../components/DepartmentHeading.tsx"; +import DepartmentStaff from "../components/DepartmentStaff.tsx"; +import SEO from "../../shared/components/SEO.tsx"; +import { useAuth } from "../../context/AuthContext.tsx"; + +export default function Department() { + const { auth } = useAuth(); + + if (!auth.isAuthenticated) { + window.location.href = "/login"; + } + + const { department } = useParams(); + const [departmentstate, setDepartmentstate] = useState(false); + + useEffect(() => { + const fetchDepartment = async () => { + const response = await fetch( + `${process.env.REACT_APP_BACKEND_SERVER}/departments/${department}`, { + headers: { + Authorization: `Bearer ${auth.token}`, + }, + } + ); + + if (!response.ok) { + setDepartmentstate("not found"); + } else { + const data = await response.json(); + // Ensure each staff member has an image property + const updatedData = { + ...data, + staff: data.staff.map((member: { id: string; name: string; role: string; image?: string }) => ({ + ...member, + image: member.image || "default-image-url" // Provide a default image URL if none exists + })) + }; + setDepartmentstate(updatedData); + } + }; + fetchDepartment(); + }, [auth.token, department]); + + const departmentComponents = ( + <> + {typeof departmentstate === "object" && ( + <> + + + + )} + + ); + + return ( +
+ + + {!departmentstate && "Loading..."} + {typeof departmentstate === "object" && departmentComponents} + {departmentstate === "not found" && "Department not found"} +
+ ); +}; diff --git a/src/staff/pages/Departments.tsx b/src/staff/pages/Departments.tsx new file mode 100644 index 00000000..8235e638 --- /dev/null +++ b/src/staff/pages/Departments.tsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from "react"; +import DepartmentItems from "../components/DepartmentItems.tsx"; +import ErrorComponent from "../../shared/components/UIElements/Error.tsx"; +import SEO from "../../shared/components/SEO.tsx"; +import { useAuth } from "../../context/AuthContext.tsx"; + +export default function Departments() { + const { auth } = useAuth(); + if (!auth.isAuthenticated) { + window.location.href = "/login"; + } + + const [departments, setDepartments] = useState< + { school_id: string; department_id: string; title: string; image: string }[] | string | null + >(null); + + useEffect(() => { + const fetchDepartments = async () => { + try { + const response = await fetch( + `${process.env.REACT_APP_BACKEND_SERVER}/departments`, { + headers: { + Authorization: `Bearer ${auth.token}`, + }, + } + ); + + if (!response.ok) { + throw new Error("Departments not found"); + } + + const data = await response.json(); + setDepartments(data); + } catch { + setDepartments("Error fetching departments"); + } + }; + fetchDepartments(); + }, [auth.token]); + + const departmentComponents = ( +
+ +
+ ); + + return ( + <> + +

+ Departments +

+ {!departments && "Loading..."} + {typeof departments === "object" && departmentComponents} + {departments === "Error fetching departments" && ( + + )} + + ); +}; diff --git a/src/staff/pages/Profile.js b/src/staff/pages/Profile.js deleted file mode 100644 index 595b4630..00000000 --- a/src/staff/pages/Profile.js +++ /dev/null @@ -1,102 +0,0 @@ -import React from "react"; -import ProfileAvatar from "../../shared/components/UIElements/ProfileAvatar"; -import ProfileDescription from "../components/ProfileDescription"; -import ProfileOpportunities from "../components/ProfileOpportunities"; -import { useParams } from "react-router"; -import { useState } from "react"; -import { useEffect } from "react"; - -const DUMMY_STAFF_PROFILES = { - d1: { - name: "Peter Johnson", - image: "https://www.bu.edu/com/files/2015/08/Katz-James-3.jpg", - researchCenter: "Computational Fake Center", - department: "Computer Science", - description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut - pharetra sit amet aliquam id diam maecenas ultricies mi. Montes - nascetur ridiculus mus mauris vitae ultricies leo. Porttitor massa - id neque aliquam. Malesuada bibendum arcu vitae elementum. Nulla - aliquet porrsus mattis molestie aiaculis at erat pellentesque. - At risus viverra adipiscing at. - Tincidunt tortor aliquam nulla facilisi cras fermentum odio eu - feugiat. Eget fUt eu sem integer vitae justo - eget magna fermentum. Lobortis feugiat vivamus at augue eget arcu - dictum. Et tortor at risus viverra adipiscing at in tellus. - Suspendisse sed nisi lacus sed viverra tellus. Potenti nullam ac - tortor vitae. Massa id neque aliquam vestibulum. Ornare arcu odio ut - sem nulla pharetra. Quam id leo in vitae turpis massa. Interdum - velit euismod in pellentesque massa placerat duis ultricies lacus. - Maecenas sed enim ut sem viverra aliquet eget sit amet. Amet - venenatis urna cursus eget nunc scelerisque viverra mauris. Interdum - varius sit amet mattis. Aliquet nec ullamcorper sit amet risus - nullam. Aliquam faucibus purus in massa tempor nec feugiat. Vitae - turpis massa sed elementum tempus. Feugiat in ante metus dictum at - tempor. Malesuada nunc vel risus commodo viverra maecenas accumsan. - Integer vitae justo.`, - }, -}; - -const Profile = () => { - const { staffId } = useParams(); - var [profile, setProfile] = useState(false); - - // if (!DUMMY_STAFF_PROFILES[staffId]) { - // return "Profile Doesn't Exist"; - // } - - // const { name, image, researchCenter, department, description } = - // DUMMY_STAFF_PROFILES[staffId]; - - const checkProfile = (data) => { - return data.name && data.image && data.department && data.description; - }; - - const fetchProfile = async () => { - const response = await fetch( - `${process.env.REACT_APP_BACKEND_SERVER}/getProfessorProfile/${staffId}` - ); - - if (!response.ok) { - setProfile("not found"); - } else { - const data = await response.json(); - if (checkProfile(data)) { - setProfile(data); - } else { - setProfile("not found"); - console.log(data); - } - } - }; - - useEffect(() => { - fetchProfile(); - }, []); - - var profileComponents = ( -
-
- - -
- -
- ); - - return ( - <> - {!profile && "Loading..."} - {typeof profile === "object" && profileComponents} - {profile === "not found" && "Profile not found"} - - ); -}; - -export default Profile; diff --git a/src/staff/pages/Staff.tsx b/src/staff/pages/Staff.tsx new file mode 100644 index 00000000..5f7abf12 --- /dev/null +++ b/src/staff/pages/Staff.tsx @@ -0,0 +1,54 @@ +import React, { useState, useEffect } from "react"; +import ProfileComponents from "../../shared/components/Profile/ProfileComponents.tsx"; +import { useParams } from "react-router"; +import { useAuth } from "../../context/AuthContext.tsx"; + +export default function StaffPage() { + const { auth } = useAuth(); + + const { staffId } = useParams(); + const [profile, setProfile] = useState(null); + + useEffect(() => { + const fetchProfile = async () => { + const response = await fetch( + `${process.env.REACT_APP_BACKEND_SERVER}/staff/${staffId}`, { + headers: { + Authorization: `Bearer ${auth.token}`, + }, + } + ); + + if (!response.ok) { + setProfile(false); + } else { + const data = await response.json(); + if (checkProfile(data)) { + setProfile(data); + } else { + setProfile(false); + console.log(data); + } + } + }; + + if (!auth.isAuthenticated) { + window.location.href = "/login"; + } else if (!staffId) { + setProfile(false); + } else { + fetchProfile(); + } + const checkProfile = (data: { name: string; image: string; department: string; description: string }) => { + return data.name && data.image && data.department && data.description; + }; + }, [auth.token, staffId, auth.isAuthenticated]); + + return ( + <> + {profile === null && "Loading..."} + {profile && typeof profile === "object" && staffId && } + {profile === false &&

Profile not found

} + + ); +}; diff --git a/src/App.css b/src/style/App.css similarity index 100% rename from src/App.css rename to src/style/App.css diff --git a/src/style/general.css b/src/style/general.css new file mode 100644 index 00000000..3a27301d --- /dev/null +++ b/src/style/general.css @@ -0,0 +1,84 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* General CSS */ +.align-items-center {@apply items-center;} +.flex2 {@apply flex flex-col;} +.flexjust {@apply flex justify-center;} +.active-link {@apply text-black py-3 border-b-2 border-black text-lg;} +.normal-link {@apply text-gray-600 py-3 text-lg border-black;} + +/* Opportunities CSS */ +.btn-job {@apply flex align-items-center p-2 rounded-3xl border;} +.about-role {@apply font-extrabold text-3xl;} +.about-map {@apply grid grid-cols-3;} +.about-head {@apply flex flex-col gap-1;} +.about-title {@apply text-gray-500 text-base;} +.about-description {@apply font-extrabold;} +.filters-template { @apply px-3 overflow-x-scroll max-h-20 flex gap-3;} +.filters-search {@apply flex gap-2 w-full;} +.all-filters-btn {@apply justify-end w-full;} +.horizontal-btn {@apply flex gap-1 align-items-center p-2 px-2.5 rounded-3xl border;} +.job-desc-header {@apply flex flex-col gap-2;} +.job-desc-title {@apply font-extrabold text-xl;} +.job-desc-description {@apply text-gray-700;} +.job-details-header {@apply w-full col-span-7 border-l border-r p-24 flex flex-col gap-5 shadow-sm;} +.job-header-header {@apply flex flex-col gap-3;} +.job-header-title {@apply font-bold text-5xl;} +.job-inter-btn {@apply flex align-items-center p-2 px-4 rounded-3xl border;} +.job-post-header {@apply p-2 min-h-36 flex gap-3 py-2;} +.job-post-btn {@apply shadow-sm border-b border-b-gray-300;} +.job-post-title {@apply font-black;} +.job-post-description {@apply text-sm;} +.jobs-header {@apply text-2xl font-bold;} +.jobs-categories {@apply text-base flex gap-4 justify-items-center font-semibold;} +.postsfield-header {@apply border-t border-b grid grid-cols-9;} +.searchbar {@apply flex p-2 px-3 border rounded-3xl align-items-center;} + +/* Shared CSS */ +.btn-disclosure {@apply relative inline-flex items-center justify-center rounded-md p-2 text-gray-400;} +.blck66 {@apply block h-6 w-6;} +.prof-ring {@apply w-min h-min rounded-full ring ring-primary ring-offset-base-100 ring-offset-2;} +.mainnav {@apply mx-auto max-w-7xl px-2;} +.mainnav-header {@apply relative flex h-16 items-center justify-between;} +.mainnav-desc {@apply absolute inset-y-0 left-0 flex items-center;} +.mainnav-desc2 {@apply flex flex-1 items-center justify-center;} +.mainnav-title-link {@apply flex flex-shrink-0 items-center;} +.mainnav-link {@apply rounded-md px-3 py-2 text-sm font-medium no-underline;} +.btn-disclosure2 {@apply block rounded-md px-3 py-2 text-base font-medium text-black no-underline;} +.pagenav {@apply text-base flex gap-4 justify-items-center font-semibold;} +.opportunitycard {@apply card-compact min-h-48 max-h-48 duration-300 w-64 p-1 bg-base-100;} +.card2-body {@apply text-sm font-light p-0 m-0;} +.avatar1 {@apply flex align-items-center gap-3;} +.avatar-img {@apply rounded-full w-12 h-12;} +.avatar-name {@apply text-blue-800 text-base;} +.avatar-card {@apply p-2 border rounded min-w-fit max-w-fit;} +.profileavatar {@apply w-min h-min rounded-full ring ring-primary ring-offset-base-100 ring-offset-2;} +.stickyfooter {@apply flex2 h-screen justify-between;} +.signin-general {@apply flex2 justify-center text-center;} +.img-center {@apply flex justify-center items-center;} +.home-general {@apply text-center font-sans;} +.stickyfooter-general {@apply rounded border-2 p-3;} +.stickyfooter-header {@apply text-center text-lg justify-center;} +.stickyfooter-info {@apply flex flex-row justify-between px-12 text-center;} +.grey-link {@apply no-underline text-neutral-600;} +.blue-link {@apply no-underline text-blue-500;} + +/* Staff CSS */ +.createpost {@apply flex justify-center mt-3;} +.check-input {@apply w-full max-w-xs;} +.lc-loading {@apply loading loading-spinner loading-lg;} +.featimage {@apply min-w-32 w-96 h-72 bg-base-100 shadow-xl rounded-md overflow-clip;} +.lg-img-card {@apply card-compact w-96 bg-base-100 transition;} +.lg-txt-card {@apply min-h-48 max-h-48 duration-300 card-compact w-56 p-1 bg-base-100;} +.lg-txt-card-attributes {@apply overflow-visible w-fit badge badge-primary;} +.staff {@apply font-bold py-3;} +.staff-body {@apply px-2 grid grid-cols-4;} +.center {@apply pb-10 flex flex-col gap-4;} +.createpost {@apply flex justify-center mt-3;} + +/* CreatePost CSS*/ +.form-container {@apply flex flex-col gap-5; /* Space between rows */} +.horizontal-form {@apply flex gap-5; /* Space between inputs */} +.horizontal-form > * {@apply flex-1; /* Each input takes equal space */} \ No newline at end of file diff --git a/src/style/index.css b/src/style/index.css new file mode 100644 index 00000000..3567d2d8 --- /dev/null +++ b/src/style/index.css @@ -0,0 +1,29 @@ +/* Base CSS */ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + + +.align-items-center { + align-items: center; +} + +.backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); /* Adjust opacity as needed */ + z-index: 1000; /* Ensure it appears above other content */ +} \ No newline at end of file