-
-
- ←
-
- Back
-
-
-
+ const renderMenteeList = () => (
+
+
+
Mentee Applications
+
+
+ {tabData.map((tab) => (
+ {
+ setActiveTab(tab.status);
+ }}
+ >
+ {tab.label}
+
+ {menteeApplications?.filter((m) => m.state === tab.status)
+ .length || 0}
+
+
+ ))}
+
+
+ {filteredApplications.map(renderMenteeLink)}
+ {filteredApplications.length === 0 && (
+
No mentee applications available.
+ )}
+
+
+ );
+
+ return (
+
+ {isMobile ? (
- } />
+
+ {
+ navigate('/mentor/dashboard');
+ }}
+ isMobile={isMobile}
+ />
+ }
+ />
+ ) : (
+ <>
+ {renderMenteeList()}
+
+
+ } />
+ {}} isMobile={isMobile} />
+ }
+ />
+
+
+ >
+ )}
+
+ );
+};
+
+const WelcomeMessage: React.FC = () => (
+
+
+ Welcome to the ScholarX Mentee Dashboard!
+
+
+ Here, you can view your mentees and provide feedback on their monthly
+ check-ins. Click on a mentee to view their profile and their monthly
+ check-in submissions.
+
+
+);
+
+const MenteeDetails: React.FC<{ onBack: () => void; isMobile: boolean }> = ({
+ onBack,
+ isMobile,
+}) => {
+ const { menteeId } = useParams<{ menteeId: string }>();
+ const [showGuidelines, setShowGuidelines] = useState(true);
+
+ const toggleGuidelines = () => {
+ setShowGuidelines((prev) => !prev);
+ };
+
+ const {
+ data: checkInHistory = [],
+ isLoading,
+ refetch,
+ } = useMonthlyCheckIns(menteeId ?? '');
+
+ return (
+
+ {isMobile && (
+
+ ← Back to Mentee List
+
+ )}
+
+
+
+
+
+ Monthly Check-Ins
+
+
+
+
);
diff --git a/src/schemas.ts b/src/schemas.ts
index b72bec90..e1f8ed34 100644
--- a/src/schemas.ts
+++ b/src/schemas.ts
@@ -4,7 +4,11 @@ export const MenteeApplicationSchema = z.object({
firstName: z.string().min(1, { message: 'First name cannot be empty' }),
lastName: z.string().min(1, { message: 'Last name cannot be empty' }),
email: z.string().email({ message: 'Invalid email address' }),
- contactNo: z.string().min(1, { message: 'Contact number cannot be empty' }),
+ contactNo: z
+ .string()
+ .min(1, { message: 'Contact number cannot be empty' })
+ .startsWith('+', { message: "Must start with '+' for country code" })
+ .regex(/^\+\d{6,14}$/, { message: 'Invalid contact number' }),
company: z.string().min(1, { message: 'Company cannot be empty' }).optional(),
profilePic: z
.string()
@@ -13,19 +17,23 @@ export const MenteeApplicationSchema = z.object({
.string()
.min(1, { message: 'Position cannot be empty' })
.optional(),
- cv: z.string().min(1, { message: 'CV cannot be empty' }),
+ cv: z
+ .string()
+ .min(1, { message: 'CV cannot be empty' })
+ .url({ message: 'Please enter a valid link' }),
isUndergrad: z.boolean(),
- consentGiven: z.boolean().refine((val) => val, {
- message: 'You must give your consent to proceed.',
- }),
graduatedYear: z
.number({ invalid_type_error: 'Graduated year is required' })
+ .int({ message: 'Graduated year must be an integer' })
.refine(
(data) => {
- return data === undefined || (!isNaN(data) && data >= 1980);
+ return (
+ data === undefined ||
+ (!isNaN(data) && data >= 1980 && data <= new Date().getFullYear())
+ );
},
{
- message: 'Graduated year must be a year',
+ message: 'Graduated year must be a valid year',
}
)
.optional(),
@@ -35,6 +43,7 @@ export const MenteeApplicationSchema = z.object({
.optional(),
yearOfStudy: z
.number({ invalid_type_error: 'Year of study is required' })
+ .int({ message: 'Year of study must be an integer' })
.gte(1, { message: 'Year must be greater than 0' })
.lte(4, { message: 'Year must be less than or equal 4' })
.optional(),
@@ -43,13 +52,18 @@ export const MenteeApplicationSchema = z.object({
submission: z
.string()
.url({ message: 'Please submit a valid video submission' }),
+ consentGiven: z.boolean().optional(),
});
export const MentorApplicationSchema = z.object({
firstName: z.string().min(1, { message: 'First name cannot be empty' }),
lastName: z.string().min(1, { message: 'Last name cannot be empty' }),
email: z.string().email({ message: 'Invalid email address' }),
- contactNo: z.string().min(1, { message: 'Contact number cannot be empty' }),
+ contactNo: z
+ .string()
+ .min(1, { message: 'Contact number cannot be empty' })
+ .startsWith('+', { message: "Must start with '+' for country code" })
+ .regex(/^\+\d{6,14}$/, { message: 'Invalid contact number' }),
country: z.string().min(1, { message: 'Country cannot be empty' }),
position: z.string().min(1, { message: 'Position cannot be empty' }),
expertise: z.string().min(1, { message: 'Expertise cannot be empty' }),
@@ -65,22 +79,34 @@ export const MentorApplicationSchema = z.object({
profilePic: z
.string()
.min(1, { message: 'The profile picture cannot be empty' }),
- cv: z.string().min(1, { message: 'CV cannot be empty' }),
+ cv: z
+ .string()
+ .min(1, { message: 'CV cannot be empty' })
+ .url({ message: 'Please enter a valid link' }),
menteeExpectations: z
.string()
.min(1, { message: 'Mentee expectations cannot be empty' }),
mentoringPhilosophy: z
.string()
.min(1, { message: 'Mentoring philosophy cannot be empty' }),
- noOfMentees: z.number().min(0, {
- message: 'Number of mentees must be greater than or equal to 0',
- }),
- canCommit: z.boolean().refine((val) => val, {
- message: 'You must mention if you can commit',
- }),
+ noOfMentees: z
+ .number({ invalid_type_error: 'Number of mentees is required' })
+ .min(1, { message: 'Number of mentees must be greater than or equal to 1' })
+ .int({ message: 'Number of mentees must be an integer number' }),
mentoredYear: z
.number({ invalid_type_error: 'Mentored year is required' })
- .or(z.number().min(0))
+ .int({ message: 'Mentored year must be a valid year' })
+ .refine(
+ (data) => {
+ return (
+ data === undefined ||
+ (!isNaN(data) && data >= 2018 && data <= new Date().getFullYear())
+ );
+ },
+ {
+ message: 'Mentored year must be a valid year',
+ }
+ )
.optional(),
category: z.string().min(1, { message: 'Category cannot be empty' }),
institution: z.string().min(1, { message: 'Institution cannot be empty' }),
@@ -94,13 +120,43 @@ export const MentorApplicationSchema = z.object({
.url({ message: 'Invalid website URL' })
.optional()
.or(z.literal('')),
+ canCommit: z.boolean().optional(),
});
export const MenteeCheckInSchema = z.object({
- generalUpdate: z.string().min(1, 'Please provide general updates'),
- progressUpdate: z.string().min(1, 'Please summarize your progress'),
- mediaLink: z
+ title: z.string().min(1, 'Title is required'),
+ generalUpdatesAndFeedback: z
.string()
- .url('Please provide a valid URL')
- .min(1, 'Please provide a media link'),
+ .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'),
+});
+
+export const MentorFeedbackSchema = z.object({
+ menteeId: z.string().min(1, 'Mentee ID is required'),
+ checkInId: z.string().min(1, 'Check-in ID is required'),
+ mentorFeedback: z.string().optional(),
+ isCheckedByMentor: z.literal(true, {
+ errorMap: () => ({ message: 'You must mark this as checked' }),
+ }),
+});
+
+export const mentorTermsAgreementModalSchema = z.object({
+ agreed: z.boolean().refine((val) => val, {
+ message: 'You must agree to the ScholarX Mentor Guide',
+ }),
+ canCommit: z.boolean().refine((val) => val, {
+ message: 'You must mention if you can commit',
+ }),
+});
+
+export const menteeTermsAgreementModalSchema = z.object({
+ agreed: z.boolean().refine((val) => val, {
+ message: 'You must agree to the ScholarX Mentee Guide',
+ }),
+ consentGiven: z.boolean().refine((val) => val, {
+ message: 'You must give consent to proceed',
+ }),
});
diff --git a/src/types.ts b/src/types.ts
index 2c4bfe98..15d4bba7 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -2,6 +2,7 @@ import { type z } from 'zod';
import {
type MentorApplicationSchema,
type MenteeApplicationSchema,
+ MenteeCheckInSchema,
} from './schemas';
import {
type StatusUpdatedBy,
@@ -102,3 +103,38 @@ export interface PasswordUpdateData {
token: string;
newPassword: string;
}
+
+export type MenteeCheckInForm = z.infer
;
+
+export interface MonthlyCheckIn {
+ uuid: string;
+ title: string;
+ checkInDate: string;
+ mediaContentLinks: string[];
+ isCheckedByMentor: boolean;
+ mentorCheckedDate: string;
+ mentorFeedback: string;
+ generalUpdatesAndFeedback: string;
+ progressTowardsGoals?: string;
+ mentee: Mentee;
+}
+
+export interface MonthlyCheckingProps {
+ isMentorView: boolean;
+ menteeId: string;
+}
+
+export interface MentorFeedbackForm {
+ checkInId: string;
+ menteeId: string;
+ mentorFeedback: string;
+ isCheckedByMentor: boolean;
+}
+
+export interface NotificationProps {
+ iconColor?: string;
+ badgeColor?: string;
+ textColor?: string;
+ count?: number;
+ className?: string;
+}
diff --git a/src/utils.ts b/src/utils.ts
index 92425c82..b5003fbd 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,3 +1,5 @@
+import { format, isValid, parseISO } from 'date-fns';
+
export const getStateColor = (state: string | undefined) => {
switch (state) {
case 'pending':
@@ -12,3 +14,11 @@ export const getStateColor = (state: string | undefined) => {
return 'bg-gray-100 text-gray-700';
}
};
+
+export const formatDate = (dateString: string | null | undefined): string => {
+ if (!dateString) return 'Date not available';
+ const date = parseISO(dateString);
+ return isValid(date)
+ ? format(date, 'MMMM dd, yyyy, hh:mm a')
+ : 'Invalid date';
+};