Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions app/api/mentors/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { NextResponse } from "next/server";
import { createClient } from "@supabase/supabase-js";

// Force Node.js runtime for API routes
export const runtime = 'nodejs';

// Create Supabase client function to avoid build-time initialization
function getSupabaseClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
}

// GET: List all approved mentors
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const expertise = searchParams.get('expertise');
const type = searchParams.get('type');
const search = searchParams.get('search');

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")
.eq("status", "approved")
.order("created_at", { ascending: false });

if (expertise && expertise !== 'all') {
query = query.contains('expertise_areas', [expertise]);
}

if (type && type !== 'all') {
query = query.contains('mentoring_types', [type]);
}

if (search) {
query = query.or(`first_name.ilike.%${search}%,last_name.ilike.%${search}%,company.ilike.%${search}%,occupation.ilike.%${search}%`);
}

const { data, error } = await query;

if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}

return NextResponse.json({ mentors: data });
}
100 changes: 100 additions & 0 deletions app/api/seed-real-mentors/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { NextResponse } from "next/server";
import { createClient } from "@supabase/supabase-js";

export const runtime = 'nodejs';

function getSupabaseClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
}

const REAL_MENTORS = [
{
first_name: "Deepak",
last_name: "Pandey",
email: "deepak@codeunia.com",
phone: "",
location: "India",
occupation: "Founder & Tech Lead",
company: "Codeunia",
experience: "Full-stack engineer turned founder. Built Codeunia's platform from the ground up. Deep experience in scaling ed-tech products and engineering teams.",
expertise: "Full Stack Development, System Architecture, Startup Engineering, Product Strategy",
linkedin: "https://www.linkedin.com/in/848deepak/",
expertise_areas: ["system-design", "web-development", "cloud-computing"],
mentoring_types: ["career-advice", "project-guidance", "system-design"],
availability: "flexible",
commitment: "occasional",
motivation: "Helping developers bridge the gap between coding tutorials and building production-ready software.",
previous_mentoring: "Guided 100+ students in their transition to professional software engineering roles.",
teaching_style: "Focus on first principles and architectural thinking.",
status: "approved"
},
{
first_name: "Parisha",
last_name: "Sharma",
email: "parisha@codeunia.com",
phone: "",
location: "India",
occupation: "Co-Founder & Operations Lead",
company: "Codeunia",
experience: "Specialist in tech operations and team dynamics. Expert in helping developers navigate their career paths, negotiate offers, and build leadership skills.",
expertise: "Tech Management, Career Strategy, Soft Skills, Agile Methodologies",
linkedin: "https://www.linkedin.com/in/parishasharma93/",
expertise_areas: ["ui-ux", "project-management"], // Mapped to closest available or generic
mentoring_types: ["career-advice", "interview-prep", "one-on-one"],
availability: "weekends",
commitment: "regular",
motivation: "Ensuring developers have the soft skills and strategic mindset needed to succeed in the industry.",
previous_mentoring: "Career coach for early-stage professionals.",
teaching_style: "Empathetic, structured, and goal-oriented coaching.",
status: "approved"
},
{
first_name: "Akshay",
last_name: "Kumar",
email: "akshay.allen26200@gmail.com",
phone: "",
location: "India",
occupation: "Web Development Lead",
company: "Codeunia",
experience: "Senior Frontend Engineer with a focus on performance and user experience. Architected the core learning platform using Next.js and React Server Components.",
expertise: "Advanced React, Next.js, Frontend Architecture, Web Performance",
linkedin: "https://www.linkedin.com/in/akshaykumar0611/",
expertise_areas: ["web-development", "ui-ux", "system-design"],
mentoring_types: ["code-reviews", "project-guidance", "one-on-one"],
availability: "evenings",
commitment: "intensive",
motivation: "Passionate about writing clean, maintainable code and teaching modern web standards.",
previous_mentoring: "Lead code reviewer and technical mentor for Codeunia interns.",
teaching_style: "Hands-on pair programming and detailed code reviews.",
status: "approved"
}
];

export async function GET() {
const supabase = getSupabaseClient();

// 1. Delete existing mentors to reset data
const { error: deleteError } = await supabase
.from("mentor_applications")
.delete()
.neq("id", "00000000-0000-0000-0000-000000000000");

if (deleteError) {
return NextResponse.json({ error: deleteError.message }, { status: 500 });
}

// 2. Insert refined mentors
const { data, error } = await supabase
.from("mentor_applications")
.insert(REAL_MENTORS)
.select();

if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}

return NextResponse.json({ success: true, count: data.length, data });
}
140 changes: 140 additions & 0 deletions app/protected/mentorship/components/MentorCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"use client";

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 { RequestDialog } from "./RequestDialog";

export interface Mentor {
id: string;
first_name: string;
last_name: string;
company: string;
occupation: string;
expertise: string;
expertise_areas: string[];
mentoring_types: string[];
linkedin: string;
availability: string;
created_at: string;
}

interface MentorCardProps {
mentor: Mentor;
}

const EXPERTISE_LABELS: Record<string, string> = {
"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<string, string> = {
"Deepak Pandey": "/images/team/deepak.jpeg",
"Parisha Sharma": "/images/team/parisha.jpeg",
"Akshay Kumar": "/images/team/akshay.jpg",
};

