diff --git a/README.md b/README.md index ec765beb..31aa72b4 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ Frontend dashboard of the scholarX platform ## Setup Development Environment +### **Project setup walkthrough :- https://youtu.be/1STopJMM2nM** + 1. Clone your forked repository ``` git clone https://github.com/USERNAME/scholarx-frontend diff --git a/package.json b/package.json index 956b3ccc..c085b15d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,10 @@ "author": "", "license": "ISC", "dependencies": { + "@emotion/react": "^11.13.5", + "@emotion/styled": "^11.13.5", "@hookform/resolvers": "^3.3.4", + "@mui/material": "^6.3.1", "@tanstack/react-query": "^5.28.4", "@tanstack/react-query-devtools": "^5.37.1", "axios": "^1.4.0", diff --git a/src/components/Layout/Navbar/index.tsx b/src/components/Layout/Navbar/index.tsx index 07ad6a5b..0c6b0663 100644 --- a/src/components/Layout/Navbar/index.tsx +++ b/src/components/Layout/Navbar/index.tsx @@ -74,6 +74,20 @@ const Navbar: React.FC = () => { }; }, []); + useEffect(() => { + if (user) { + if (isUserMentor) { + navigate('/mentor/dashboard'); + } else if (isUserAdmin) { + navigate('/admin/dashboard/mentor-applications'); + } else if (isUserMentee) { + navigate('/mentee/dashboard'); + } else { + navigate('/'); + } + } + }, [user]); + return (
diff --git a/src/components/MonthlyChecking/MenteeMonthlyChecking.tsx b/src/components/MonthlyChecking/MenteeMonthlyChecking.tsx index c498d0d4..40dbf4de 100644 --- a/src/components/MonthlyChecking/MenteeMonthlyChecking.tsx +++ b/src/components/MonthlyChecking/MenteeMonthlyChecking.tsx @@ -29,7 +29,7 @@ const CheckInItem: React.FC = ({ checkIn }) => { >

- {checkIn.title} + {checkIn.month}

diff --git a/src/components/MonthlyChecking/MentorMonthlyChecking.tsx b/src/components/MonthlyChecking/MentorMonthlyChecking.tsx index f1f96303..634f85c7 100644 --- a/src/components/MonthlyChecking/MentorMonthlyChecking.tsx +++ b/src/components/MonthlyChecking/MentorMonthlyChecking.tsx @@ -45,7 +45,7 @@ const CheckInItem: React.FC = ({ >

- {checkIn.title} + {checkIn.month}

diff --git a/src/hooks/useCountries.ts b/src/hooks/useCountries.ts new file mode 100644 index 00000000..656fdfba --- /dev/null +++ b/src/hooks/useCountries.ts @@ -0,0 +1,22 @@ +import { useQuery } from '@tanstack/react-query'; +import { API_URL } from '../constants'; +import axios from 'axios'; + +const useCountries = () => { + const { isLoading, error, data } = useQuery({ + queryKey: ['countries'], + initialData: [], + queryFn: async () => { + const { data } = await axios.get(`${API_URL}/countries`); + return data.data; + }, + }); + + return { + isLoading, + error, + data, + }; +}; + +export default useCountries; diff --git a/src/pages/Dashboard/scenes/OngoingMentorshipPrograms/OngoingMentorshipPrograms.tsx b/src/pages/Dashboard/scenes/OngoingMentorshipPrograms/OngoingMentorshipPrograms.tsx index 7e0952f6..1103e65f 100644 --- a/src/pages/Dashboard/scenes/OngoingMentorshipPrograms/OngoingMentorshipPrograms.tsx +++ b/src/pages/Dashboard/scenes/OngoingMentorshipPrograms/OngoingMentorshipPrograms.tsx @@ -52,7 +52,7 @@ const OngoingMentorshipPrograms: React.FC = () => {

Approved Mentees

-
+
{approvedMentees.map(renderMenteeLink)} {approvedMentees.length === 0 && (

No approved mentees available.

diff --git a/src/pages/MentorProfile/MentorProfile.component.tsx b/src/pages/MentorProfile/MentorProfile.component.tsx index 8e86cb3e..391e5f9d 100644 --- a/src/pages/MentorProfile/MentorProfile.component.tsx +++ b/src/pages/MentorProfile/MentorProfile.component.tsx @@ -193,22 +193,28 @@ const MentorProfile: React.FC = () => {
- - Linkedin - - - Website - + + {mentor?.application.linkedin && ( + + Linkedin + + )} + + {mentor?.application.website && ( + + Website + + )}
diff --git a/src/pages/MentorRegistration/MentorRegistration.component.tsx b/src/pages/MentorRegistration/MentorRegistration.component.tsx index aed43ad0..1d41b94f 100644 --- a/src/pages/MentorRegistration/MentorRegistration.component.tsx +++ b/src/pages/MentorRegistration/MentorRegistration.component.tsx @@ -14,6 +14,7 @@ import { useLoginModalContext } from '../../contexts/LoginModalContext'; import useMentor from '../../hooks/useMentor'; import { Link } from 'react-router-dom'; import TermsAgreementModalMentor from '../../components/TermsAgreementModal'; +import useCountries from '../../hooks/useCountries'; const steps = [ { @@ -63,6 +64,12 @@ const MentorRegistrationPage: React.FC = () => { error: categoriesError, } = useCategories(); + const { + data: allCountries, + isLoading: countriesLoading, + error: countriesError, + } = useCountries(); + const { createMentorApplication, applicationError, @@ -265,14 +272,25 @@ const MentorRegistrationPage: React.FC = () => { register={register} error={errors.contactNo} /> - +
+ + {!countriesLoading && ( + + )} +
)} {currentStep === 1 && ( diff --git a/src/pages/Mentors/index.tsx b/src/pages/Mentors/index.tsx index 7b84d1c8..e781b3e4 100644 --- a/src/pages/Mentors/index.tsx +++ b/src/pages/Mentors/index.tsx @@ -5,22 +5,25 @@ import { usePublicMentors } from '../../hooks/usePublicMentors'; import { type Mentor, type Category } from '../../types'; import MentorCard from '../../components/MentorCard/MentorCard.component'; import Loading from '../../assets/svg/Loading'; +import { ApplicationStatus } from '../../enums'; +import Slider from '@mui/material/Slider'; +import Box from '@mui/material/Box'; const Mentors = () => { const [selectedCategory, setSelectedCategory] = useState(null); + const [selectedCountries, setSelectedCountries] = useState([]); + const [selectedAvailableSlots, setSelectedAvailableSlots] = useState([0, 10]); const [sortedMentors, setSortedMentors] = useState([]); + const [uniqueCountries, setUniqueCountries] = useState([]); + const [uniqueAvailableSlots, setUniqueAvailableSlots] = useState([]); + const [isCountryDropdownOpen, setIsCountryDropdownOpen] = useState(false); + const [isSlotsDropdownOpen, setIsSlotsDropdownOpen] = useState(false); + const [isMobileFilterOpen, setIsMobileFilterOpen] = useState(false); const pageSize = 10; const { ref, inView } = useInView(); - - const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = - usePublicMentors(selectedCategory, pageSize); - - const { - data: allCategories, - isLoading: categoriesLoading, - error: categoriesError, - } = useCategories(); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = usePublicMentors(selectedCategory, pageSize); + const { data: allCategories, isLoading: categoriesLoading, error: categoriesError } = useCategories(); useEffect(() => { if (inView && hasNextPage) { @@ -31,21 +34,71 @@ const Mentors = () => { useEffect(() => { if (data) { const allMentors = data.pages.flatMap((page) => page.items); - setSortedMentors(allMentors); + const countries = allMentors.map((mentor) => mentor.application?.country).filter((country) => !!country); + setUniqueCountries([...new Set(countries)]); + const availableSlots = allMentors.map((mentor) => { + const approvedMenteesCount = mentor?.mentees ? mentor.mentees.filter((mentee) => mentee.state === ApplicationStatus.APPROVED).length : 0; + return Math.max(0, mentor.application.noOfMentees - approvedMenteesCount); + }).filter((slots, index, self) => self.indexOf(slots) === index); + setUniqueAvailableSlots(availableSlots); } }, [data]); + useEffect(() => { + if (data) { + let allMentors = data.pages.flatMap((page) => page.items); + if (selectedCountries.length > 0) { + allMentors = allMentors.filter((mentor) => selectedCountries.includes(mentor.application.country)); + } + const [minSlots, maxSlots] = selectedAvailableSlots; + if (minSlots > 0 || maxSlots < 10) { + allMentors = allMentors.filter((mentor) => { + const approvedMenteesCount = mentor?.mentees ? mentor.mentees.filter((mentee) => mentee.state === ApplicationStatus.APPROVED).length : 0; + return mentor?.application.noOfMentees ? Math.max(0, mentor.application.noOfMentees - approvedMenteesCount) >= minSlots && Math.max(0, mentor.application.noOfMentees - approvedMenteesCount) <= maxSlots : false; + }); + } + setSortedMentors(allMentors); + } + }, [data, selectedCountries, selectedAvailableSlots]); + const handleSortAZ = () => { const sorted = [...sortedMentors].sort((a, b) => a.application.firstName.localeCompare(b.application.firstName) - ); - setSortedMentors(sorted); + );setSortedMentors(sorted); }; const handleCategoryChange = (category: string | null) => { setSelectedCategory(category); }; + const handleCountryChange = (country: string) => { + setSelectedCountries((prevCountries) => { + if (prevCountries.includes(country)) { + return prevCountries.filter((c) => c !== country); + } else { + return [...prevCountries, country]; + } + }); + }; + + const handleAvailableSlotsChange = (event: Event, newValue: number | number[]) => { + if (Array.isArray(newValue)) { + setSelectedAvailableSlots(newValue); + } + }; + + const toggleCountryDropdown = () => { + setIsCountryDropdownOpen(!isCountryDropdownOpen); + }; + + const toggleSlotsDropdown = () => { + setIsSlotsDropdownOpen(!isSlotsDropdownOpen); + }; + + const toggleMobileFilter = () => { + setIsMobileFilterOpen(!isMobileFilterOpen); + }; + if (status === 'pending' || categoriesLoading) { return (
@@ -59,69 +112,104 @@ const Mentors = () => { } return ( -
-
-
-

Mentors

-
-
- -
-
- - {allCategories.map((category: Category) => ( - - ))} +
+
+ +
+

Filters

+ +
+

Categories

+
+ + {allCategories.map((category: Category) => ( + + ))} +
-
-
- -
+
+

+ +

+ {isCountryDropdownOpen && ( +
+ + {uniqueCountries.map((country) => ( + + ))} +
+ )} +
- {sortedMentors.length > 0 ? ( -
- {sortedMentors.map((mentor) => ( - - ))} +
+

+ +

+ {isSlotsDropdownOpen && ( + + `${value} slots`} + min={0} + max={10} + step={1} + /> + + )}
- ) : ( -

No mentors found for this category.

- )} - {isFetchingNextPage && ( -
- +
+
- )} +
-
+
+
+

Mentors

+
+ + {sortedMentors.length > 0 ? ( +
+ {sortedMentors.map((mentor) => ( + + ))} +
+ ) : ( +

No mentors found for this filter.

+ )} + + {isFetchingNextPage && ( +
+ +
+ )} + +
+
+
); diff --git a/src/schemas.ts b/src/schemas.ts index e1f8ed34..1933f220 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -124,14 +124,14 @@ export const MentorApplicationSchema = z.object({ }); export const MenteeCheckInSchema = z.object({ - title: z.string().min(1, 'Title is required'), + month: z.string().min(1, 'Month is required'), generalUpdatesAndFeedback: z .string() .min(5, 'Please provide general updates'), progressTowardsGoals: z.string().min(5, 'Please summarize your progress'), mediaContentLinks: z .array(z.string().url('Please provide a valid URL')) - .min(1, 'Please provide at least 1 media links'), + .optional(), }); export const MentorFeedbackSchema = z.object({ diff --git a/src/types.ts b/src/types.ts index 15d4bba7..fb8c4006 100644 --- a/src/types.ts +++ b/src/types.ts @@ -108,7 +108,7 @@ export type MenteeCheckInForm = z.infer; export interface MonthlyCheckIn { uuid: string; - title: string; + month: string; checkInDate: string; mediaContentLinks: string[]; isCheckedByMentor: boolean;