Skip to content
Open
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
22 changes: 9 additions & 13 deletions src/pages/MentorDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useState } from "react";
import { useRole } from "@/contexts/RoleContext";
import { useAuth } from "@/contexts/useAuth";
Expand All @@ -8,14 +7,7 @@ import SessionCard from "@/components/SessionCard";
import { MentorshipMilestones } from "@/components/mentorship/MentorshipMilestones";
import { sessionJoinPath } from "@/lib/sessionJoinPath";
import type { Session } from "@/types";

type MentorSessionRow = {
id: number;
title: string | null;
scheduled_at: string | null;
duration_minutes: number | null;
status: string | null;
};
import { toSessionCardModel, type MentorSessionRow } from "./mentorSessionCard";

type MentorProfile = {
name: string | null;
Expand All @@ -24,6 +16,7 @@ type MentorProfile = {
rating: number | null;
};


const toSessionCardModel = (session: MentorSessionRow): Session => {
const scheduledAt = session.scheduled_at ? new Date(session.scheduled_at) : null;
const id = String(session.id);
Expand Down Expand Up @@ -72,16 +65,20 @@ const MentorDashboard = () => {
setProfile(profileData);
}

// Fetch Upcoming Mentor Sessions
// Fetch Upcoming Mentor Sessions with booked learner profiles
const { data: sessionData } = await supabase
.from("sessions")
.select("id,title,scheduled_at,duration_minutes,status")
.select(
"id,title,scheduled_at,duration_minutes,status,student_id, student:profiles!student_id(id, name, avatar_url)"
)
.eq("status", "scheduled")
.eq("mentor_id", user.id)
.limit(4);

if (sessionData) {
setUpcomingSessions(sessionData.map(toSessionCardModel));
setUpcomingSessions(
(sessionData as MentorSessionRow[]).map(toSessionCardModel)
);
}
} catch (err) {
console.error("Failed to fetch mentor dashboard data", err);
Expand Down Expand Up @@ -161,4 +158,3 @@ const MentorDashboard = () => {
};

export default MentorDashboard;

77 changes: 77 additions & 0 deletions src/pages/mentorSessionCard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { describe, it, expect } from "vitest";
import { toSessionCardModel } from "./mentorSessionCard";

describe("toSessionCardModel", () => {
it("maps distinct learner profiles onto different session cards", () => {
const alice = toSessionCardModel({
id: 1,
title: "React fundamentals",
scheduled_at: "2026-08-10T15:00:00.000Z",
duration_minutes: 45,
status: "scheduled",
student_id: "learner-alice",
student: {
id: "learner-alice",
name: "Alice Chen",
avatar_url: "https://cdn.example/alice.png",
},
});

const bob = toSessionCardModel({
id: 2,
title: "System design",
scheduled_at: "2026-08-11T16:00:00.000Z",
duration_minutes: 60,
status: "scheduled",
student_id: "learner-bob",
student: {
id: "learner-bob",
name: "Bob Patel",
avatar_url: "https://cdn.example/bob.png",
},
});

expect(alice.peerId).toBe("learner-alice");
expect(alice.peerName).toBe("Alice Chen");
expect(alice.peerAvatar).toBe("https://cdn.example/alice.png");

expect(bob.peerId).toBe("learner-bob");
expect(bob.peerName).toBe("Bob Patel");
expect(bob.peerAvatar).toBe("https://cdn.example/bob.png");

expect(alice.peerName).not.toBe(bob.peerName);
});

it("falls back to placeholder learner details when the profile is missing", () => {
const session = toSessionCardModel({
id: 3,
title: null,
scheduled_at: null,
duration_minutes: null,
status: "scheduled",
student_id: null,
student: null,
});

expect(session.peerId).toBe("");
expect(session.peerName).toBe("Learner");
expect(session.peerAvatar).toBe("/placeholder.svg");
expect(session.subject).toBe("Mentorship session");
});

it("uses student_id when the nested profile relation is empty", () => {
const session = toSessionCardModel({
id: 4,
title: "Algorithms",
scheduled_at: "2026-08-12T12:00:00.000Z",
duration_minutes: 30,
status: "scheduled",
student_id: "orphan-learner",
student: null,
});

expect(session.peerId).toBe("orphan-learner");
expect(session.peerName).toBe("Learner");
expect(session.peerAvatar).toBe("/placeholder.svg");
});
});
46 changes: 46 additions & 0 deletions src/pages/mentorSessionCard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Session } from "@/types";

type LearnerProfile = {
id: string;
name: string | null;
avatar_url: string | null;
};

export type MentorSessionRow = {
id: number;
title: string | null;
scheduled_at: string | null;
duration_minutes: number | null;
status: string | null;
student_id: string | null;
student: LearnerProfile | LearnerProfile[] | null;
};

const resolveLearner = (session: MentorSessionRow): LearnerProfile | null => {
if (Array.isArray(session.student)) {
return session.student[0] ?? null;
}
return session.student ?? null;
};

export const toSessionCardModel = (session: MentorSessionRow): Session => {
const scheduledAt = session.scheduled_at ? new Date(session.scheduled_at) : null;
const learner = resolveLearner(session);

return {
id: String(session.id),
peerId: learner?.id ?? session.student_id ?? "",
peerName: learner?.name?.trim() || "Learner",
peerAvatar: learner?.avatar_url || "/placeholder.svg",
subject: session.title || "Mentorship session",
date: scheduledAt ? scheduledAt.toLocaleDateString() : "Not scheduled",
time: scheduledAt
? scheduledAt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
: "",
duration: session.duration_minutes ?? 60,
status:
session.status === "ended" || session.status === "completed"
? "completed"
: "upcoming",
};
};
Loading