diff --git a/components/AlertModal.js b/components/AlertModal.js new file mode 100644 index 00000000..c2b00f2e --- /dev/null +++ b/components/AlertModal.js @@ -0,0 +1,21 @@ +import React from "react"; +import { useAlert } from "../contexts/AlertContext"; + +const AlertModal = () => { + const { alert, closeAlert } = useAlert(); + + if (!alert.isOpen) return null; + + return ( +
+
e.stopPropagation()}> +

{alert.message}

+ +
+
+ ); +}; + +export default AlertModal; diff --git a/components/FacultyIdCard.jsx b/components/FacultyIdCard.jsx new file mode 100644 index 00000000..b1449919 --- /dev/null +++ b/components/FacultyIdCard.jsx @@ -0,0 +1,99 @@ +import PropTypes from "prop-types"; +import Image from "next/image"; + +const FacultyIdCard = ({ + faculty, + placeholderImage, + isFavorite, + toggleFavorite, +}) => { + const facultyId = faculty.user_id; + + const handleViewProfile = () => { + localStorage.setItem("selectedFacultyId", facultyId); + console.log(localStorage.getItem("selectedFacultyId")); + }; + + return ( +
+ {`${faculty.personal_info.first_name +

+ {faculty.personal_info.first_name || "No Name"}{" "} + {faculty.personal_info.last_name || ""} +

+

Email: {faculty.personal_info?.email || "N/A"}

+

User ID: {faculty.user_id}

+

+ Address: {faculty.personal_info.present_address?.street || "N/A"},{" "} + {faculty.personal_info.present_address?.city || "N/A"},{" "} + {faculty.personal_info.present_address?.district || "N/A"},{" "} + {faculty.personal_info.present_address?.state || "N/A"} +

+

Gender: {faculty.personal_info.gender || "N/A"}

+

+ DOB:{" "} + {faculty.personal_info.dob + ? new Date(faculty.personal_info.dob).toLocaleDateString() + : "N/A"} +

+

Contact: {faculty.personal_info.contact || "N/A"}

+

Faculty ID: {faculty.job_info.faculty_id}

+

Room: {faculty.job_info.room || "N/A"}

+

Department: {faculty.job_info.department || "Not Assigned"}

+ + + + +
+ ); +}; + +FacultyIdCard.propTypes = { + faculty: PropTypes.shape({ + user_id: PropTypes.string.isRequired, + image: PropTypes.string, + personal_info: PropTypes.shape({ + first_name: PropTypes.string, + last_name: PropTypes.string, + email: PropTypes.string, + present_address: PropTypes.shape({ + street: PropTypes.string, + city: PropTypes.string, + district: PropTypes.string, + state: PropTypes.string, + }), + gender: PropTypes.string, + dob: PropTypes.string, + contact: PropTypes.string, + }), + job_info: PropTypes.shape({ + faculty_id: PropTypes.string, + room: PropTypes.string, + department: PropTypes.string, + }), + }).isRequired, + placeholderImage: PropTypes.string.isRequired, + isFavorite: PropTypes.bool.isRequired, + toggleFavorite: PropTypes.func.isRequired, +}; + +export default FacultyIdCard; diff --git a/components/Routine.jsx b/components/Routine.jsx new file mode 100644 index 00000000..0f185d05 --- /dev/null +++ b/components/Routine.jsx @@ -0,0 +1,128 @@ +import React from "react"; + + +const routineData = [ + { + period: 1, + subjects: ["Mathematics", "English", "History", "Science", "Art"], + teachers: [ + "Mrs. Jane Doe", + "Mr. John Smith", + "Ms. Sarah Lee", + "Dr. Michael Johnson", + "Ms. Emily Davis", + ], + }, + { + period: 2, + subjects: ["English", "Mathematics", "Art", "History", "Science"], + teachers: [ + "Mr. John Smith", + "Mrs. Jane Doe", + "Ms. Emily Davis", + "Ms. Sarah Lee", + "Dr. Michael Johnson", + ], + }, + { + period: 3, + subjects: ["History", "Science", "Mathematics", "Art", "English"], + teachers: [ + "Ms. Sarah Lee", + "Dr. Michael Johnson", + "Mrs. Jane Doe", + "Ms. Emily Davis", + "Mr. John Smith", + ], + }, + { + period: 4, + subjects: ["Science", "Art", "English", "Mathematics", "History"], + teachers: [ + "Dr. Michael Johnson", + "Ms. Emily Davis", + "Mr. John Smith", + "Mrs. Jane Doe", + "Ms. Sarah Lee", + ], + }, + { + period: 5, + subjects: ["Art", "History", "Science", "English", "Mathematics"], + teachers: [ + "Ms. Emily Davis", + "Ms. Sarah Lee", + "Dr. Michael Johnson", + "Mr. John Smith", + "Mrs. Jane Doe", + ], + }, + { + period: 6, + subjects: ["Mathematics", "Science", "English", "Art", "History"], + teachers: [ + "Mrs. Jane Doe", + "Dr. Michael Johnson", + "Mr. John Smith", + "Ms. Emily Davis", + "Ms. Sarah Lee", + ], + }, + { + period: 7, + subjects: ["English", "Art", "History", "Science", "Mathematics"], + teachers: [ + "Mr. John Smith", + "Ms. Emily Davis", + "Ms. Sarah Lee", + "Dr. Michael Johnson", + "Mrs. Jane Doe", + ], + }, +]; + +const days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]; + +const colors = ["blue", "green", "yellow", "red", "purple"]; + +export default function Routine() { + return ( +
+
+
+

Class Routine Schedule

+
Monday - Friday
+
+ +
+
+
Period
+ {days.map((day, index) => ( +
+ {day} +
+ ))} + + {routineData.map(({ period, subjects, teachers }) => ( + +
{period}
+ {subjects.map((subject, index) => ( +
+

{subject}

+

{teachers[index]}

+
+ ))} +
+ ))} +
+
+
+
+ ); +} diff --git a/components/index.jsx b/components/index.jsx index dc501cb2..12b74c38 100644 --- a/components/index.jsx +++ b/components/index.jsx @@ -1,5 +1,7 @@ export { default as TypingText } from "./TypingText"; export { default as ApplicantForm } from "./ApplicantForm/ApplicantForm"; +export { default as AlertModal } from "./AlertModal"; + // Loading Components export { default as Circle } from "./Loading/Circle"; export { default as Default } from "./Loading/Default"; @@ -28,3 +30,4 @@ export { default as ManageApp } from "./ManageApp"; export { default as CommonMeta } from "./CommonMeta"; export { default as IdCard } from "./IdCard"; +export { default as FacultyIdCard } from "./FacultyIdCard"; diff --git a/containers/Header/ApplicantNavbar.jsx b/containers/Header/ApplicantNavbar.jsx index f02385d4..e6414c97 100644 --- a/containers/Header/ApplicantNavbar.jsx +++ b/containers/Header/ApplicantNavbar.jsx @@ -1,45 +1,56 @@ import React, { Fragment, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { logoutApi } from "@/functions"; import { AllLoader } from "@/components"; +import { withLoading, devLog, apiRequest } from "@/utils/apiUtils"; +import { useAlert } from "@/contexts/AlertContext"; function ApplicantNavbar() { const router = useRouter(); const logoText = "eduversa"; const [isMenuOpen, setIsMenuOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); + const { showAlert } = useAlert(); const handleLogout = async () => { const userId = localStorage.getItem("userid"); const authToken = localStorage.getItem("authToken"); try { - setIsLoading(true); - const apiResponse = await logoutApi(userId, authToken); - if (apiResponse.status === false) { - alert(apiResponse.message); - setIsLoading(false); + const wrappedApiRequest = withLoading( + apiRequest, + setIsLoading, + showAlert, + "Logout" + ); + + const response = await wrappedApiRequest( + `/account/auth?user_id=${userId}`, + "PATCH", + null, + authToken, + "logout" + ); + + if (!response.success || response.status === false) { + devLog("Logout error:", response); + showAlert(response.message); return; } - if (process.env.NODE_ENV === "development") { - console.log("Logout data:", apiResponse); - } - localStorage.removeItem("authToken"); - localStorage.removeItem("email"); - localStorage.removeItem("userType"); - localStorage.removeItem("userid"); - localStorage.removeItem("applicant_profile"); + + // devLog("Logout response:", response); + showAlert(response.message); + localStorage.clear(); - alert(apiResponse.message); - setIsLoading(false); router.push("/"); } catch (error) { - if (process.env.NODE_ENV === "development") { - console.error("Logout error:", error.message); - } + devLog("Global error:", error); + showAlert( + error.message || "An unexpected error occurred. Please try again." + ); } }; + const menuItems = [ { label: "Dashboard", className: "nav-item", src: "/applicant" }, { @@ -78,7 +89,7 @@ function ApplicantNavbar() {
setIsMenuOpen(!isMenuOpen)} >
diff --git a/containers/Header/Navbar.jsx b/containers/Header/Navbar.jsx index 60ff9d52..4b81c48a 100644 --- a/containers/Header/Navbar.jsx +++ b/containers/Header/Navbar.jsx @@ -96,6 +96,11 @@ function Navbar() { className: "nav-item", src: "/admin/manage/students", }, + { + label: "Manage Faculties", + className: "nav-item", + src: "/admin/manage/faculties", + }, { label: "Contact Us", className: "nav-item", @@ -112,9 +117,7 @@ function Navbar() { src: "/admin/scanner", }, ], - faculty: [ - { label: "Dashboard", className: "nav-item", src: "/faculty/dashboard" }, - ], + faculty: [{ label: "Dashboard", className: "nav-item", src: "/faculty" }], student: [ { label: "Dashboard", className: "nav-item", src: "/student" }, { diff --git a/contexts/AlertContext.js b/contexts/AlertContext.js new file mode 100644 index 00000000..faeccf2b --- /dev/null +++ b/contexts/AlertContext.js @@ -0,0 +1,18 @@ +import React, { createContext, useState, useContext } from "react"; + +const AlertContext = createContext(); + +export const AlertProvider = ({ children }) => { + const [alert, setAlert] = useState({ isOpen: false, message: "" }); + + const showAlert = (message) => setAlert({ isOpen: true, message }); + const closeAlert = () => setAlert({ isOpen: false, message: "" }); + + return ( + + {children} + + ); +}; + +export const useAlert = () => useContext(AlertContext); diff --git a/functions/generateOtp.js b/functions/generateOtp.js index afd149aa..d84d913a 100644 --- a/functions/generateOtp.js +++ b/functions/generateOtp.js @@ -37,3 +37,5 @@ const generateOtpApi = async (userIdOrEmail) => { }; export default generateOtpApi; + + diff --git a/functions/logoutApi.js b/functions/logoutApi.js index c9f854cc..b9c8662e 100644 --- a/functions/logoutApi.js +++ b/functions/logoutApi.js @@ -20,7 +20,6 @@ const logoutApi = async (userId, authToken) => { if (process.env.NODE_ENV === "development") { console.log(response); } - // throw new Error(`Logout request failed with status ${response.status}`); } if (process.env.NODE_ENV === "development") { console.log("Response:", response); diff --git a/layout/admin_layout/AdminLayout.jsx b/layout/admin_layout/AdminLayout.jsx index 61c172b0..5379dbfc 100644 --- a/layout/admin_layout/AdminLayout.jsx +++ b/layout/admin_layout/AdminLayout.jsx @@ -1,30 +1,37 @@ -import React, { Fragment, useEffect } from "react"; +import React, { Fragment, useEffect, useRef } from "react"; import { useRouter } from "next/router"; import { Navbar, Footer, ChatBot } from "@/containers"; + function AdminLayout({ children }) { const router = useRouter(); + + const hasLogged = useRef(false); + useEffect(() => { const authToken = localStorage.getItem("authToken"); const userType = localStorage.getItem("userType"); - if (process.env.NODE_ENV === "development") { + + if (process.env.NODE_ENV === "development" && !hasLogged.current) { console.log("AuthToken", authToken); console.log("UserType", userType); + hasLogged.current = true; } + if (!authToken) { localStorage.clear(); router.push("/"); - } - if (userType !== "admin") { + } else if (userType !== "admin") { localStorage.clear(); router.push("/"); } }, [router]); + return ( - +
{children}
-
- +
+
- New to eduversa? + New to Eduversa? -

Click here to Register

+

Click here to Register

diff --git a/pages/register/index.jsx b/pages/register/index.jsx index 7a9da1f3..23918c47 100644 --- a/pages/register/index.jsx +++ b/pages/register/index.jsx @@ -3,97 +3,110 @@ import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; import { LandingLayout } from "@/layout"; -import { registerUser, createAccountWithSocialPlatform } from "@/functions"; +import { withLoading, devLog, apiRequest } from "@/utils/apiUtils"; import { AllLoader } from "@/components"; import { useSession, signIn, signOut } from "next-auth/react"; import Head from "next/head"; +import { useAlert } from "@/contexts/AlertContext"; + function Register() { const [email, setEmail] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); const { data: session } = useSession(); + const { showAlert } = useAlert(); useEffect(() => { const platformName = localStorage.getItem("platformName"); if (session) { if (process.env.NODE_ENV === "development") { - console.log("Session:", session); + devLog("Session detected:", session); } + setLoading(true); - fetch( - `https://eduversa-api.onrender.com/account/auth/platform?platform=${platformName}`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(session), - } - ) - .then((response) => response.json()) - .then(async (res) => { - console.log(res); - alert(res.message); - if (!res.status) { - setLoading(false); - return; - } - localStorage.removeItem("platformName"); - await signOut({ callbackUrl: "/" }); - }) - .catch((error) => console.log(error)); + handlePlatformAuth(platformName, session) + .catch((error) => devLog("Platform auth error:", error)) + .finally(() => setLoading(false)); } - }, [session]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [session, showAlert]); + + const handlePlatformAuth = async (platformName, sessionData) => { + const response = await fetch( + `https://eduversa-api.onrender.com/account/auth/platform?platform=${platformName}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(sessionData), + } + ); + + const res = await response.json(); + devLog("Auth platform response:", res); + showAlert(res.message); + + if (!res.status) { + setLoading(false); + return; + } + + localStorage.removeItem("platformName"); + await signOut({ callbackUrl: "/" }); + }; const handleSubmit = async (e) => { e.preventDefault(); + const wrappedApiRequest = withLoading( + apiRequest, + setLoading, + showAlert, + "Registration" + ); + try { - setLoading(true); - const registrationData = await registerUser(email); - if (registrationData.status === false) { - if (process.env.NODE_ENV === "development") { - console.log("Registration data:", registrationData); - } - alert(registrationData.message); - setLoading(false); + const response = await wrappedApiRequest( + "/account", + "POST", + { email }, + localStorage.getItem("authToken"), + "Registration" + ); + + if (!response.success) { + devLog("Registration error response:", response); + showAlert(response.message); return; } - if (process.env.NODE_ENV === "development") { - console.log("Registration data:", registrationData); - } - alert( - "Registration Was Successful! Check Your Email For Login Credentials" + + // devLog("Registration success data:", response); + showAlert( + "Registration Was Successful! Check Your Email For Login Credentials" || + response.message ); - setLoading(false); router.push("/"); } catch (error) { - if (process.env.NODE_ENV === "development") { - console.error("Error during registration:", error.message); - } + devLog("Global Error:", error); + showAlert( + error.message || "An unexpected error occurred. Please try again." + ); } }; - const handleSocialRegisterClick = async (provider) => { - alert(`Register with ${provider} is coming soon!`); - console.log("apiResponse", apiResponse); - console.log("Session:", session); - }; - const handleGoogleSignIn = async () => { - await signIn("google"); - localStorage.setItem("platformName", "google"); - }; - const handleGithubSignIn = async () => { - await signIn("github"); - localStorage.setItem("platformName", "github"); + const handleSocialRegisterClick = (provider) => { + showAlert(`Register with ${provider} is coming soon!`); + devLog("Social provider registration attempt:", provider); }; - const handleFacebookSignIn = async () => { - await signIn("facebook"); - localStorage.setItem("platformName", "facebook"); + + const handleGoogleSignIn = () => initiateSignIn("google"); + const handleGithubSignIn = () => initiateSignIn("github"); + const handleFacebookSignIn = () => initiateSignIn("facebook"); + + const initiateSignIn = async (provider) => { + await signIn(provider); + localStorage.setItem("platformName", provider); }; - if (process.env.NODE_ENV === "development") { - console.log("Session:", session); - } + return ( @@ -112,7 +125,7 @@ function Register() {

Register

- Your Pathway to Academic Excellence: Register Now on Eduversa๐Ÿ˜‰ + Your Pathway to Academic Excellence: Register Now on Eduversa ๐Ÿ˜‰

@@ -142,16 +155,16 @@ function Register() { height={25} width={25} className="google-icon" - onClick={() => handleGoogleSignIn("Google")} - > + onClick={handleGoogleSignIn} + /> facebook handleFacebookSignIn("Facebook")} - > + onClick={handleFacebookSignIn} + /> twitter handleSocialRegisterClick("Twitter")} - > + /> linkedin - await handleSocialRegisterClick("LinkedIn") - } - > + onClick={() => handleSocialRegisterClick("LinkedIn")} + /> github handleGithubSignIn("GitHub")} - > + onClick={handleGithubSignIn} + />
diff --git a/pages/student/routine/index.jsx b/pages/student/routine/index.jsx new file mode 100644 index 00000000..be69fca1 --- /dev/null +++ b/pages/student/routine/index.jsx @@ -0,0 +1,18 @@ +import Routine from "@/components/Routine"; +import { Fragment } from "react"; +import { StudentLayout } from "@/layout"; +import React from "react"; + +const RoutineComponent = () => { + return ( +
+ + + + + +
+ ); +}; + +export default RoutineComponent; diff --git a/styles/components/_manifest.scss b/styles/components/_manifest.scss index 47a6cf16..e9f272de 100644 --- a/styles/components/_manifest.scss +++ b/styles/components/_manifest.scss @@ -27,4 +27,6 @@ } @import "id-card"; +@import "faculty-card"; @import "chatbot"; +@import "modal"; diff --git a/styles/components/_modal.scss b/styles/components/_modal.scss new file mode 100644 index 00000000..d51ce602 --- /dev/null +++ b/styles/components/_modal.scss @@ -0,0 +1,29 @@ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + .modal-content { + background: #fff; + padding: 1.5em; + border-radius: 8px; + max-width: 90%; + width: 400px; + text-align: center; + .close-button { + margin-top: 1em; + padding: 0.5em 1em; + background-color: #0070f3; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + } + } +} diff --git a/styles/components/faculty-card.scss b/styles/components/faculty-card.scss new file mode 100644 index 00000000..16f31fc0 --- /dev/null +++ b/styles/components/faculty-card.scss @@ -0,0 +1,55 @@ +.faculty-card { + background-color: #ffffff; + border-radius: 12px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 20px; + margin: 15px; + transition: transform 0.3s, box-shadow 0.3s; + + &:hover { + transform: translateY(-5px); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + } + + .faculty-image { + margin-bottom: 15px; + width: 100px; + height: 100px; + object-fit: cover; + border: 3px solid #007bff; + } + + h2 { + font-size: 1.8rem; + color: #343a40; + margin-bottom: 10px; + text-align: center; + } + + p { + font-size: 1rem; + color: #495057; + margin: 5px 0; + } + + .faculty-info { + &__label { + font-weight: bold; + color: #007bff; + } + + &__value { + color: #6c757d; + } + } + .favorite-button { + background-color: transparent; + border: none; + cursor: pointer; + color: blue; /* Change to your desired color */ + } + + .favorite-button.favorited { + color: red; /* Change to your desired color for favorited state */ + } +} diff --git a/styles/pages/_manifest.scss b/styles/pages/_manifest.scss index fa4c423d..11ce64e8 100644 --- a/styles/pages/_manifest.scss +++ b/styles/pages/_manifest.scss @@ -17,5 +17,8 @@ @import "admin/scanner"; @import "admin/dashboard"; @import "admin/manageStudent"; +@import "admin/manageFaculties"; // student @import "student/dashboard"; + +@import "routine/routineLayout"; diff --git a/styles/pages/admin/_manageFaculties.scss b/styles/pages/admin/_manageFaculties.scss new file mode 100644 index 00000000..05774996 --- /dev/null +++ b/styles/pages/admin/_manageFaculties.scss @@ -0,0 +1,165 @@ +.manage-faculty { + padding: 20px; + background-color: #e9ecef; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + + &__title { + font-size: 2.5rem; + margin-bottom: 30px; + color: #343a40; + text-align: center; + text-transform: uppercase; + letter-spacing: 1px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + &__search-bar { + width: 100%; + max-width: 450px; + padding: 14px 20px; + margin: 0 auto 20px auto; + border: 2px solid #007bff; + border-radius: 6px; + font-size: 1.2rem; + transition: border-color 0.3s, box-shadow 0.3s, transform 0.3s; + + &:focus { + border-color: #0056b3; + box-shadow: 0 0 5px rgba(0, 123, 255, 0.5); + outline: none; + transform: scale(1.02); + } + } + + &__dropdowns { + display: flex; + justify-content: center; + margin-bottom: 20px; + + &__select { + padding: 14px; + margin: 0 10px; + border: 1px solid #007bff; + border-radius: 6px; + font-size: 1rem; + background: linear-gradient(to right, #f0f4f8, #ffffff); + transition: border-color 0.3s, background-color 0.3s; + + &:focus { + border-color: #0056b3; + background-color: #e9ecef; + outline: none; + } + } + } + + &__export-button { + display: inline-block; + padding: 14px 28px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 1.1rem; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3); + transition: background-color 0.3s, transform 0.3s; + + &:hover { + background-color: #0056b3; + transform: translateY(-3px); + box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4); + } + } + + &__list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 30px; + margin-top: 20px; + + &__no-results { + grid-column: span 3; + text-align: center; + color: #777; + font-size: 1.5rem; + font-style: italic; + } + } + + &__faculty-card { + background-color: #ffffff; + border-radius: 10px; + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + overflow: hidden; + transition: transform 0.2s, box-shadow 0.2s; + + &:hover { + transform: scale(1.02); + box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2); + } + } + + &__pagination-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 20px; + + &__select-pagesize { + padding: 10px; + border: 1px solid #007bff; + border-radius: 6px; + font-size: 1rem; + transition: border-color 0.3s; + + &:focus { + border-color: #0056b3; + outline: none; + } + } + } + + &__pagination { + &__pagination-list { + display: flex; + list-style: none; + padding: 0; + + &__pagination-item { + margin: 0 5px; + + &.active { + font-weight: bold; + color: #fff; + background-color: #007bff; + border-radius: 5px; + transition: background-color 0.3s; + + &:hover { + background-color: #0056b3; + } + } + } + + &__pagination-link { + padding: 10px 15px; + border: 1px solid #007bff; + border-radius: 6px; + background-color: #fff; + color: #007bff; + cursor: pointer; + transition: background-color 0.3s, color 0.3s, transform 0.2s; + + &:hover { + background-color: #007bff; + color: #fff; + transform: scale(1.05); + } + } + } + } +} diff --git a/styles/pages/routine/_routineLayout.scss b/styles/pages/routine/_routineLayout.scss new file mode 100644 index 00000000..72292bb3 --- /dev/null +++ b/styles/pages/routine/_routineLayout.scss @@ -0,0 +1,482 @@ + + +// $colors: ( +// blue: #dbeafe, +// green: #dcfce7, +// yellow: #fef9c3, +// red: #fee2e2, +// purple: #f3e8ff, +// ); + +// $breakpoint-sm: 640px; + +// @mixin sm { +// @media (min-width: #{$breakpoint-sm}) { +// @content; +// } +// } + +// .routine-container { +// max-width: 1200px; +// // width: 100%; +// margin: 0 auto; +// padding: 1rem; + + + + +// .routine-card { +// background-color: rgba(255, 255, 255, 0.3); +// backdrop-filter: blur(4px); +// border-radius: 0.5rem; +// border: 1px solid rgba(255, 255, 255, 0.3); +// padding: 1rem; +// overflow-x: auto; + +// @include sm { +// padding: 1.5rem; +// } +// } + +// .routine-header { +// display: flex; +// flex-direction: column; +// align-items: flex-start; +// justify-content: space-between; +// margin-bottom: 1.5rem; + +// @include sm { +// flex-direction: row; +// align-items: center; +// } + +// h2 { +// font-size: 1.5rem; +// font-weight: bold; +// margin-bottom: 0.5rem; + +// @include sm { +// font-size: 1.5rem; +// margin-bottom: 0; +// } +// } + +// .date-range { +// font-size: 0.875rem; +// color: #6b7280; + +// @include sm { +// font-size: 1rem; +// } +// } +// } + +// .routine-grid { +// display: grid; +// grid-template-columns: auto repeat(5, minmax(0, 1fr)); +// gap: 0.5rem; +// // min-width: max-content; +// overflow-x: auto; + +// @include sm { +// gap: 1rem; +// } + +// .grid-header { +// font-weight: 600; +// text-align: center; +// font-size: 0.75rem; +// padding: 0.25rem; +// border-radius: 0.375rem; +// // width: 4rem; + +// &.day { +// padding: 0.5rem; +// } + +// @each $color, $value in $colors { +// &.#{$color} { +// background-color: $value; +// } +// } +// } + +// .period-number { +// font-weight: 600; +// display: flex; +// justify-content: center; +// align-items: center; +// font-size: 0.75rem; +// background-color: #f3f4f6; +// border: 2px solid #d1d5db; +// padding: 0.25rem; +// border-radius: 0.375rem; +// width: 4rem; +// } + +// .subject-cell { +// border-radius: 0.5rem; +// padding: 0.5rem; +// display: flex; +// flex-direction: column; +// border: 1px solid #d1d5db; + +// @include sm { +// padding: 0.75rem; +// } + +// @each $color, $value in $colors { +// &.#{$color} { +// background-color: rgba($value, 0.6); +// } +// } + +// h3 { +// font-size: 0.75rem; +// font-weight: bold; + +// @include sm { +// font-size: 1rem; +// } +// } + +// p { +// font-size: 0.75rem; +// color: #6b7280; + +// @include sm { +// font-size: 0.875rem; +// } +// } +// } +// } +// } + + +// + + +// $colors: ( +// blue: #dbeafe, +// green: #dcfce7, +// yellow: #fef9c3, +// red: #fee2e2, +// purple: #f3e8ff, +// ); + +// $breakpoint-sm: 640px; + +// @mixin sm { +// @media (min-width: #{$breakpoint-sm}) { +// @content; +// } +// } + +// .routine-container { +// max-width: 1200px; +// margin: 0 auto; +// padding: 1rem; + +// .routine-card { +// background-color: rgba(255, 255, 255, 0.3); +// backdrop-filter: blur(4px); +// border-radius: 0.5rem; +// border: 1px solid rgba(255, 255, 255, 0.3); +// padding: 1rem; + +// @include sm { +// padding: 1.5rem; +// } +// } + +// .routine-header { +// display: flex; +// flex-direction: column; +// align-items: flex-start; +// justify-content: space-between; +// margin-bottom: 1.5rem; + +// @include sm { +// flex-direction: row; +// align-items: center; +// } + +// h2 { +// font-size: 1.25rem; +// font-weight: bold; +// margin-bottom: 0.5rem; + +// @include sm { +// font-size: 1.5rem; +// margin-bottom: 0; +// } +// } + +// .date-range { +// font-size: 0.875rem; +// color: #6b7280; + +// @include sm { +// font-size: 1rem; +// } +// } +// } + +// .routine-grid-wrapper { +// overflow-x: auto; +// -webkit-overflow-scrolling: touch; +// } + +// .routine-grid { +// display: grid; +// grid-template-columns: auto repeat(5, minmax(120px, 1fr)); +// gap: 0.5rem; +// min-width: 600px; // Adjust this value based on your content + +// @include sm { +// gap: 1rem; +// } + +// .grid-header { +// font-weight: 600; +// text-align: center; +// font-size: 0.75rem; +// padding: 0.25rem; +// border-radius: 0.375rem; + +// &.day { +// padding: 0.5rem; +// } + +// @each $color, $value in $colors { +// &.#{$color} { +// background-color: $value; +// } +// } +// } + +// .period-number { +// font-weight: 600; +// display: flex; +// justify-content: center; +// align-items: center; +// font-size: 0.75rem; +// background-color: #f3f4f6; +// border: 2px solid #d1d5db; +// padding: 0.25rem; +// border-radius: 0.375rem; +// width: 3rem; +// } + +// .subject-cell { +// border-radius: 0.5rem; +// padding: 0.5rem; +// display: flex; +// flex-direction: column; +// border: 1px solid #d1d5db; + +// @include sm { +// padding: 0.75rem; +// } + +// @each $color, $value in $colors { +// &.#{$color} { +// background-color: rgba($value, 0.6); +// } +// } + +// h3 { +// font-size: 0.75rem; +// font-weight: bold; + +// @include sm { +// font-size: 0.875rem; +// } +// } + +// p { +// font-size: 0.675rem; +// color: #6b7280; + +// @include sm { +// font-size: 0.75rem; +// } +// } +// } +// } +// } + + + + + +$colors: ( + blue: #dbeafe, + green: #dcfce7, + yellow: #fef9c3, + red: #fee2e2, + purple: #f3e8ff, +); + +$breakpoint-sm: 640px; +$breakpoint-md: 768px; +$breakpoint-lg: 1024px; + +@mixin sm { + @media (min-width: #{$breakpoint-sm}) { + @content; + } +} + +@mixin md { + @media (min-width: #{$breakpoint-md}) { + @content; + } +} + +@mixin lg { + @media (min-width: #{$breakpoint-lg}) { + @content; + } +} + +.routine-container { + max-width: 1200px; + margin: 0 auto; + padding: 1rem; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + .routine-card { + background-color: rgba(255, 255, 255, 0.9); + border-radius: 0.5rem; + border: 1px solid rgba(255, 255, 255, 0.3); + padding: 1rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + + @include sm { + padding: 1.5rem; + } + } + + .routine-header { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 1.5rem; + + @include sm { + flex-direction: row; + align-items: center; + } + + h2 { + font-size: 1.5rem; + font-weight: bold; + margin-bottom: 0.5rem; + + @include sm { + font-size: 1.75rem; + margin-bottom: 0; + } + } + + .date-range { + font-size: 1rem; + color: #6b7280; + + @include sm { + font-size: 1.125rem; + } + } + } + + .routine-grid-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + will-change: transform; + } + + .routine-grid { + display: grid; + grid-template-columns: auto repeat(5, minmax(150px, 1fr)); + gap: 0.75rem; + min-width: 800px; + + @include md { + gap: 1rem; + } + + .grid-header { + font-weight: 600; + text-align: center; + font-size: 0.875rem; + padding: 0.5rem; + border-radius: 0.5rem; + + &.day { + padding: 0.75rem; + font-size: 1rem; + } + + @each $color, $value in $colors { + &.#{$color} { + background-color: $value; + } + } + } + + .period-number { + font-weight: 600; + display: flex; + justify-content: center; + align-items: center; + font-size: 1rem; + background-color: #f3f4f6; + border: 2px solid #d1d5db; + padding: 0.5rem; + border-radius: 0.5rem; + width: 4rem; + } + + .subject-cell { + border-radius: 0.5rem; + padding: 0.75rem; + display: flex; + flex-direction: column; + border: 1px solid #d1d5db; + min-height: 80px; + transform: translateZ(0); + + @include sm { + padding: 1rem; + } + + @each $color, $value in $colors { + &.#{$color} { + background-color: $value; + } + } + + h3 { + font-size: 1rem; + font-weight: bold; + margin-bottom: 0.25rem; + + @include sm { + font-size: 1.125rem; + } + } + + p { + font-size: 0.875rem; + color: #6b7280; + + @include sm { + font-size: 1rem; + } + } + } + } +} \ No newline at end of file diff --git a/utils/apiUtils.js b/utils/apiUtils.js new file mode 100644 index 00000000..d409d24f --- /dev/null +++ b/utils/apiUtils.js @@ -0,0 +1,159 @@ +export async function apiRequest( + endpoint, + method, + body = {}, + authToken = "", + routeName +) { + try { + let requestBody; + + switch (routeName) { + case "Registration": + requestBody = JSON.stringify({ email: body.email }); + break; + case "login": + requestBody = JSON.stringify({ + user_id: body.user_id, + password: body.password, + }); + break; + case "logout": + requestBody = null; + break; + case "ForgetPassword": + requestBody = JSON.stringify({ + password: body.password, + confirm_password: body.confirm_password, + }); + break; + case "ForgetUsername": + requestBody = null; + break; + case "GenerateOTP": + requestBody = null; + break; + case "GetCollegeDetails": + requestBody = null; + break; + case "GetSingleApplicant": + requestBody = null; + break; + case "GetAllApplicants": + requestBody = null; + break; + case "DeleteApplicant": + requestBody = null; + break; + case "ApproveApplicant": + requestBody = null; + break; + case "GetAllFaculties": + requestBody = null; + break; + case "GetSinglefaculty": + requestBody = null; + break; + default: + requestBody = method !== "GET" ? JSON.stringify(body) : null; + } + + const headers = { + "Content-Type": "application/json", + authorization: authToken, + }; + + if (isDevelopment()) { + devLog(`API Request - ${method} ${endpoint}`, { + headers, + body: requestBody, + }); + } + + const response = await fetch( + `https://eduversa-api.onrender.com${endpoint}`, + { + method, + headers, + body: requestBody, + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw new Error( + data.message || "Error occurred while processing your request." + ); + } + + if (isDevelopment()) { + devLog(`API Response - ${method} ${endpoint}`, data); + } + + return { + success: true, + status: data.status, + message: data.message, + data: data, + }; + } catch (error) { + if (isDevelopment()) { + devLog("Network or unexpected error:", error.message || error); + } + return { + success: false, + status: false, + message: error.message || "An unexpected error occurred.", + data: null, + }; + } +} + +export function withLoading( + asyncFunction, + setLoading, + showAlert, + routeName = "" +) { + return async (...args) => { + if (isDevelopment()) { + devLog(`${routeName} route accessed`, args); + } + + setLoading(true); + try { + const result = await asyncFunction(...args); + + if (isDevelopment()) { + devLog(`Response from ${routeName} route`, result); + } + + if (!result.success) { + showAlert(result.message || "Request failed. Please try again."); + } + + return result; + } catch (error) { + if (isDevelopment()) { + devLog(`Error in ${routeName} route`, error.message); + } + showAlert( + error.message || "An unexpected error occurred. Please try again." + ); + throw error; + } finally { + setLoading(false); + } + }; +} + +function isDevelopment() { + return process.env.NODE_ENV === "development"; +} + +export function devLog(message, data = "") { + if (isDevelopment()) { + console.log(message, data); + } +}