export function MentorCard({ mentor }: MentorCardProps) {
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}`;

return (
<Card className="flex flex-col h-full overflow-hidden border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900/50 hover:shadow-lg transition-all duration-300 group">
<CardHeader className="p-6 pb-4 space-y-4">
<div className="flex justify-between items-start">
<div className="flex gap-4">
<Avatar className="h-16 w-16 border-2 border-primary/10">
<AvatarImage src={imageSrc} className="object-cover" />
<AvatarFallback className="bg-primary/5 text-primary text-lg font-semibold">
{initials}
</AvatarFallback>
</Avatar>
<div>
<h3 className="font-bold text-lg leading-none mb-1 group-hover:text-primary transition-colors">
{fullName}
</h3>
<div className="flex items-center text-sm text-muted-foreground mb-1">
<Briefcase className="h-3.5 w-3.5 mr-1.5" />
{mentor.occupation} at {mentor.company}
</div>
{mentor.linkedin && (
<a
href={mentor.linkedin}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-xs text-blue-500 hover:underline"
>
<Linkedin className="h-3 w-3 mr-1" />
LinkedIn Profile
</a>
)}
</div>
</div>
</div>
</CardHeader>

<CardContent className="p-6 pt-0 flex-grow space-y-4">
<div className="space-y-2">
<p className="text-sm text-muted-foreground line-clamp-3">
{mentor.expertise}
</p>
</div>

<div className="space-y-3">
<div className="flex flex-wrap gap-1.5">
{mentor.expertise_areas?.slice(0, 4).map((area) => (
<Badge
key={area}
variant="secondary"
className="text-xs bg-primary/5 text-primary hover:bg-primary/10 border-transparent"
>
{EXPERTISE_LABELS[area] || area}
</Badge>
))}
{mentor.expertise_areas?.length > 4 && (
<Badge variant="outline" className="text-xs">
+{mentor.expertise_areas.length - 4} more
</Badge>
)}
</div>
</div>

<div className="flex items-center gap-4 text-xs text-muted-foreground pt-2 border-t border-border/50">
<div className="flex items-center">
<Clock className="h-3.5 w-3.5 mr-1.5" />
{mentor.availability}
</div>
<div className="flex items-center">
<GraduationCap className="h-3.5 w-3.5 mr-1.5" />
{mentor.mentoring_types?.length || 0} Types
</div>
</div>
</CardContent>

<CardFooter className="p-6 pt-0 mt-auto">
<RequestDialog
mentorName={fullName}
mentorId={mentor.id}
trigger={
<Button className="w-full bg-primary/10 text-primary hover:bg-primary/20 shadow-none border-0">
Request Mentorship
</Button>
}
/>
</CardFooter>
</Card>
);
}
98 changes: 98 additions & 0 deletions app/protected/mentorship/components/MentorshipFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use client";

import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Search, Filter } from "lucide-react";

interface MentorshipFiltersProps {
search: string;
setSearch: (value: string) => void;
expertise: string;
setExpertise: (value: string) => void;
type: string;
setType: (value: string) => void;
}

const EXPERTISE_OPTIONS = [
{ value: "all", label: "All Expertise" },
{ value: "web-development", label: "Web Development" },
{ value: "mobile-development", label: "Mobile Development" },
{ value: "ai-ml", label: "AI & Machine Learning" },
{ value: "data-science", label: "Data Science" },
{ value: "cybersecurity", label: "Cybersecurity" },
{ value: "blockchain", label: "Blockchain" },
{ value: "ui-ux", label: "UI/UX Design" },
{ value: "devops", label: "DevOps" },
{ value: "game-development", label: "Game Development" },
{ value: "cloud-computing", label: "Cloud Computing" },
{ value: "system-design", label: "System Design" },
{ value: "algorithms", label: "Algorithms" },
];

const TYPE_OPTIONS = [
{ value: "all", label: "All Mentoring Types" },
{ value: "one-on-one", label: "One-on-One" },
{ value: "group-sessions", label: "Group Sessions" },
{ value: "code-reviews", label: "Code Reviews" },
{ value: "project-guidance", label: "Project Guidance" },
{ value: "career-advice", label: "Career Advice" },
{ value: "interview-prep", label: "Interview Prep" },
];

export function MentorshipFilters({
search,
setSearch,
expertise,
setExpertise,
type,
setType,
}: MentorshipFiltersProps) {
return (
<div className="flex flex-col md:flex-row gap-4 items-center bg-card p-4 rounded-lg border shadow-sm">
<div className="relative w-full md:w-1/3">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search mentors..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-10"
/>
</div>

<div className="flex gap-4 w-full md:w-2/3">
<div className="w-1/2">
<Select value={expertise} onValueChange={setExpertise}>
<SelectTrigger>
<div className="flex items-center gap-2">
<Filter className="h-4 w-4 text-muted-foreground" />
<SelectValue placeholder="Expertise" />
</div>
</SelectTrigger>
<SelectContent>
{EXPERTISE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>

<div className="w-1/2">
<Select value={type} onValueChange={setType}>
<SelectTrigger>
<SelectValue placeholder="Mentoring Type" />
</SelectTrigger>
<SelectContent>
{TYPE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
);
}
Loading
Loading