From 05532cb61e2244149fc460e4fef282702620a055 Mon Sep 17 00:00:00 2001 From: Raafay-Qureshi Date: Sat, 14 Feb 2026 14:36:56 -0500 Subject: [PATCH 1/3] feat: implement interviewee dashboard epic --- .../controllers/meeting/getUserMeetings.ts | 66 ++-- .../dashboard/components/Availability.tsx | 105 +++++- .../dashboard/components/CalendarStats.tsx | 164 +++++++-- .../features/dashboard/components/Header.tsx | 66 +++- .../components/InterviewScheduleSection.tsx | 313 ++++++++++++------ .../features/dashboard/components/Sidebar.tsx | 173 +++++----- frontend/src/utils/timezone.ts | 137 ++++++++ 7 files changed, 768 insertions(+), 256 deletions(-) create mode 100644 frontend/src/utils/timezone.ts diff --git a/backend/src/controllers/meeting/getUserMeetings.ts b/backend/src/controllers/meeting/getUserMeetings.ts index 0b94e94..9dfbd35 100644 --- a/backend/src/controllers/meeting/getUserMeetings.ts +++ b/backend/src/controllers/meeting/getUserMeetings.ts @@ -23,23 +23,25 @@ export async function getUserMeetings(req: AuthRequest, res: Response, next: Nex const currentUserTeamId = req.user.teamId?.toString(); const targetUserId = req.params.userId; - // Everyone can view their own meetings - if (currentUserId === targetUserId) { - // Determine the user's role to fetch appropriate meetings - if (currentUserRole === UserRole.INTERVIEWER) { - const meetings = await Meeting.find({ interviewerIds: targetUserId }); - return ApiResponseUtil.success(res, meetings, 'Meetings retrieved successfully'); - } else { - // Candidates and admins - const meetings = await Meeting.find({ - $or: [ - { candidateId: targetUserId }, - { interviewerIds: targetUserId } - ] - }); - return ApiResponseUtil.success(res, meetings, 'Meetings retrieved successfully'); - } - } + // Everyone can view their own meetings + if (currentUserId === targetUserId) { + // Determine the user's role to fetch appropriate meetings + if (currentUserRole === UserRole.INTERVIEWER) { + const meetings = await Meeting.find({ interviewerIds: targetUserId }) + .populate('interviewerIds', 'name email'); + return ApiResponseUtil.success(res, meetings, 'Meetings retrieved successfully'); + } else { + // Candidates and admins + const meetings = await Meeting.find({ + $or: [ + { candidateId: targetUserId }, + { interviewerIds: targetUserId } + ] + }) + .populate('interviewerIds', 'name email'); + return ApiResponseUtil.success(res, meetings, 'Meetings retrieved successfully'); + } + } // For viewing others' meetings, check team-based permissions const targetUser = await User.findById(targetUserId); @@ -61,20 +63,22 @@ export async function getUserMeetings(req: AuthRequest, res: Response, next: Nex return ApiResponseUtil.error(res, 'Access denied: you can only view meetings of team members', 403); } - // Fetch meetings based on target user's role - const targetUserRole = targetUser.role || UserRole.CANDIDATE; - if (targetUserRole === UserRole.INTERVIEWER) { - const meetings = await Meeting.find({ interviewerIds: targetUserId }); - return ApiResponseUtil.success(res, meetings, 'Meetings retrieved successfully'); - } else { - const meetings = await Meeting.find({ - $or: [ - { candidateId: targetUserId }, - { interviewerIds: targetUserId } - ] - }); - return ApiResponseUtil.success(res, meetings, 'Meetings retrieved successfully'); - } + // Fetch meetings based on target user's role + const targetUserRole = targetUser.role || UserRole.CANDIDATE; + if (targetUserRole === UserRole.INTERVIEWER) { + const meetings = await Meeting.find({ interviewerIds: targetUserId }) + .populate('interviewerIds', 'name email'); + return ApiResponseUtil.success(res, meetings, 'Meetings retrieved successfully'); + } else { + const meetings = await Meeting.find({ + $or: [ + { candidateId: targetUserId }, + { interviewerIds: targetUserId } + ] + }) + .populate('interviewerIds', 'name email'); + return ApiResponseUtil.success(res, meetings, 'Meetings retrieved successfully'); + } } catch (error) { next(error); } diff --git a/frontend/src/features/dashboard/components/Availability.tsx b/frontend/src/features/dashboard/components/Availability.tsx index f18dada..85dc300 100644 --- a/frontend/src/features/dashboard/components/Availability.tsx +++ b/frontend/src/features/dashboard/components/Availability.tsx @@ -1,7 +1,10 @@ import * as React from "react"; -import { useState } from "react"; +import { useState, useContext } from "react"; import { Check } from "lucide-react"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { AuthContext } from "@/features/auth/services/AuthContext"; +import { authenticatedFetch } from "@/features/auth/utils/authClient"; +import { combineDateAndTime, toISOTimestamp, formatDisplayDateOnly } from "@/utils/timezone"; interface AvailabilityData { [dateKey: string]: string[]; @@ -27,10 +30,16 @@ const MONTHS = ["January", "February", "March", "April", "May", "June", "July", const TIME_SLOTS = ["8:00 am", "9:00 am", "10:00 am", "11:00 am", "12:00 pm", "1:00 pm", "2:00 pm", "3:00 pm", "4:00 pm", "5:00 pm", "6:00 pm", "7:00 pm", "8:00 pm", "9:00 pm", "10:00 pm", "11:00 pm"]; export default function Availability() { + const auth = useContext(AuthContext); + const user = auth?.user; + const [selectedDate, setSelectedDate] = useState(null); const [availability, setAvailability] = useState({}); const [currentMonth, setCurrentMonth] = useState(new Date().getMonth()); const [currentYear, setCurrentYear] = useState(new Date().getFullYear()); + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitError, setSubmitError] = useState(null); + const [submitSuccess, setSubmitSuccess] = useState(false); const formatDateKey = (date: Date): string => { const year = date.getFullYear(); @@ -91,8 +100,72 @@ export default function Availability() { }; const handleSubmit = async () => { - console.log("Submitting availability:", availability); - alert("Availability submitted successfully!"); + // Check if user has a team + if (!user?.teamId) { + setSubmitError("You must join a team before submitting availability."); + return; + } + + if (Object.keys(availability).length === 0) { + setSubmitError("Please select at least one time slot."); + return; + } + + setIsSubmitting(true); + setSubmitError(null); + setSubmitSuccess(false); + + try { + // Transform availability data to API format + const availabilitySlots: Array<{ teamId: string; startTime: string; endTime: string; type: string }> = []; + + for (const [dateKey, timeSlots] of Object.entries(availability)) { + const date = new Date(dateKey); + + for (const timeSlot of timeSlots) { + const startDateTime = combineDateAndTime(date, timeSlot); + // Each slot is 1 hour + const endDateTime = new Date(startDateTime); + endDateTime.setHours(endDateTime.getHours() + 1); + + availabilitySlots.push({ + teamId: user.teamId, + startTime: toISOTimestamp(startDateTime), + endTime: toISOTimestamp(endDateTime), + type: "one-time", + }); + } + } + + // Submit each availability slot + const results = await Promise.all( + availabilitySlots.map(async (slot) => { + const response = await authenticatedFetch("/availability", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(slot), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ message: "Failed to submit availability" })); + throw new Error(errorData.message || `Failed to submit slot at ${slot.startTime}`); + } + + return response.json(); + }) + ); + + setSubmitSuccess(true); + // Clear availability after successful submission + setAvailability({}); + setSelectedDate(null); + } catch (err) { + setSubmitError(err instanceof Error ? err.message : "Failed to submit availability. Please try again."); + } finally { + setIsSubmitting(false); + } }; const days = generateCalendarDays(); @@ -156,10 +229,26 @@ export default function Availability() { )} - - - ); + {submitError && ( +
+

{submitError}

+
+ )} + + {submitSuccess && ( +
+

Availability submitted successfully!

+
+ )} + + + +); } diff --git a/frontend/src/features/dashboard/components/CalendarStats.tsx b/frontend/src/features/dashboard/components/CalendarStats.tsx index ae007d2..685def6 100644 --- a/frontend/src/features/dashboard/components/CalendarStats.tsx +++ b/frontend/src/features/dashboard/components/CalendarStats.tsx @@ -1,25 +1,139 @@ -interface CalendarStat { - title: string; - count: number; -} - -interface CalendarStatsProps { - stats: CalendarStat[]; -} - -export default function CalendarStats({ stats }: CalendarStatsProps) { - return ( -
-

Welcome back, Name!

-

Your calendar at a glance

-
- {stats.map((stat, index) => ( -
-

{stat.title}

-

{stat.count}

-
- ))} -
-
- ); -} +import { useContext, useEffect, useState } from "react"; +import { AuthContext } from "@/features/auth/services/AuthContext"; +import { authenticatedFetch } from "@/features/auth/utils/authClient"; + +interface Meeting { + _id: string; + title: string; + startTime: string; + endTime: string; + interviewerIds: Array<{ _id: string; name: string; email: string }>; + candidateId: string; + status: string; +} + +interface CalendarStat { + title: string; + count: number; +} + +export default function CalendarStats() { + const auth = useContext(AuthContext); + const user = auth?.user; + + const [meetings, setMeetings] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchMeetings = async () => { + if (!user?.id) return; + + try { + setIsLoading(true); + const response = await authenticatedFetch(`/meetings/user/${user.id}`); + + if (!response.ok) { + throw new Error("Failed to fetch meetings"); + } + + const data = await response.json(); + setMeetings(data.data || []); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load meetings"); + } finally { + setIsLoading(false); + } + }; + + fetchMeetings(); + }, [user?.id]); + + // Calculate stats based on meetings + const calculateStats = (): CalendarStat[] => { + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const endOfToday = new Date(today); + endOfToday.setHours(23, 59, 59, 999); + + const endOfWeek = new Date(today); + endOfWeek.setDate(today.getDate() + 6); + endOfWeek.setHours(23, 59, 59, 999); + + const endOfNextWeek = new Date(today); + endOfNextWeek.setDate(today.getDate() + 13); + endOfNextWeek.setHours(23, 59, 59, 999); + + const todayCount = meetings.filter((m) => { + const meetingDate = new Date(m.startTime); + return meetingDate >= today && meetingDate <= endOfToday; + }).length; + + const thisWeekCount = meetings.filter((m) => { + const meetingDate = new Date(m.startTime); + return meetingDate >= today && meetingDate <= endOfWeek; + }).length; + + const nextWeekCount = meetings.filter((m) => { + const meetingDate = new Date(m.startTime); + return meetingDate > endOfWeek && meetingDate <= endOfNextWeek; + }).length; + + return [ + { title: "Today's Interviews", count: todayCount }, + { title: "This week's interviews", count: thisWeekCount }, + { title: "Next 7 days", count: nextWeekCount }, + ]; + }; + + const stats = calculateStats(); + + if (isLoading) { + return ( +
+
+
+
+
+ {[1, 2, 3].map((i) => ( +
+
+
+
+ ))} +
+
+
+ ); + } + + if (error) { + return ( +
+

+ Welcome back, {user?.name || "User"}! +

+
+

Failed to load calendar stats: {error}

+
+
+ ); + } + + return ( +
+

+ Welcome back, {user?.name || "User"}! +

+

Your calendar at a glance

+
+ {stats.map((stat, index) => ( +
+

{stat.title}

+

{stat.count}

+
+ ))} +
+
+ ); +} diff --git a/frontend/src/features/dashboard/components/Header.tsx b/frontend/src/features/dashboard/components/Header.tsx index 51ba740..06b8620 100644 --- a/frontend/src/features/dashboard/components/Header.tsx +++ b/frontend/src/features/dashboard/components/Header.tsx @@ -1,15 +1,51 @@ -import { User } from "lucide-react"; - -export default function DashboardHeader() { - return ( -
-
- -
- Name - Interviewer -
-
-
- ); -} +import { User } from "lucide-react"; +import { useContext } from "react"; +import { AuthContext } from "@/features/auth/services/AuthContext"; +import { UserRole } from "@/features/auth/types/authTypes"; + +export default function DashboardHeader() { + const auth = useContext(AuthContext); + const user = auth?.user; + const isLoading = auth?.isLoading; + + // Get display role text + const getRoleDisplay = (role?: UserRole) => { + if (role === UserRole.CANDIDATE) return "Applicant"; + return role || "User"; + }; + + // Show loading state + if (isLoading || !user) { + return ( +
+
+
+
+
+
+
+
+
+ ); + } + + return ( +
+
+ {user.profileImage ? ( + {user.name} + ) : ( + + )} +
+ {user.name} + {getRoleDisplay(user.role)} +
+
+
+ ); +} diff --git a/frontend/src/features/dashboard/components/InterviewScheduleSection.tsx b/frontend/src/features/dashboard/components/InterviewScheduleSection.tsx index fbf4f98..7c8825f 100644 --- a/frontend/src/features/dashboard/components/InterviewScheduleSection.tsx +++ b/frontend/src/features/dashboard/components/InterviewScheduleSection.tsx @@ -1,98 +1,215 @@ -import { useState } from "react"; -import { User } from "lucide-react"; - -interface Interview { - id: number; - role: string; - interviewer: string; - date: string; -} - -interface InterviewScheduleSectionProps { - interviews: Interview[]; - getDayFromDate: (dateString: string) => number; - currentDate: number; -} - -export default function InterviewScheduleSection({ - interviews, - getDayFromDate, - currentDate, -}: InterviewScheduleSectionProps) { - const [activeTab, setActiveTab] = useState("This week"); - const tabs = ["Today", "Tomorrow", "This week", "Next week"]; - - const getFilteredInterviews = (interviewsList: Interview[]) => { - switch (activeTab) { - case "Today": - return interviewsList.filter((interview) => { - const interviewDay = getDayFromDate(interview.date); - return interviewDay === currentDate; - }); - - case "Tomorrow": - return interviewsList.filter((interview) => { - const interviewDay = getDayFromDate(interview.date); - return interviewDay === currentDate + 1; - }); - - case "This week": - return interviewsList.filter((interview) => { - const interviewDay = getDayFromDate(interview.date); - return interviewDay >= currentDate && interviewDay <= currentDate + 6; - }); - - case "Next week": - return interviewsList.filter((interview) => { - const interviewDay = getDayFromDate(interview.date); - return interviewDay >= currentDate + 7 && interviewDay <= currentDate + 13; - }); - - default: - return interviewsList; - } - }; - - return ( -
-

Interview Schedule

- {/* Tabs */} -
- {tabs.map((tab) => ( - - ))} -
- - {/* Interviews List */} -
-

Interviews

-
- {getFilteredInterviews(interviews).map((interview) => ( -
- -
-

- {interview.role} | with {interview.interviewer} -

-

{interview.date}

-
-
- ))} -
-
-
- ); -} +import { useState, useEffect, useContext } from "react"; +import { User } from "lucide-react"; +import { AuthContext } from "@/features/auth/services/AuthContext"; +import { authenticatedFetch } from "@/features/auth/utils/authClient"; + +interface Meeting { + _id: string; + title: string; + startTime: string; + endTime: string; + interviewerIds: Array<{ _id: string; name: string; email: string }>; + candidateId: string; + status: string; +} + +interface InterviewDisplay { + id: string; + role: string; + interviewer: string; + date: string; + startTime: Date; +} + +export default function InterviewScheduleSection() { + const auth = useContext(AuthContext); + const user = auth?.user; + + const [meetings, setMeetings] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [activeTab, setActiveTab] = useState("This week"); + const tabs = ["Today", "Tomorrow", "This week", "Next week"]; + + useEffect(() => { + const fetchMeetings = async () => { + if (!user?.id) return; + + try { + setIsLoading(true); + const response = await authenticatedFetch(`/meetings/user/${user.id}`); + + if (!response.ok) { + throw new Error("Failed to fetch meetings"); + } + + const data = await response.json(); + setMeetings(data.data || []); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load meetings"); + } finally { + setIsLoading(false); + } + }; + + fetchMeetings(); + }, [user?.id]); + + // Format date for display: "Thursday, Oct 9, 2:00 PM" + const formatDisplayDate = (dateString: string): string => { + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { + weekday: "long", + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + }); + }; + + // Transform meetings to interview display format + const transformMeetings = (meetingsList: Meeting[]): InterviewDisplay[] => { + return meetingsList.map((meeting) => ({ + id: meeting._id, + role: meeting.title || "Interview", + interviewer: meeting.interviewerIds.map((i) => i.name).join(", ") || "TBD", + date: formatDisplayDate(meeting.startTime), + startTime: new Date(meeting.startTime), + })); + }; + + const interviews = transformMeetings(meetings); + + // Filter interviews based on active tab + const getFilteredInterviews = (): InterviewDisplay[] => { + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const tomorrow = new Date(today); + tomorrow.setDate(tomorrow.getDate() + 1); + const endOfWeek = new Date(today); + endOfWeek.setDate(today.getDate() + 6); + endOfWeek.setHours(23, 59, 59, 999); + const endOfNextWeek = new Date(today); + endOfNextWeek.setDate(today.getDate() + 13); + endOfNextWeek.setHours(23, 59, 59, 999); + + switch (activeTab) { + case "Today": + return interviews.filter((interview) => { + const interviewDate = new Date(interview.startTime); + return interviewDate >= today && interviewDate < tomorrow; + }); + + case "Tomorrow": + return interviews.filter((interview) => { + const interviewDate = new Date(interview.startTime); + const endOfTomorrow = new Date(tomorrow); + endOfTomorrow.setDate(endOfTomorrow.getDate() + 1); + return interviewDate >= tomorrow && interviewDate < endOfTomorrow; + }); + + case "This week": + return interviews.filter((interview) => { + const interviewDate = new Date(interview.startTime); + return interviewDate >= today && interviewDate <= endOfWeek; + }); + + case "Next week": + return interviews.filter((interview) => { + const interviewDate = new Date(interview.startTime); + return interviewDate > endOfWeek && interviewDate <= endOfNextWeek; + }); + + default: + return interviews; + } + }; + + const filteredInterviews = getFilteredInterviews(); + + if (isLoading) { + return ( +
+
+
+
+ {[1, 2, 3, 4].map((i) => ( +
+ ))} +
+
+ {[1, 2, 3].map((i) => ( +
+
+
+
+
+
+
+ ))} +
+
+
+ ); + } + + if (error) { + return ( +
+

Interview Schedule

+
+

Failed to load interview schedule: {error}

+
+
+ ); + } + + return ( +
+

Interview Schedule

+ {/* Tabs */} +
+ {tabs.map((tab) => ( + + ))} +
+ + {/* Interviews List */} +
+

Interviews

+ {filteredInterviews.length === 0 ? ( +
+

No interviews scheduled for {activeTab.toLowerCase()}

+
+ ) : ( +
+ {filteredInterviews.map((interview) => ( +
+ +
+

+ {interview.role} | with {interview.interviewer} +

+

{interview.date}

+
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/frontend/src/features/dashboard/components/Sidebar.tsx b/frontend/src/features/dashboard/components/Sidebar.tsx index fa83761..a0fa6ce 100644 --- a/frontend/src/features/dashboard/components/Sidebar.tsx +++ b/frontend/src/features/dashboard/components/Sidebar.tsx @@ -1,79 +1,94 @@ -import { CalendarDays, LayoutDashboard, Users, Settings, Plus } from "lucide-react"; - -interface DashboardSidebarProps { - activePage?: string; - onPageChange?: (page: string) => void; -} - -export default function DashboardSidebar({ activePage = "dashboard", onPageChange }: DashboardSidebarProps) { - const handleAdminSettingsClick = (e: React.MouseEvent) => { - e.preventDefault(); - onPageChange?.("admin-settings"); - }; - - const handleAvailabilityClick = (e: React.MouseEvent) => { - e.preventDefault(); - onPageChange?.("availability"); - }; - - return ( - - ); -} +import { CalendarDays, LayoutDashboard, Users, Settings, Plus } from "lucide-react"; +import { useContext } from "react"; +import { AuthContext } from "@/features/auth/services/AuthContext"; +import { UserRole } from "@/features/auth/types/authTypes"; + +interface DashboardSidebarProps { + activePage?: string; + onPageChange?: (page: string) => void; +} + +export default function DashboardSidebar({ activePage = "dashboard", onPageChange }: DashboardSidebarProps) { + const auth = useContext(AuthContext); + const user = auth?.user; + + // Check if user is a candidate + const isCandidate = user?.role === UserRole.CANDIDATE; + + const handleAdminSettingsClick = (e: React.MouseEvent) => { + e.preventDefault(); + onPageChange?.("admin-settings"); + }; + + const handleAvailabilityClick = (e: React.MouseEvent) => { + e.preventDefault(); + onPageChange?.("availability"); + }; + + return ( + + ); +} diff --git a/frontend/src/utils/timezone.ts b/frontend/src/utils/timezone.ts new file mode 100644 index 0000000..a47b011 --- /dev/null +++ b/frontend/src/utils/timezone.ts @@ -0,0 +1,137 @@ +/** + * Timezone conversion utilities + * Handles conversion between local time strings and ISO timestamps + */ + +/** + * Parse a time string like "8:00 am" or "2:30 pm" into hours and minutes + * @param timeString - Time string in format like "8:00 am" + * @returns Object with hours (0-23) and minutes (0-59) + */ +export function parseTimeString(timeString: string): { hours: number; minutes: number } { + const cleanTime = timeString.toLowerCase().trim(); + const isPM = cleanTime.includes("pm"); + const isAM = cleanTime.includes("am"); + + // Remove am/pm and trim + const timePart = cleanTime.replace(/[ap]m/, "").trim(); + const [hoursStr, minutesStr] = timePart.split(":"); + + let hours = parseInt(hoursStr, 10); + const minutes = parseInt(minutesStr, 10) || 0; + + // Convert to 24-hour format + if (isPM && hours !== 12) { + hours += 12; + } else if (isAM && hours === 12) { + hours = 0; + } + + return { hours, minutes }; +} + +/** + * Combine a date from calendar selection with a time string + * @param date - Date object (from calendar selection) + * @param timeString - Time string like "8:00 am" + * @returns Date object with combined date and time in local timezone + */ +export function combineDateAndTime(date: Date, timeString: string): Date { + const { hours, minutes } = parseTimeString(timeString); + + const result = new Date(date); + result.setHours(hours, minutes, 0, 0); + + return result; +} + +/** + * Convert a Date object to ISO 8601 timestamp string + * @param date - Date object + * @returns ISO 8601 string (e.g., "2024-01-15T14:30:00.000Z") + */ +export function toISOTimestamp(date: Date): string { + return date.toISOString(); +} + +/** + * Format a Date object for display + * @param date - Date object or ISO string + * @returns Formatted string like "Thursday, Jan 15, 2:00 PM" + */ +export function formatDisplayDate(date: Date | string): string { + const d = typeof date === "string" ? new Date(date) : date; + + return d.toLocaleDateString("en-US", { + weekday: "long", + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + }); +} + +/** + * Format a Date object for display with date only + * @param date - Date object or ISO string + * @returns Formatted string like "Thursday, Jan 15" + */ +export function formatDisplayDateOnly(date: Date | string): string { + const d = typeof date === "string" ? new Date(date) : date; + + return d.toLocaleDateString("en-US", { + weekday: "long", + month: "short", + day: "numeric", + }); +} + +/** + * Add hours to a date + * @param date - Date object + * @param hours - Number of hours to add + * @returns New Date object + */ +export function addHours(date: Date, hours: number): Date { + const result = new Date(date); + result.setHours(result.getHours() + hours); + return result; +} + +/** + * Check if a date is today + * @param date - Date to check + * @returns boolean + */ +export function isToday(date: Date | string): boolean { + const d = typeof date === "string" ? new Date(date) : date; + const today = new Date(); + + return ( + d.getDate() === today.getDate() && + d.getMonth() === today.getMonth() && + d.getFullYear() === today.getFullYear() + ); +} + +/** + * Get the start of the day + * @param date - Date object + * @returns Date set to midnight (00:00:00) + */ +export function startOfDay(date: Date): Date { + const result = new Date(date); + result.setHours(0, 0, 0, 0); + return result; +} + +/** + * Get the end of the day + * @param date - Date object + * @returns Date set to 23:59:59 + */ +export function endOfDay(date: Date): Date { + const result = new Date(date); + result.setHours(23, 59, 59, 999); + return result; +} From 92666314ccc5af04be73384ed3a3d941de91bbdc Mon Sep 17 00:00:00 2001 From: Raafay-Qureshi Date: Sat, 14 Feb 2026 14:48:10 -0500 Subject: [PATCH 2/3] Typescript errors fixed --- frontend/src/features/dashboard/Dashboard.tsx | 88 +------------------ .../dashboard/components/Availability.tsx | 4 +- 2 files changed, 4 insertions(+), 88 deletions(-) diff --git a/frontend/src/features/dashboard/Dashboard.tsx b/frontend/src/features/dashboard/Dashboard.tsx index b72ddb5..90549b0 100644 --- a/frontend/src/features/dashboard/Dashboard.tsx +++ b/frontend/src/features/dashboard/Dashboard.tsx @@ -10,86 +10,6 @@ import { AdminSettings } from "../admin/components"; export default function Dashboard() { const [activePage, setActivePage] = useState("dashboard"); - const currentDate = new Date().getDate(); // get current date - - // Function to extract day number from date string - const getDayFromDate = (dateString: string): number => { - const match = dateString.match(/(\d+)/); - return match ? parseInt(match[1]) : 0; - }; - - // list containing the upcoming interviews, uses the CalendarCard componenet to allow for dynamically added interviews - // add an object to this list an it will appear as a new interview - const interviews = [ - { - id: 1, - role: "Frontend Developer", - interviewer: "Interviewer #2", - date: "Thursday, Oct 9, 2:00 PM", - }, - { - id: 2, - role: "Backend Developer", - interviewer: "Interviewer #3", - date: " Thursday, Oct 9, 4:00 PM", - }, - { - id: 3, - role: "Full Stack Developer", - interviewer: "Interviewer #1", - date: "Saturday, Oct 10, 3:30 PM", - }, - { - id: 4, - role: "Full Stack Developer", - interviewer: "Interviewer #2", - date: "Monday, Oct 20, 3:30 PM", - }, - { - id: 5, - role: "Full Stack Developer", - interviewer: "Interviewer #4", - date: "monday, Oct 11, 3:30 PM", - }, - ]; - - // TODO: create a system that will schedule interviews and connect it to this list - - // Function to categorize interviews based on current date - const categorizeInterviews = (interviewsList: typeof interviews) => { - // create a list with all interviews happening today - const todayInterviews = interviewsList.filter((interview) => { - const interviewDay = getDayFromDate(interview.date); - return interviewDay === currentDate; - }); - - // create a list with all interviews happening this week - const thisWeekInterviews = interviewsList.filter((interview) => { - const interviewDay = getDayFromDate(interview.date); - return interviewDay >= currentDate && interviewDay <= currentDate + 6; - }); - - // create a list with all interviews happening in the next 7 days - const next7DaysInterviews = interviewsList.filter((interview) => { - const interviewDay = getDayFromDate(interview.date); - return interviewDay >= currentDate && interviewDay <= currentDate + 7; - }); - - // return these three in a json format - will become stats - return { - today: todayInterviews.length, - thisWeek: thisWeekInterviews.length, - next7Days: next7DaysInterviews.length, - }; - }; - - const stats = categorizeInterviews(interviews); - - const calendarStats = [ - { title: "Today's Interviews", count: stats.today }, - { title: "This week's interviews", count: stats.thisWeek }, - { title: "Next 7 days", count: stats.next7Days }, - ]; return (
@@ -100,12 +20,8 @@ export default function Dashboard() { {activePage === "dashboard" ? ( <> - - + + ) : activePage === "admin-settings" ? ( diff --git a/frontend/src/features/dashboard/components/Availability.tsx b/frontend/src/features/dashboard/components/Availability.tsx index 85dc300..c025ada 100644 --- a/frontend/src/features/dashboard/components/Availability.tsx +++ b/frontend/src/features/dashboard/components/Availability.tsx @@ -4,7 +4,7 @@ import { Check } from "lucide-react"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import { AuthContext } from "@/features/auth/services/AuthContext"; import { authenticatedFetch } from "@/features/auth/utils/authClient"; -import { combineDateAndTime, toISOTimestamp, formatDisplayDateOnly } from "@/utils/timezone"; +import { combineDateAndTime, toISOTimestamp } from "@/utils/timezone"; interface AvailabilityData { [dateKey: string]: string[]; @@ -138,7 +138,7 @@ export default function Availability() { } // Submit each availability slot - const results = await Promise.all( + await Promise.all( availabilitySlots.map(async (slot) => { const response = await authenticatedFetch("/availability", { method: "POST", From 2f84abd647743f6ed39a1941093cb8230ca7887e Mon Sep 17 00:00:00 2001 From: Raafay-Qureshi Date: Sat, 14 Feb 2026 18:48:25 -0500 Subject: [PATCH 3/3] fixed availability --- .../features/dashboard/components/Availability.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/dashboard/components/Availability.tsx b/frontend/src/features/dashboard/components/Availability.tsx index c025ada..a72c323 100644 --- a/frontend/src/features/dashboard/components/Availability.tsx +++ b/frontend/src/features/dashboard/components/Availability.tsx @@ -132,7 +132,7 @@ export default function Availability() { teamId: user.teamId, startTime: toISOTimestamp(startDateTime), endTime: toISOTimestamp(endDateTime), - type: "one-time", + type: "available", }); } } @@ -229,6 +229,15 @@ export default function Availability() {
)} + {!user?.teamId && ( +
+

+ Cannot submit availability: You must join a team first. + Please contact your moderator or check your email for a team invitation. +

+
+ )} + {submitError && (

{submitError}