From 871a03e174ebb18350804bb8039678b81e34e0a2 Mon Sep 17 00:00:00 2001 From: Akshay Date: Fri, 28 Nov 2025 15:44:27 +0530 Subject: [PATCH] feat: Enable direct messaging with mentors from their cards by fetching user IDs and integrating conversation initiation. --- app/api/mentors/route.ts | 4 +- .../mentorship/components/MentorCard.tsx | 142 ++++++++++-------- lib/services/conversationService.ts | 64 +++++++- types/messaging.ts | 8 +- 4 files changed, 147 insertions(+), 71 deletions(-) diff --git a/app/api/mentors/route.ts b/app/api/mentors/route.ts index 8a034b63..ba58eac0 100644 --- a/app/api/mentors/route.ts +++ b/app/api/mentors/route.ts @@ -22,7 +22,7 @@ export async function GET(req: Request) { const supabase = getSupabaseClient(); let query = supabase .from("mentor_applications") - .select("id, first_name, last_name, company, occupation, expertise, expertise_areas, mentoring_types, linkedin, availability, created_at") + .select("id, user_id, first_name, last_name, company, occupation, expertise, expertise_areas, mentoring_types, linkedin, availability, created_at") .eq("status", "approved") .order("created_at", { ascending: false }); @@ -43,6 +43,6 @@ export async function GET(req: Request) { if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } - + return NextResponse.json({ mentors: data }); } diff --git a/app/protected/mentorship/components/MentorCard.tsx b/app/protected/mentorship/components/MentorCard.tsx index 6343d928..8ad3caf7 100644 --- a/app/protected/mentorship/components/MentorCard.tsx +++ b/app/protected/mentorship/components/MentorCard.tsx @@ -1,14 +1,19 @@ "use client"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; -import { Briefcase, Linkedin, Clock, GraduationCap } from "lucide-react"; +import { Briefcase, Linkedin, MessageCircle, Loader2 } from "lucide-react"; import { RequestDialog } from "./RequestDialog"; +import { conversationService } from "@/lib/services/conversationService"; +import { toast } from "sonner"; export interface Mentor { id: string; + user_id?: string | null; first_name: string; last_name: string; company: string; @@ -25,21 +30,6 @@ interface MentorCardProps { mentor: Mentor; } -const EXPERTISE_LABELS: Record = { - "web-development": "Web Dev", - "mobile-development": "Mobile Dev", - "ai-ml": "AI & ML", - "data-science": "Data Science", - "cybersecurity": "Cybersecurity", - "blockchain": "Blockchain", - "ui-ux": "UI/UX", - "devops": "DevOps", - "game-development": "Game Dev", - "cloud-computing": "Cloud", - "system-design": "System Design", - "algorithms": "Algorithms", -}; - const IMAGE_MAP: Record = { "Deepak Pandey": "/images/team/deepak.jpeg", "Parisha Sharma": "/images/team/parisha.jpeg", @@ -47,10 +37,27 @@ const IMAGE_MAP: Record = { }; export function MentorCard({ mentor }: MentorCardProps) { + const router = useRouter(); + const [loading, setLoading] = useState(false); const initials = `${mentor.first_name[0]}${mentor.last_name[0]}`; const fullName = `${mentor.first_name} ${mentor.last_name}`; const imageSrc = IMAGE_MAP[fullName] || `https://api.dicebear.com/7.x/avataaars/svg?seed=${mentor.id}`; + const handleMessage = async () => { + if (!mentor.user_id) return; + + setLoading(true); + try { + const conversation = await conversationService.getOrCreateMentorshipConversation(mentor.user_id); + router.push(`/protected/messages?conversation=${conversation.id}`); + } catch (error) { + console.error("Failed to start conversation:", error); + toast.error("Failed to start conversation. Please try again."); + } finally { + setLoading(false); + } + }; + return ( @@ -63,77 +70,92 @@ export function MentorCard({ mentor }: MentorCardProps) {
-

- {fullName} +

+ {mentor.first_name} {mentor.last_name}

-
- +
+ {mentor.occupation} at {mentor.company}
- {mentor.linkedin && ( - - - LinkedIn Profile - - )}
+ {mentor.linkedin && ( + + + + )}
- -
-

+ +

+

{mentor.expertise}

-
- -
- {mentor.expertise_areas?.slice(0, 4).map((area) => ( + {mentor.expertise_areas.slice(0, 3).map((area) => ( - {EXPERTISE_LABELS[area] || area} + {area.replace("-", " ")} ))} - {mentor.expertise_areas?.length > 4 && ( + {mentor.expertise_areas?.length > 3 && ( - +{mentor.expertise_areas.length - 4} more + +{mentor.expertise_areas.length - 3} more )}
-
-
- - {mentor.availability} -
-
- - {mentor.mentoring_types?.length || 0} Types +
+
+ {mentor.mentoring_types.map((type) => ( + + • {type.replace("-", " ")} + + ))}
- - - Request Mentorship - - } - /> + + {mentor.user_id ? ( + + ) : ( + + Request Mentorship + + } + /> + )} ); diff --git a/lib/services/conversationService.ts b/lib/services/conversationService.ts index 6899e16f..93e96a21 100644 --- a/lib/services/conversationService.ts +++ b/lib/services/conversationService.ts @@ -9,7 +9,7 @@ export class ConversationService { // Decrypt message content via API private async decryptContent(encrypted: string | null): Promise { if (!encrypted) return null - + try { const response = await fetch('/api/messages/decrypt', { method: 'POST', @@ -133,9 +133,9 @@ export class ConversationService { } if (!data) { - return { - canMessage: false, - reason: 'This user does not accept messages from you. You need to be mutual connections or they need to enable "Allow messages from anyone".' + return { + canMessage: false, + reason: 'This user does not accept messages from you. You need to be mutual connections or they need to enable "Allow messages from anyone".' } } @@ -171,7 +171,7 @@ export class ConversationService { .eq('conversation_id', conv.conversation_id) const participantIds = participants?.map(p => p.user_id) || [] - + // Check if it's a 1-on-1 with the target user if ( participantIds.length === 2 && @@ -210,7 +210,8 @@ export class ConversationService { .from('conversations') .insert([{ is_group: data.is_group || false, - group_name: data.group_name || null + group_name: data.group_name || null, + conversation_type: data.conversation_type || 'personal' }]) .select() .single() @@ -281,6 +282,57 @@ export class ConversationService { return data || [] } + + // Get or create a mentorship conversation with a mentor + async getOrCreateMentorshipConversation(mentorUserId: string): Promise { + const supabase = this.getSupabaseClient() + const { data: { user } } = await supabase.auth.getUser() + + if (!user) { + throw new Error('User not authenticated') + } + + // Check if mentorship conversation already exists + const { data: existingConversations } = await supabase + .from('conversation_participants') + .select('conversation_id') + .eq('user_id', user.id) + + if (existingConversations) { + for (const conv of existingConversations) { + const { data: conversation } = await supabase + .from('conversations') + .select('*') + .eq('id', conv.conversation_id) + .eq('conversation_type', 'mentorship') + .single() + + if (conversation) { + const { data: participants } = await supabase + .from('conversation_participants') + .select('user_id') + .eq('conversation_id', conv.conversation_id) + + const participantIds = participants?.map(p => p.user_id) || [] + + // Check if it's a 1-on-1 mentorship with the target mentor + if ( + participantIds.length === 2 && + participantIds.includes(user.id) && + participantIds.includes(mentorUserId) + ) { + return conversation as Conversation + } + } + } + } + + // Create new mentorship conversation + return this.createConversation({ + participant_ids: [user.id, mentorUserId], + conversation_type: 'mentorship' + }) + } } export const conversationService = new ConversationService() diff --git a/types/messaging.ts b/types/messaging.ts index 6667845e..f6cd9dd2 100644 --- a/types/messaging.ts +++ b/types/messaging.ts @@ -9,7 +9,8 @@ export interface Conversation { is_group: boolean group_name: string | null group_avatar_url: string | null - + conversation_type?: 'personal' | 'mentorship' | 'group' + // Computed fields unread_count?: number other_user?: { @@ -29,7 +30,7 @@ export interface ConversationParticipant { joined_at: string last_read_at: string is_admin: boolean - + // User details user?: { id: string @@ -51,7 +52,7 @@ export interface Message { is_deleted: boolean reply_to_id: string | null attachments: MessageAttachment[] | null - + // Sender details sender?: { id: string @@ -82,6 +83,7 @@ export interface CreateConversationData { participant_ids: string[] is_group?: boolean group_name?: string + conversation_type?: 'personal' | 'mentorship' | 'group' initial_message?: string }