From 673afa9e5bab758b55fd9c44cd4a4fc74308dc7a Mon Sep 17 00:00:00 2001 From: Aakif-Kohari Date: Wed, 12 Aug 2026 15:56:23 +0000 Subject: [PATCH] feat: build alumni mentorship matching module and directory --- src/components/mentorship/MentorDirectory.tsx | 160 ++++++++++++++++++ .../mentorship/MentorProfileCard.tsx | 101 +++++++++++ .../mentorship/RequestMentorshipModal.tsx | 158 +++++++++++++++++ src/hooks/useMentorshipDirectory.ts | 124 ++++++++++++++ .../20260825000002_alumni_mentorship.sql | 127 ++++++++++++++ 5 files changed, 670 insertions(+) create mode 100644 src/components/mentorship/MentorDirectory.tsx create mode 100644 src/components/mentorship/MentorProfileCard.tsx create mode 100644 src/components/mentorship/RequestMentorshipModal.tsx create mode 100644 src/hooks/useMentorshipDirectory.ts create mode 100644 supabase/migrations/20260825000002_alumni_mentorship.sql diff --git a/src/components/mentorship/MentorDirectory.tsx b/src/components/mentorship/MentorDirectory.tsx new file mode 100644 index 00000000..595b567b --- /dev/null +++ b/src/components/mentorship/MentorDirectory.tsx @@ -0,0 +1,160 @@ +// ============================================================================= +// Component: MentorDirectory +// Issue: #2963 - Build an 'Alumni Mentorship' Matching Module +--Description: The main directory page where students can browse, filter, + --and search for alumni mentors.Includes a sidebar for filters and a grid +--of MentorProfileCards. +// ============================================================================= + +import React, { useState } from 'react'; +import { useMentorshipDirectory } from '../../hooks/useMentorshipDirectory'; +import { MentorProfileCard } from './MentorProfileCard'; +import { RequestMentorshipModal } from './RequestMentorshipModal'; + +export const MentorDirectory: React.FC = () => { + const { mentors, isLoading, error, filters, setFilters, industries, companies } = useMentorshipDirectory(); + const [selectedMentorId, setSelectedMentorId] = useState(null); + + const selectedMentor = mentors.find(m => m.user_id === selectedMentorId); + + return ( +
+
+

Alumni Mentor Network

+

+ Connect with alumni who have volunteered to mentor current students in their industry. +

+
+ +
+ {/* Filters Sidebar */} + + + {/* Mentors Grid */} +
+ {isLoading ? ( +
+ {[1, 2, 3, 4].map(i => ( +
+ ))} +
+ ) : error ? ( +
+ {error} +
+ ) : mentors.length === 0 ? ( +
+ + + +

No Mentors Found

+

Try adjusting your filters or check back later.

+
+ ) : ( +
+ {mentors.map(mentor => ( + setSelectedMentorId(mentor.user_id)} + /> + ))} +
+ )} +
+
+ + {/* Request Modal */} + {selectedMentor && ( + setSelectedMentorId(null)} + /> + )} +
+ ); +}; diff --git a/src/components/mentorship/MentorProfileCard.tsx b/src/components/mentorship/MentorProfileCard.tsx new file mode 100644 index 00000000..5174889f --- /dev/null +++ b/src/components/mentorship/MentorProfileCard.tsx @@ -0,0 +1,101 @@ +// ============================================================================= +// Component: MentorProfileCard +-- Issue: #2963 - Build an 'Alumni Mentorship' Matching Module +-- Description: Renders an individual alumni mentor's profile in the directory +-- grid. Shows their capacity status, expertise tags, and a CTA to request +-- mentorship. +// ============================================================================= + +import React from 'react'; +import { MentorProfile } from '../../hooks/useMentorshipDirectory'; + +interface MentorProfileCardProps { + mentor: MentorProfile; + onRequest: () => void; +} + +export const MentorProfileCard: React.FC = ({ mentor, onRequest }) => { + const isFull = !mentor.is_accepting || mentor.current_mentees >= mentor.max_mentees; + const capacityPct = (mentor.current_mentees / mentor.max_mentees) * 100; + + return ( +
+
+ {mentor.profiles?.avatar_url ? ( + {mentor.profiles.full_name} + ) : ( +
+ {mentor.profiles?.full_name?.charAt(0) || 'A'} +
+ )} +
+

+ {mentor.profiles?.full_name || 'Alumni'} +

+

+ {mentor.job_title} +

+

+ {mentor.company} • {mentor.industry} +

+
+
+ + {mentor.bio && ( +

+ {mentor.bio} +

+ )} + + {/* Expertise Tags */} +
+ {mentor.expertise_tags.slice(0, 4).map(tag => ( + + {tag} + + ))} + {mentor.expertise_tags.length > 4 && ( + + +{mentor.expertise_tags.length - 4} more + + )} +
+ + {/* Capacity Indicator */} +
+
+ Mentorship Capacity + {mentor.current_mentees}/{mentor.max_mentees} +
+
+
60 ? 'bg-yellow-500' : 'bg-green-500' + }`} + style={{ width: `${Math.min(100, capacityPct)}%` }} + >
+
+
+ + {/* Action Button */} + +
+ ); +}; diff --git a/src/components/mentorship/RequestMentorshipModal.tsx b/src/components/mentorship/RequestMentorshipModal.tsx new file mode 100644 index 00000000..835c6af1 --- /dev/null +++ b/src/components/mentorship/RequestMentorshipModal.tsx @@ -0,0 +1,158 @@ +// ============================================================================= +// Component: RequestMentorshipModal +--Issue: #2963 - Build an 'Alumni Mentorship' Matching Module +--Description: Modal for students to send an introductory message to an +--alumni mentor.Enforces character limits and handles submission state. +// ============================================================================= + +import React, { useState } from 'react'; +import { MentorProfile } from '../../hooks/useMentorshipDirectory'; +import { supabase } from '../../../lib/supabaseClient'; + +interface RequestMentorshipModalProps { + mentor: MentorProfile; + onClose: () => void; +} + +export const RequestMentorshipModal: React.FC = ({ mentor, onClose }) => { + const [message, setMessage] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (message.trim().length < 50) { + setError('Please write a slightly longer introduction (at least 50 characters).'); + return; + } + + setIsSubmitting(true); + setError(null); + + try { + const { error: insertError } = await supabase + .from('mentorship_requests') + .insert({ + mentor_id: mentor.user_id, + message: message.trim(), + status: 'pending' + }); + + if (insertError) { + if (insertError.code === '23505') { // Unique violation + throw new Error('You have already sent a request to this mentor.'); + } + throw insertError; + } + + setSuccess(true); + setTimeout(onClose, 2000); // Auto-close after success + } catch (err: any) { + setError(err.message || 'Failed to send request. Please try again.'); + } finally { + setIsSubmitting(false); + } + }; + + if (success) { + return ( +
+
+
+ + + +
+

Request Sent!

+

+ {mentor.profiles?.full_name} will review your request and get back to you soon. +

+
+
+ ); + } + + return ( +
+
+
+ {mentor.profiles?.avatar_url ? ( + + ) : ( +
+ {mentor.profiles?.full_name?.charAt(0) || 'A'} +
+ )} +
+

+ Request Mentorship +

+

+ {mentor.profiles?.full_name} • {mentor.company} +

+
+
+ +
+
+ +