From f243edcdf37b984f55566145f69d156fd37b14f8 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Mon, 9 Feb 2026 14:56:50 -0500 Subject: [PATCH 01/12] Fix: moved admin dashboard | removed twmerge util & updated related files --- frontend/src/App.tsx | 10 +- frontend/src/components/Home.tsx | 25 ---- .../components/dashboards/AdminDashboard.tsx | 113 ------------------ frontend/src/components/ui/alert.tsx | 8 +- frontend/src/components/ui/badge.tsx | 4 +- frontend/src/components/ui/button.tsx | 4 +- frontend/src/components/ui/card.tsx | 17 +-- frontend/src/components/ui/checkbox.tsx | 9 +- frontend/src/components/ui/input.tsx | 30 ++--- .../admin/AdminDashboard.tsx} | 0 .../candidate}/CandidateDashboard.tsx | 0 frontend/src/lib/utils.ts | 6 - 12 files changed, 29 insertions(+), 197 deletions(-) delete mode 100644 frontend/src/components/Home.tsx delete mode 100644 frontend/src/components/dashboards/AdminDashboard.tsx rename frontend/src/{components/dashboards/AdminDashboard2.tsx => features/admin/AdminDashboard.tsx} (100%) rename frontend/src/{components/dashboards => features/candidate}/CandidateDashboard.tsx (100%) delete mode 100644 frontend/src/lib/utils.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0c01554..49ab024 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,7 +2,6 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-d import Navbar from '@/components/ui/Navbar' import ProtectedRoute from '@/components/ProtectedRoute' import StatusDashboard from '@/components/StatusDashboard' -import Home from '@/components/Home' import Welcome from '@/components/Welcome' import { SignupForm, @@ -12,14 +11,13 @@ import { NewPassword, ForgotPassword, } from '@/features/auth/components' -import AdminDashboard2 from '@/components/dashboards/AdminDashboard2' +import AdminDashboard2 from '@/features/admin/AdminDashboard' import JoinATeam from '@/components/JoinATeam' import CreateOrJoinTeam from '@/components/CreateOrJoinTeam' import InterviewerAvailability from '@/components/InterviewerAvailability' import { AuthProvider } from '@/provider/AuthProvider' -import AdminDashboard from '@/components/dashboards/AdminDashboard' import InterviewerDashboard from '@/components/dashboards/InterviewerDashboard' -import CandidateDashboard from '@/components/dashboards/CandidateDashboard' +import CandidateDashboard from '@/features/candidate/CandidateDashboard' import AdminSettings from './components/AdminSettings' function App() { @@ -29,7 +27,6 @@ function App() { } /> - } /> } /> } /> } /> @@ -43,8 +40,7 @@ function App() { } /> {/* Role-Based Dashboards - Protected */} - } /> - } /> + } /> } /> } /> diff --git a/frontend/src/components/Home.tsx b/frontend/src/components/Home.tsx deleted file mode 100644 index ff6bbd5..0000000 --- a/frontend/src/components/Home.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Button } from '@/components/ui/button' -import { Link } from 'react-router-dom' - -const Home = () => { - return ( -
-

schedule app

-

- welcome to the schedule app. -

- -
-
- -
-
-
- ) -} - -export default Home \ No newline at end of file diff --git a/frontend/src/components/dashboards/AdminDashboard.tsx b/frontend/src/components/dashboards/AdminDashboard.tsx deleted file mode 100644 index e404878..0000000 --- a/frontend/src/components/dashboards/AdminDashboard.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { useAuth } from "@/features/auth/hooks/useAuth"; - -export default function AdminDashboard() { - const { user } = useAuth(); - - return ( -
- {/* Header */} -
-
-

Admin Dashboard

-
-
- - {/* Main Content */} -
-
- {/* Welcome Card */} -
-
-

- Welcome back, {user?.name}! 👋 -

-

Role: Administrator

-

Email: {user?.email}

-
-
- - {/* Admin Features Grid */} -
- {/* Users Management */} -
-
-
-
- - - -
-
-
-
- User Management -
-
- Manage all users -
-
-
-
-
-
- - {/* Teams Management */} -
-
-
-
- - - -
-
-
-
- Teams -
-
- Manage teams -
-
-
-
-
-
- - {/* Schedule Overview */} -
-
-
-
- - - -
-
-
-
- Schedules -
-
- View all schedules -
-
-
-
-
-
-
- - {/* Info Box */} -
-

- 🎉 Authentication Working! You're logged in as an Administrator. - This dashboard is a placeholder - actual admin features will be implemented next. -

-
-
-
-
- ); -} - diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx index 5afd41d..44aab6c 100644 --- a/frontend/src/components/ui/alert.tsx +++ b/frontend/src/components/ui/alert.tsx @@ -1,8 +1,6 @@ import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/utils" - const alertVariants = cva( "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", { @@ -26,7 +24,7 @@ const Alert = React.forwardRef<
)) @@ -38,7 +36,7 @@ const AlertTitle = React.forwardRef< >(({ className, ...props }, ref) => (
)) @@ -50,7 +48,7 @@ const AlertDescription = React.forwardRef< >(({ className, ...props }, ref) => (
)) diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx index 644e9dd..8ff71a5 100644 --- a/frontend/src/components/ui/badge.tsx +++ b/frontend/src/components/ui/badge.tsx @@ -1,8 +1,6 @@ import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/utils" - const badgeVariants = cva( "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", { @@ -29,7 +27,7 @@ export interface BadgeProps function Badge({ className, variant, ...props }: BadgeProps) { return ( -
+
) } diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index d8f7202..0be8260 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -2,8 +2,6 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/utils" - const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { @@ -45,7 +43,7 @@ const Button = React.forwardRef( const Comp = asChild ? Slot : "button" return ( diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx index cabfbfc..eb30494 100644 --- a/frontend/src/components/ui/card.tsx +++ b/frontend/src/components/ui/card.tsx @@ -1,17 +1,12 @@ import * as React from "react" -import { cn } from "@/lib/utils" - const Card = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => (
)) @@ -23,7 +18,7 @@ const CardHeader = React.forwardRef< >(({ className, ...props }, ref) => (
)) @@ -35,7 +30,7 @@ const CardTitle = React.forwardRef< >(({ className, ...props }, ref) => (
)) @@ -47,7 +42,7 @@ const CardDescription = React.forwardRef< >(({ className, ...props }, ref) => (
)) @@ -57,7 +52,7 @@ const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( -
+
)) CardContent.displayName = "CardContent" @@ -67,7 +62,7 @@ const CardFooter = React.forwardRef< >(({ className, ...props }, ref) => (
)) diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx index e00a7d3..bfc4d3a 100644 --- a/frontend/src/components/ui/checkbox.tsx +++ b/frontend/src/components/ui/checkbox.tsx @@ -2,22 +2,17 @@ import * as React from "react" import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import { Check } from "lucide-react" -import { cn } from "@/lib/utils" - const Checkbox = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx index 69b64fb..957d1f5 100644 --- a/frontend/src/components/ui/input.tsx +++ b/frontend/src/components/ui/input.tsx @@ -1,22 +1,18 @@ import * as React from "react" -import { cn } from "@/lib/utils" - -const Input = React.forwardRef>( - ({ className, type, ...props }, ref) => { - return ( - - ) - } -) +const Input = React.forwardRef>(( + { className, type, ...props }, + ref +) => { + return ( + + ) +}) Input.displayName = "Input" export { Input } diff --git a/frontend/src/components/dashboards/AdminDashboard2.tsx b/frontend/src/features/admin/AdminDashboard.tsx similarity index 100% rename from frontend/src/components/dashboards/AdminDashboard2.tsx rename to frontend/src/features/admin/AdminDashboard.tsx diff --git a/frontend/src/components/dashboards/CandidateDashboard.tsx b/frontend/src/features/candidate/CandidateDashboard.tsx similarity index 100% rename from frontend/src/components/dashboards/CandidateDashboard.tsx rename to frontend/src/features/candidate/CandidateDashboard.tsx diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts deleted file mode 100644 index bd0c391..0000000 --- a/frontend/src/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} From 54c1542d4cc979fa100c8e8aa9ad84196e2d4fc3 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Mon, 9 Feb 2026 15:18:15 -0500 Subject: [PATCH 02/12] Fix: rough new locations --- frontend/src/App.tsx | 18 +++++++++--------- frontend/src/features/admin/AdminDashboard.tsx | 2 +- .../admin}/AdminSettings.tsx | 0 .../auth}/CreateOrJoinTeam.tsx | 2 +- .../auth/components/ForgotPassword.tsx | 2 +- .../features/auth/components/NewPassword.tsx | 2 +- .../auth/components/NewPasswordMade.tsx | 2 +- .../features/auth/components/TwoFactorAuth.tsx | 4 ++-- .../candidate}/JoinATeam.tsx | 4 ++-- .../dashboard}/InterviewerDashboard.tsx | 0 .../interviewer}/InterviewerAvailability.tsx | 2 +- .../src/{components => }/ui/CalendarCard.tsx | 0 frontend/src/{components => }/ui/Navbar.tsx | 2 +- .../src/{components => ui}/ProtectedRoute.tsx | 0 .../src/{components => ui}/StatusDashboard.tsx | 8 ++++---- frontend/src/{components => ui}/Welcome.tsx | 2 +- frontend/src/{components => }/ui/alert.tsx | 0 frontend/src/{components => }/ui/badge.tsx | 0 frontend/src/{components => }/ui/button.tsx | 0 frontend/src/{components => }/ui/card.tsx | 0 frontend/src/{components => }/ui/checkbox.tsx | 0 frontend/src/{components => }/ui/input.tsx | 0 22 files changed, 25 insertions(+), 25 deletions(-) rename frontend/src/{components => features/admin}/AdminSettings.tsx (100%) rename frontend/src/{components => features/auth}/CreateOrJoinTeam.tsx (97%) rename frontend/src/{components => features/candidate}/JoinATeam.tsx (97%) rename frontend/src/{components/dashboards => features/dashboard}/InterviewerDashboard.tsx (100%) rename frontend/src/{components => features/interviewer}/InterviewerAvailability.tsx (99%) rename frontend/src/{components => }/ui/CalendarCard.tsx (100%) rename frontend/src/{components => }/ui/Navbar.tsx (97%) rename frontend/src/{components => ui}/ProtectedRoute.tsx (100%) rename frontend/src/{components => ui}/StatusDashboard.tsx (97%) rename frontend/src/{components => ui}/Welcome.tsx (99%) rename frontend/src/{components => }/ui/alert.tsx (100%) rename frontend/src/{components => }/ui/badge.tsx (100%) rename frontend/src/{components => }/ui/button.tsx (100%) rename frontend/src/{components => }/ui/card.tsx (100%) rename frontend/src/{components => }/ui/checkbox.tsx (100%) rename frontend/src/{components => }/ui/input.tsx (100%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 49ab024..403ecaf 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,8 +1,8 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom' -import Navbar from '@/components/ui/Navbar' -import ProtectedRoute from '@/components/ProtectedRoute' -import StatusDashboard from '@/components/StatusDashboard' -import Welcome from '@/components/Welcome' +import Navbar from '@/ui/Navbar' +import ProtectedRoute from '@/ui/ProtectedRoute' +import StatusDashboard from '@/ui/StatusDashboard' +import Welcome from '@/ui/Welcome' import { SignupForm, SigninForm, @@ -12,13 +12,13 @@ import { ForgotPassword, } from '@/features/auth/components' import AdminDashboard2 from '@/features/admin/AdminDashboard' -import JoinATeam from '@/components/JoinATeam' -import CreateOrJoinTeam from '@/components/CreateOrJoinTeam' -import InterviewerAvailability from '@/components/InterviewerAvailability' +import JoinATeam from '@/features/candidate/JoinATeam' +import CreateOrJoinTeam from '@/features/auth/CreateOrJoinTeam' +import InterviewerAvailability from '@/features/interviewer/InterviewerAvailability' import { AuthProvider } from '@/provider/AuthProvider' -import InterviewerDashboard from '@/components/dashboards/InterviewerDashboard' +import InterviewerDashboard from '@/features/dashboard/InterviewerDashboard' import CandidateDashboard from '@/features/candidate/CandidateDashboard' -import AdminSettings from './components/AdminSettings' +import AdminSettings from './features/admin/AdminSettings' function App() { return ( diff --git a/frontend/src/features/admin/AdminDashboard.tsx b/frontend/src/features/admin/AdminDashboard.tsx index da446c9..0158890 100644 --- a/frontend/src/features/admin/AdminDashboard.tsx +++ b/frontend/src/features/admin/AdminDashboard.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { Link } from "react-router-dom"; import { CalendarDays, LayoutDashboard, User, Plus, Users, Settings } from "lucide-react"; -import CalendarCard from "@/components/ui/CalendarCard"; +import CalendarCard from "@/ui/CalendarCard"; export default function InterviewerSchedule() { const [activeTab, setActiveTab] = useState("This week"); diff --git a/frontend/src/components/AdminSettings.tsx b/frontend/src/features/admin/AdminSettings.tsx similarity index 100% rename from frontend/src/components/AdminSettings.tsx rename to frontend/src/features/admin/AdminSettings.tsx diff --git a/frontend/src/components/CreateOrJoinTeam.tsx b/frontend/src/features/auth/CreateOrJoinTeam.tsx similarity index 97% rename from frontend/src/components/CreateOrJoinTeam.tsx rename to frontend/src/features/auth/CreateOrJoinTeam.tsx index 4ce8ae7..e14ed8c 100644 --- a/frontend/src/components/CreateOrJoinTeam.tsx +++ b/frontend/src/features/auth/CreateOrJoinTeam.tsx @@ -1,4 +1,4 @@ -import { Button } from "./ui/button"; +import { Button } from "../../ui/button"; import { Link } from "react-router-dom"; export default function CreateOrJoinTeam() { diff --git a/frontend/src/features/auth/components/ForgotPassword.tsx b/frontend/src/features/auth/components/ForgotPassword.tsx index f9f115f..cdd8c97 100644 --- a/frontend/src/features/auth/components/ForgotPassword.tsx +++ b/frontend/src/features/auth/components/ForgotPassword.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/ui/button"; import { ArrowLeft } from "lucide-react"; import { useFormValidation } from "../hooks/useFormValidation"; import { forgotPassword } from "../services/authApi"; diff --git a/frontend/src/features/auth/components/NewPassword.tsx b/frontend/src/features/auth/components/NewPassword.tsx index 949673c..f667df5 100644 --- a/frontend/src/features/auth/components/NewPassword.tsx +++ b/frontend/src/features/auth/components/NewPassword.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; -import { Button } from "@/components/ui/button"; +import { Button } from "@/ui/button"; import { usePasswordValidation, isPasswordValid } from "../hooks/usePasswordValidation"; import { useFormValidation } from "../hooks/useFormValidation"; import { resetPassword } from "../services/authApi"; diff --git a/frontend/src/features/auth/components/NewPasswordMade.tsx b/frontend/src/features/auth/components/NewPasswordMade.tsx index 0481e34..f08cfbe 100644 --- a/frontend/src/features/auth/components/NewPasswordMade.tsx +++ b/frontend/src/features/auth/components/NewPasswordMade.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/components/ui/button"; +import { Button } from "@/ui/button"; import { useNavigate } from "react-router-dom"; export default function NewPasswordMade() { diff --git a/frontend/src/features/auth/components/TwoFactorAuth.tsx b/frontend/src/features/auth/components/TwoFactorAuth.tsx index ad165b7..404aa7e 100644 --- a/frontend/src/features/auth/components/TwoFactorAuth.tsx +++ b/frontend/src/features/auth/components/TwoFactorAuth.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; +import { Button } from "@/ui/button"; +import { Input } from "@/ui/input"; import { ArrowLeft } from "lucide-react"; import { verifyResetCode, verifyEmail, setTokens, getCurrentUser } from "../services/authApi"; import { useAuth } from "../hooks/useAuth"; diff --git a/frontend/src/components/JoinATeam.tsx b/frontend/src/features/candidate/JoinATeam.tsx similarity index 97% rename from frontend/src/components/JoinATeam.tsx rename to frontend/src/features/candidate/JoinATeam.tsx index 6a35853..8e2600f 100644 --- a/frontend/src/components/JoinATeam.tsx +++ b/frontend/src/features/candidate/JoinATeam.tsx @@ -2,8 +2,8 @@ import { useState } from "react"; import { Link } from "react-router-dom"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; +import { Button } from "@/ui/button"; +import { Input } from "@/ui/input"; export default function JoinATeam() { const [email, setEmail] = useState(""); diff --git a/frontend/src/components/dashboards/InterviewerDashboard.tsx b/frontend/src/features/dashboard/InterviewerDashboard.tsx similarity index 100% rename from frontend/src/components/dashboards/InterviewerDashboard.tsx rename to frontend/src/features/dashboard/InterviewerDashboard.tsx diff --git a/frontend/src/components/InterviewerAvailability.tsx b/frontend/src/features/interviewer/InterviewerAvailability.tsx similarity index 99% rename from frontend/src/components/InterviewerAvailability.tsx rename to frontend/src/features/interviewer/InterviewerAvailability.tsx index 6450121..cdb70b6 100644 --- a/frontend/src/components/InterviewerAvailability.tsx +++ b/frontend/src/features/interviewer/InterviewerAvailability.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { Link } from "react-router-dom"; import { ArrowLeft } from "lucide-react"; -import { Checkbox } from "@/components/ui/checkbox"; +import { Checkbox } from "@/ui/checkbox"; interface AvailabilityData { [dateKey: string]: string[]; // dateKey format: "YYYY-MM-DD", value: array of time slots diff --git a/frontend/src/components/ui/CalendarCard.tsx b/frontend/src/ui/CalendarCard.tsx similarity index 100% rename from frontend/src/components/ui/CalendarCard.tsx rename to frontend/src/ui/CalendarCard.tsx diff --git a/frontend/src/components/ui/Navbar.tsx b/frontend/src/ui/Navbar.tsx similarity index 97% rename from frontend/src/components/ui/Navbar.tsx rename to frontend/src/ui/Navbar.tsx index fe07950..838c6df 100644 --- a/frontend/src/components/ui/Navbar.tsx +++ b/frontend/src/ui/Navbar.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { Button } from '@/ui/button' import { Link, useNavigate } from 'react-router-dom' import { useAuth } from '@/features/auth/hooks/useAuth' diff --git a/frontend/src/components/ProtectedRoute.tsx b/frontend/src/ui/ProtectedRoute.tsx similarity index 100% rename from frontend/src/components/ProtectedRoute.tsx rename to frontend/src/ui/ProtectedRoute.tsx diff --git a/frontend/src/components/StatusDashboard.tsx b/frontend/src/ui/StatusDashboard.tsx similarity index 97% rename from frontend/src/components/StatusDashboard.tsx rename to frontend/src/ui/StatusDashboard.tsx index 35ba66b..d0c5d71 100644 --- a/frontend/src/components/StatusDashboard.tsx +++ b/frontend/src/ui/StatusDashboard.tsx @@ -1,8 +1,8 @@ import { useState, useEffect, useCallback } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { Alert, AlertDescription } from "@/components/ui/alert" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/ui/card" +import { Badge } from "@/ui/badge" +import { Button } from "@/ui/button" +import { Alert, AlertDescription } from "@/ui/alert" import { CheckCircle, XCircle, Clock, Terminal } from "lucide-react" import { apiFetch } from '@/utils/api' diff --git a/frontend/src/components/Welcome.tsx b/frontend/src/ui/Welcome.tsx similarity index 99% rename from frontend/src/components/Welcome.tsx rename to frontend/src/ui/Welcome.tsx index 1235182..f556380 100644 --- a/frontend/src/components/Welcome.tsx +++ b/frontend/src/ui/Welcome.tsx @@ -1,4 +1,4 @@ -import { Button } from '@/components/ui/button' +import { Button } from '@/ui/button' import { Link } from 'react-router-dom' const Welcome = () => { diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/ui/alert.tsx similarity index 100% rename from frontend/src/components/ui/alert.tsx rename to frontend/src/ui/alert.tsx diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/ui/badge.tsx similarity index 100% rename from frontend/src/components/ui/badge.tsx rename to frontend/src/ui/badge.tsx diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/ui/button.tsx similarity index 100% rename from frontend/src/components/ui/button.tsx rename to frontend/src/ui/button.tsx diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/ui/card.tsx similarity index 100% rename from frontend/src/components/ui/card.tsx rename to frontend/src/ui/card.tsx diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/ui/checkbox.tsx similarity index 100% rename from frontend/src/components/ui/checkbox.tsx rename to frontend/src/ui/checkbox.tsx diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/ui/input.tsx similarity index 100% rename from frontend/src/components/ui/input.tsx rename to frontend/src/ui/input.tsx From 9d90cf734d7bdedcb8df4ea9b17f93caa342ae64 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Tue, 10 Feb 2026 18:12:56 -0500 Subject: [PATCH 03/12] feat: stdb ui files on file --- frontend/src/App.tsx | 2 - frontend/src/ui/Navbar.tsx | 64 ------------- frontend/src/ui/StatusDashboard.tsx | 135 +++++++++++++++++++++++++++- frontend/src/ui/alert.tsx | 57 ------------ frontend/src/ui/badge.tsx | 35 -------- frontend/src/ui/card.tsx | 71 --------------- 6 files changed, 132 insertions(+), 232 deletions(-) delete mode 100644 frontend/src/ui/Navbar.tsx delete mode 100644 frontend/src/ui/alert.tsx delete mode 100644 frontend/src/ui/badge.tsx delete mode 100644 frontend/src/ui/card.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 403ecaf..ce764ae 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,4 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom' -import Navbar from '@/ui/Navbar' import ProtectedRoute from '@/ui/ProtectedRoute' import StatusDashboard from '@/ui/StatusDashboard' import Welcome from '@/ui/Welcome' @@ -24,7 +23,6 @@ function App() { return ( - } /> } /> diff --git a/frontend/src/ui/Navbar.tsx b/frontend/src/ui/Navbar.tsx deleted file mode 100644 index 838c6df..0000000 --- a/frontend/src/ui/Navbar.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Button } from '@/ui/button' -import { Link, useNavigate } from 'react-router-dom' -import { useAuth } from '@/features/auth/hooks/useAuth' - -export default function Navbar() { - const { isAuthenticated, user, logout } = useAuth() - const navigate = useNavigate() - - const handleLogout = async () => { - await logout() - navigate('/') - } - - return ( - - ) -} \ No newline at end of file diff --git a/frontend/src/ui/StatusDashboard.tsx b/frontend/src/ui/StatusDashboard.tsx index d0c5d71..65bba64 100644 --- a/frontend/src/ui/StatusDashboard.tsx +++ b/frontend/src/ui/StatusDashboard.tsx @@ -1,11 +1,140 @@ import { useState, useEffect, useCallback } from 'react' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/ui/card" -import { Badge } from "@/ui/badge" +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" import { Button } from "@/ui/button" -import { Alert, AlertDescription } from "@/ui/alert" import { CheckCircle, XCircle, Clock, Terminal } from "lucide-react" import { apiFetch } from '@/utils/api' +// ===== CARD COMPONENTS ===== +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +// ===== BADGE COMPONENTS ===== +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +// ===== ALERT COMPONENTS ===== +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + // types for our status checks interface StatusCheck { name: string diff --git a/frontend/src/ui/alert.tsx b/frontend/src/ui/alert.tsx deleted file mode 100644 index 44aab6c..0000000 --- a/frontend/src/ui/alert.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" - -const alertVariants = cva( - "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", - { - variants: { - variant: { - default: "bg-background text-foreground", - destructive: - "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -const Alert = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & VariantProps ->(({ className, variant, ...props }, ref) => ( -
-)) -Alert.displayName = "Alert" - -const AlertTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -AlertTitle.displayName = "AlertTitle" - -const AlertDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -AlertDescription.displayName = "AlertDescription" - -export { Alert, AlertTitle, AlertDescription } diff --git a/frontend/src/ui/badge.tsx b/frontend/src/ui/badge.tsx deleted file mode 100644 index 8ff71a5..0000000 --- a/frontend/src/ui/badge.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" - -const badgeVariants = cva( - "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ) -} - -// eslint-disable-next-line react-refresh/only-export-components -export { Badge, badgeVariants } diff --git a/frontend/src/ui/card.tsx b/frontend/src/ui/card.tsx deleted file mode 100644 index eb30494..0000000 --- a/frontend/src/ui/card.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as React from "react" - -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -Card.displayName = "Card" - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardHeader.displayName = "CardHeader" - -const CardTitle = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardTitle.displayName = "CardTitle" - -const CardDescription = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardDescription.displayName = "CardDescription" - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardContent.displayName = "CardContent" - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) -CardFooter.displayName = "CardFooter" - -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } From 36d4038a5b1e207f84d349d1e5beff048101d06b Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Tue, 10 Feb 2026 18:16:29 -0500 Subject: [PATCH 04/12] Fix: protected routes util --- frontend/src/App.tsx | 9 +- frontend/src/{ui => utils}/ProtectedRoute.tsx | 97 +++++++++++-------- 2 files changed, 59 insertions(+), 47 deletions(-) rename frontend/src/{ui => utils}/ProtectedRoute.tsx (60%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ce764ae..1a8b756 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom' -import ProtectedRoute from '@/ui/ProtectedRoute' +import { UserRole } from '@/features/auth/services/authApi' +import ProtectedRoute from '@/utils/ProtectedRoute' import StatusDashboard from '@/ui/StatusDashboard' import Welcome from '@/ui/Welcome' import { @@ -38,9 +39,9 @@ function App() { } /> {/* Role-Based Dashboards - Protected */} - } /> - } /> - } /> + } /> + } /> + } /> {/* Catch-all - must be last */} } /> diff --git a/frontend/src/ui/ProtectedRoute.tsx b/frontend/src/utils/ProtectedRoute.tsx similarity index 60% rename from frontend/src/ui/ProtectedRoute.tsx rename to frontend/src/utils/ProtectedRoute.tsx index 647b627..6d8fe48 100644 --- a/frontend/src/ui/ProtectedRoute.tsx +++ b/frontend/src/utils/ProtectedRoute.tsx @@ -1,43 +1,54 @@ -import { ReactNode } from 'react'; -import { Navigate } from 'react-router-dom'; -import { useAuth } from '@/features/auth/hooks/useAuth'; - -interface ProtectedRouteProps { - children: ReactNode; -} - -/** - * ProtectedRoute Component - * Protects dashboard routes by ensuring: - * 1. User is authenticated - * 2. User's email is verified - * - * If either condition fails, redirects to appropriate page. - */ -export default function ProtectedRoute({ children }: ProtectedRouteProps) { - const { user, isLoading } = useAuth(); - - // Still loading auth state - if (isLoading) { - return ( -
-
-

Loading...

-
-
- ); - } - - // Not authenticated - if (!user) { - return ; - } - - // Email not verified (for signup flow) - if (!user.isEmailVerified) { - return ; - } - - // All checks passed, render the protected component - return <>{children}; -} +import { ReactNode } from 'react'; +import { Navigate } from 'react-router-dom'; +import { useAuth } from '@/features/auth/hooks/useAuth'; +import { UserRole } from '@/features/auth/services/authApi'; + +interface ProtectedRouteProps { + children: ReactNode; + requiredRole?: UserRole; +} + +/** + * ProtectedRoute Component + * Protects dashboard routes by ensuring: + * 1. User is authenticated + * 2. User's email is verified + * 3. User has the required role (if specified) + * + * If any condition fails, redirects to appropriate page. + * + * @param children - The component to render if all checks pass + * @param requiredRole - Optional role requirement (admin, interviewer, candidate) + */ +export default function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) { + const { user, isLoading } = useAuth(); + + // Still loading auth state + if (isLoading) { + return ( +
+
+

Loading...

+
+
+ ); + } + + // Not authenticated + if (!user) { + return ; + } + + // Email not verified (for signup flow) + if (!user.isEmailVerified) { + return ; + } + + // Check role if required + if (requiredRole && user.role !== requiredRole) { + return ; + } + + // All checks passed, render the protected component + return <>{children}; +} From 64410abb29f3a9e5196ed178448505765fc9b1c8 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Tue, 10 Feb 2026 18:39:27 -0500 Subject: [PATCH 05/12] Feat: feature subfolders with placeholder index for barrel exports --- frontend/src/features/admin/components/index.ts | 1 + frontend/src/features/admin/hooks/index.ts | 1 + frontend/src/features/admin/services/index.ts | 1 + frontend/src/features/admin/types/index.ts | 1 + frontend/src/features/admin/utils/index.ts | 1 + frontend/src/features/candidate/components/index.ts | 1 + frontend/src/features/candidate/hooks/index.ts | 1 + frontend/src/features/candidate/services/index.ts | 1 + frontend/src/features/candidate/types/index.ts | 1 + frontend/src/features/candidate/utils/index.ts | 1 + frontend/src/features/dashboard/components/index.ts | 1 + frontend/src/features/dashboard/hooks/index.ts | 1 + frontend/src/features/dashboard/services/index.ts | 1 + frontend/src/features/dashboard/types/index.ts | 1 + frontend/src/features/dashboard/utils/index.ts | 1 + frontend/src/features/interviewer/components/index.ts | 1 + frontend/src/features/interviewer/hooks/index.ts | 1 + frontend/src/features/interviewer/services/index.ts | 1 + frontend/src/features/interviewer/types/index.ts | 1 + frontend/src/features/interviewer/utils/index.ts | 1 + 20 files changed, 20 insertions(+) create mode 100644 frontend/src/features/admin/components/index.ts create mode 100644 frontend/src/features/admin/hooks/index.ts create mode 100644 frontend/src/features/admin/services/index.ts create mode 100644 frontend/src/features/admin/types/index.ts create mode 100644 frontend/src/features/admin/utils/index.ts create mode 100644 frontend/src/features/candidate/components/index.ts create mode 100644 frontend/src/features/candidate/hooks/index.ts create mode 100644 frontend/src/features/candidate/services/index.ts create mode 100644 frontend/src/features/candidate/types/index.ts create mode 100644 frontend/src/features/candidate/utils/index.ts create mode 100644 frontend/src/features/dashboard/components/index.ts create mode 100644 frontend/src/features/dashboard/hooks/index.ts create mode 100644 frontend/src/features/dashboard/services/index.ts create mode 100644 frontend/src/features/dashboard/types/index.ts create mode 100644 frontend/src/features/dashboard/utils/index.ts create mode 100644 frontend/src/features/interviewer/components/index.ts create mode 100644 frontend/src/features/interviewer/hooks/index.ts create mode 100644 frontend/src/features/interviewer/services/index.ts create mode 100644 frontend/src/features/interviewer/types/index.ts create mode 100644 frontend/src/features/interviewer/utils/index.ts diff --git a/frontend/src/features/admin/components/index.ts b/frontend/src/features/admin/components/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/admin/components/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/admin/hooks/index.ts b/frontend/src/features/admin/hooks/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/admin/hooks/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/admin/services/index.ts b/frontend/src/features/admin/services/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/admin/services/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/admin/types/index.ts b/frontend/src/features/admin/types/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/admin/types/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/admin/utils/index.ts b/frontend/src/features/admin/utils/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/admin/utils/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/candidate/components/index.ts b/frontend/src/features/candidate/components/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/candidate/components/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/candidate/hooks/index.ts b/frontend/src/features/candidate/hooks/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/candidate/hooks/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/candidate/services/index.ts b/frontend/src/features/candidate/services/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/candidate/services/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/candidate/types/index.ts b/frontend/src/features/candidate/types/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/candidate/types/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/candidate/utils/index.ts b/frontend/src/features/candidate/utils/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/candidate/utils/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/dashboard/components/index.ts b/frontend/src/features/dashboard/components/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/dashboard/components/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/dashboard/hooks/index.ts b/frontend/src/features/dashboard/hooks/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/dashboard/hooks/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/dashboard/services/index.ts b/frontend/src/features/dashboard/services/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/dashboard/services/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/dashboard/types/index.ts b/frontend/src/features/dashboard/types/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/dashboard/types/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/dashboard/utils/index.ts b/frontend/src/features/dashboard/utils/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/dashboard/utils/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/interviewer/components/index.ts b/frontend/src/features/interviewer/components/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/interviewer/components/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/interviewer/hooks/index.ts b/frontend/src/features/interviewer/hooks/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/interviewer/hooks/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/interviewer/services/index.ts b/frontend/src/features/interviewer/services/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/interviewer/services/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/interviewer/types/index.ts b/frontend/src/features/interviewer/types/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/interviewer/types/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file diff --git a/frontend/src/features/interviewer/utils/index.ts b/frontend/src/features/interviewer/utils/index.ts new file mode 100644 index 0000000..ab0c014 --- /dev/null +++ b/frontend/src/features/interviewer/utils/index.ts @@ -0,0 +1 @@ +// \ No newline at end of file From 630c8b05541c8148f67c76d7cac282a5dcc59621 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Wed, 11 Feb 2026 12:13:02 -0500 Subject: [PATCH 06/12] feat: Calcard into dashboard --- frontend/src/features/admin/AdminDashboard.tsx | 10 ++++------ frontend/src/ui/CalendarCard.tsx | 14 -------------- 2 files changed, 4 insertions(+), 20 deletions(-) delete mode 100644 frontend/src/ui/CalendarCard.tsx diff --git a/frontend/src/features/admin/AdminDashboard.tsx b/frontend/src/features/admin/AdminDashboard.tsx index 0158890..6971e51 100644 --- a/frontend/src/features/admin/AdminDashboard.tsx +++ b/frontend/src/features/admin/AdminDashboard.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { Link } from "react-router-dom"; import { CalendarDays, LayoutDashboard, User, Plus, Users, Settings } from "lucide-react"; -import CalendarCard from "@/ui/CalendarCard"; export default function InterviewerSchedule() { const [activeTab, setActiveTab] = useState("This week"); @@ -203,11 +202,10 @@ export default function InterviewerSchedule() {

Your calendar at a glance

{calendarStats.map((stat, index) => ( - +
+

{stat.title}

+

{stat.count}

+
))}
diff --git a/frontend/src/ui/CalendarCard.tsx b/frontend/src/ui/CalendarCard.tsx deleted file mode 100644 index 83777c0..0000000 --- a/frontend/src/ui/CalendarCard.tsx +++ /dev/null @@ -1,14 +0,0 @@ -interface CalendarCardProps { - title: string; - count: number; - className?: string; -} - -export default function CalendarCard({ title, count, className = "" }: CalendarCardProps) { - return ( -
-

{title}

-

{count}

-
- ); -} From 5f91ce19dc77bcd18dff03399d19074aa7777f2e Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Wed, 11 Feb 2026 12:22:37 -0500 Subject: [PATCH 07/12] Fix: checkbox -> availability --- .../interviewer/InterviewerAvailability.tsx | 301 ++++++------------ frontend/src/ui/checkbox.tsx | 23 -- 2 files changed, 89 insertions(+), 235 deletions(-) delete mode 100644 frontend/src/ui/checkbox.tsx diff --git a/frontend/src/features/interviewer/InterviewerAvailability.tsx b/frontend/src/features/interviewer/InterviewerAvailability.tsx index cdb70b6..2f7eac3 100644 --- a/frontend/src/features/interviewer/InterviewerAvailability.tsx +++ b/frontend/src/features/interviewer/InterviewerAvailability.tsx @@ -1,49 +1,38 @@ +import * as React from "react"; import { useState } from "react"; import { Link } from "react-router-dom"; -import { ArrowLeft } from "lucide-react"; -import { Checkbox } from "@/ui/checkbox"; +import { ArrowLeft, Check } from "lucide-react"; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; interface AvailabilityData { - [dateKey: string]: string[]; // dateKey format: "YYYY-MM-DD", value: array of time slots + [dateKey: string]: string[]; } +const Checkbox = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; +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 InterviewerAvailability() { const [selectedDate, setSelectedDate] = useState(null); const [availability, setAvailability] = useState({}); const [currentMonth, setCurrentMonth] = useState(new Date().getMonth()); const [currentYear, setCurrentYear] = useState(new Date().getFullYear()); - // Time slots from 8am to 11pm - const timeSlots = [ - "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", - ]; - - // Get days in month - const getDaysInMonth = (month: number, year: number) => { - return new Date(year, month + 1, 0).getDate(); - }; - - // Get first day of month (0 = Sunday, 1 = Monday, etc.) - const getFirstDayOfMonth = (month: number, year: number) => { - return new Date(year, month, 1).getDay(); - }; - - // Format date as YYYY-MM-DD const formatDateKey = (date: Date): string => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); @@ -51,225 +40,118 @@ export default function InterviewerAvailability() { return `${year}-${month}-${day}`; }; - // Get month name - const getMonthName = (month: number) => { - const months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - return months[month]; - }; + const getDaysInMonth = (month: number, year: number) => new Date(year, month + 1, 0).getDate(); + const getFirstDayOfMonth = (month: number, year: number) => new Date(year, month, 1).getDay(); - // Handle date selection - const handleDateClick = (day: number) => { - const date = new Date(currentYear, currentMonth, day); - setSelectedDate(date); - }; + const handleDateClick = (day: number) => setSelectedDate(new Date(currentYear, currentMonth, day)); - // Handle time slot toggle const handleTimeSlotToggle = (timeSlot: string, checked: boolean) => { if (!selectedDate) return; - const dateKey = formatDateKey(selectedDate); const currentTimes = availability[dateKey] || []; - - if (checked) { - setAvailability({ - ...availability, - [dateKey]: [...currentTimes, timeSlot], - }); - } else { - setAvailability({ - ...availability, - [dateKey]: currentTimes.filter((time) => time !== timeSlot), - }); - } + setAvailability({ + ...availability, + [dateKey]: checked ? [...currentTimes, timeSlot] : currentTimes.filter((time) => time !== timeSlot), + }); }; - // Check if time slot is selected for current date const isTimeSlotSelected = (timeSlot: string): boolean => { if (!selectedDate) return false; - const dateKey = formatDateKey(selectedDate); - return availability[dateKey]?.includes(timeSlot) || false; + return availability[formatDateKey(selectedDate)]?.includes(timeSlot) || false; }; - // Check if date has availability const dateHasAvailability = (day: number): boolean => { - const date = new Date(currentYear, currentMonth, day); - const dateKey = formatDateKey(date); + const dateKey = formatDateKey(new Date(currentYear, currentMonth, day)); return availability[dateKey]?.length > 0 || false; }; - // Handle submit - const handleSubmit = async () => { - // TODO: Connect to backend API - console.log("Submitting availability:", availability); - - // Example API call structure: - // const response = await apiFetch('/availability', { - // method: 'POST', - // headers: { 'Content-Type': 'application/json' }, - // body: JSON.stringify(availability) - // }); - - alert("Availability submitted successfully!"); + const isDateSelected = (day: number): boolean => + selectedDate?.getDate() === day && selectedDate?.getMonth() === currentMonth && selectedDate?.getFullYear() === currentYear; + + const handleMonthChange = (direction: number) => { + let newMonth = currentMonth + direction; + let newYear = currentYear; + if (newMonth < 0) { + newMonth = 11; + newYear--; + } else if (newMonth > 11) { + newMonth = 0; + newYear++; + } + setCurrentMonth(newMonth); + setCurrentYear(newYear); + setSelectedDate(null); }; - // Generate calendar days - const daysInMonth = getDaysInMonth(currentMonth, currentYear); - const firstDay = getFirstDayOfMonth(currentMonth, currentYear); - const days: (number | null)[] = []; - - // Add empty cells for days before the first day of the month - for (let i = 0; i < firstDay; i++) { - days.push(null); - } - - // Add all days of the month - for (let day = 1; day <= daysInMonth; day++) { - days.push(day); - } - - const isDateSelected = (day: number): boolean => { - if (!selectedDate) return false; - return ( - selectedDate.getDate() === day && - selectedDate.getMonth() === currentMonth && - selectedDate.getFullYear() === currentYear - ); + const generateCalendarDays = () => { + const days: (number | null)[] = []; + const firstDay = getFirstDayOfMonth(currentMonth, currentYear); + for (let i = 0; i < firstDay; i++) days.push(null); + for (let day = 1; day <= getDaysInMonth(currentMonth, currentYear); day++) days.push(day); + return days; }; - // Navigation functions - const goToPreviousMonth = () => { - if (currentMonth === 0) { - setCurrentMonth(11); - setCurrentYear(currentYear - 1); - } else { - setCurrentMonth(currentMonth - 1); - } - setSelectedDate(null); + const handleSubmit = async () => { + console.log("Submitting availability:", availability); + alert("Availability submitted successfully!"); }; - const goToNextMonth = () => { - if (currentMonth === 11) { - setCurrentMonth(0); - setCurrentYear(currentYear + 1); - } else { - setCurrentMonth(currentMonth + 1); - } - setSelectedDate(null); - }; + const days = generateCalendarDays(); return (
- {/* Back Button */} - + Back to Dashboard - {/* Instructions */}
-

- Please select all the times you're available to meet for an interview. We'll use this information to schedule your interview. -

-

- • Select a day, fill in available times, and then repeat process for all available days. -

+

Please select all the times you're available to meet for an interview. We'll use this information to schedule your interview.

+

• Select a day, fill in available times, and then repeat process for all available days.

- {/* Calendar */}
- -

- {getMonthName(currentMonth)} {currentYear} -

- + +

{MONTHS[currentMonth]} {currentYear}

+
- {/* Calendar Grid */}
- {/* Day headers */} - {["S", "M", "T", "W", "T", "F", "S"].map((day, index) => ( -
+ {["S", "M", "T", "W", "T", "F", "S"].map((day, i) => ( +
{day}
))} - - {/* Calendar days */} - {days.map((day, index) => { - if (day === null) { - return
; - } - - const isSelected = isDateSelected(day); - const hasAvailability = dateHasAvailability(day); - - return ( - - ); - })} + {days.map((day, i) => ( +
+ {day === null ? null : ( + + )} +
+ ))}
- {/* Time Slot Selector */} {selectedDate && (
- +
- {timeSlots.map((timeSlot) => ( -
)} - {/* Submit Button */} -
diff --git a/frontend/src/ui/checkbox.tsx b/frontend/src/ui/checkbox.tsx deleted file mode 100644 index bfc4d3a..0000000 --- a/frontend/src/ui/checkbox.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from "react" -import * as CheckboxPrimitive from "@radix-ui/react-checkbox" -import { Check } from "lucide-react" - -const Checkbox = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - - -)) -Checkbox.displayName = CheckboxPrimitive.Root.displayName - -export { Checkbox } From b80c915207a63deeeb8b8eace9ae8ce3066d3c86 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Wed, 11 Feb 2026 12:32:20 -0500 Subject: [PATCH 08/12] Fix: input comp -> joinateam & removed from 2fa --- .../auth/components/TwoFactorAuth.tsx | 29 +++++++------------ frontend/src/features/candidate/JoinATeam.tsx | 17 ++++++++++- frontend/src/ui/input.tsx | 18 ------------ 3 files changed, 26 insertions(+), 38 deletions(-) delete mode 100644 frontend/src/ui/input.tsx diff --git a/frontend/src/features/auth/components/TwoFactorAuth.tsx b/frontend/src/features/auth/components/TwoFactorAuth.tsx index 404aa7e..8915f72 100644 --- a/frontend/src/features/auth/components/TwoFactorAuth.tsx +++ b/frontend/src/features/auth/components/TwoFactorAuth.tsx @@ -1,7 +1,6 @@ import { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; import { Button } from "@/ui/button"; -import { Input } from "@/ui/input"; import { ArrowLeft } from "lucide-react"; import { verifyResetCode, verifyEmail, setTokens, getCurrentUser } from "../services/authApi"; import { useAuth } from "../hooks/useAuth"; @@ -187,24 +186,16 @@ export default function TwoFactorAuth() { /> {/* 2FA Code input */} -
- - handleCodeChange(e.target.value)} - placeholder="Enter 6-digit code" - maxLength={6} - required - disabled={success} - /> -
- + handleCodeChange(e.target.value)} + placeholder="Enter 6-digit code" + required + disabled={success} + /> {/* Back link */}
>(( + { className, type, ...props }, + ref +) => { + return ( + + ) +}); +Input.displayName = "Input"; export default function JoinATeam() { const [email, setEmail] = useState(""); diff --git a/frontend/src/ui/input.tsx b/frontend/src/ui/input.tsx deleted file mode 100644 index 957d1f5..0000000 --- a/frontend/src/ui/input.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from "react" - -const Input = React.forwardRef>(( - { className, type, ...props }, - ref -) => { - return ( - - ) -}) -Input.displayName = "Input" - -export { Input } From a6cbeeaa2c6017f413804301955e0976649fff15 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Wed, 11 Feb 2026 12:36:02 -0500 Subject: [PATCH 09/12] Fix: renamed interviewerAvailability to Availability since its a universal dashboard comp --- .../InterviewerAvailability.tsx => dashboard/Availability.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/src/features/{interviewer/InterviewerAvailability.tsx => dashboard/Availability.tsx} (100%) diff --git a/frontend/src/features/interviewer/InterviewerAvailability.tsx b/frontend/src/features/dashboard/Availability.tsx similarity index 100% rename from frontend/src/features/interviewer/InterviewerAvailability.tsx rename to frontend/src/features/dashboard/Availability.tsx From 79e1b98dc08f9d0984b0b7d4ad7dc8064761ea51 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Thu, 12 Feb 2026 10:49:54 -0500 Subject: [PATCH 10/12] Feat: major dashboard changes | Unified all dashboards into 1 --- frontend/src/App.tsx | 20 +- .../src/features/admin/AdminDashboard.tsx | 257 ----------- frontend/src/features/admin/AdminSettings.tsx | 414 ------------------ .../admin/components/AdminSettings.tsx | 350 +++++++++++++++ .../src/features/admin/components/index.ts | 2 +- .../features/candidate/CandidateDashboard.tsx | 90 ---- frontend/src/features/dashboard/Dashboard.tsx | 118 +++++ .../dashboard/InterviewerDashboard.tsx | 90 ---- .../{ => components}/Availability.tsx | 46 +- .../dashboard/components/CalendarStats.tsx | 25 ++ .../features/dashboard/components/Header.tsx | 15 + .../components/InterviewScheduleSection.tsx | 98 +++++ .../features/dashboard/components/Sidebar.tsx | 79 ++++ .../features/dashboard/components/index.ts | 6 +- 14 files changed, 718 insertions(+), 892 deletions(-) delete mode 100644 frontend/src/features/admin/AdminDashboard.tsx delete mode 100644 frontend/src/features/admin/AdminSettings.tsx create mode 100644 frontend/src/features/admin/components/AdminSettings.tsx delete mode 100644 frontend/src/features/candidate/CandidateDashboard.tsx create mode 100644 frontend/src/features/dashboard/Dashboard.tsx delete mode 100644 frontend/src/features/dashboard/InterviewerDashboard.tsx rename frontend/src/features/dashboard/{ => components}/Availability.tsx (80%) create mode 100644 frontend/src/features/dashboard/components/CalendarStats.tsx create mode 100644 frontend/src/features/dashboard/components/Header.tsx create mode 100644 frontend/src/features/dashboard/components/InterviewScheduleSection.tsx create mode 100644 frontend/src/features/dashboard/components/Sidebar.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1a8b756..bfa7eae 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,4 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom' -import { UserRole } from '@/features/auth/services/authApi' -import ProtectedRoute from '@/utils/ProtectedRoute' import StatusDashboard from '@/ui/StatusDashboard' import Welcome from '@/ui/Welcome' import { @@ -11,14 +9,11 @@ import { NewPassword, ForgotPassword, } from '@/features/auth/components' -import AdminDashboard2 from '@/features/admin/AdminDashboard' +import Dashboard from '@/features/dashboard/Dashboard' import JoinATeam from '@/features/candidate/JoinATeam' import CreateOrJoinTeam from '@/features/auth/CreateOrJoinTeam' -import InterviewerAvailability from '@/features/interviewer/InterviewerAvailability' +import Availability from '@/features/dashboard/components/Availability' import { AuthProvider } from '@/provider/AuthProvider' -import InterviewerDashboard from '@/features/dashboard/InterviewerDashboard' -import CandidateDashboard from '@/features/candidate/CandidateDashboard' -import AdminSettings from './features/admin/AdminSettings' function App() { return ( @@ -35,13 +30,10 @@ function App() { } /> } /> } /> - } /> - } /> - - {/* Role-Based Dashboards - Protected */} - } /> - } /> - } /> + } /> + + {/* Dashboard */} + } /> {/* Catch-all - must be last */} } /> diff --git a/frontend/src/features/admin/AdminDashboard.tsx b/frontend/src/features/admin/AdminDashboard.tsx deleted file mode 100644 index 6971e51..0000000 --- a/frontend/src/features/admin/AdminDashboard.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import { useState } from "react"; -import { Link } from "react-router-dom"; -import { CalendarDays, LayoutDashboard, User, Plus, Users, Settings } from "lucide-react"; - -export default function InterviewerSchedule() { - const [activeTab, setActiveTab] = useState("This week"); - - const tabs = ["Today", "Tomorrow", "This week", "Next week"]; - 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 } - ]; - - // Function to filter interviews based on active tab - const getFilteredInterviews = (interviewsList: typeof interviews) => { - - 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 ( - - // left side bar -
- - - {/* Main Content Area */} -
- - {/* Header / Top Right */} -
-
- -
- Name - Interviewer -
-
-
- - {/* Calendar at a glance */} -
-

Welcome back, Name!

-

Your calendar at a glance

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

{stat.title}

-

{stat.count}

-
- ))} -
-
- - {/* Interview Schedule Section */} -
-

Interview Schedule

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

Interviews

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

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

-

{interview.date}

-
-
- ))} -
-
-
-
-
- ); -} diff --git a/frontend/src/features/admin/AdminSettings.tsx b/frontend/src/features/admin/AdminSettings.tsx deleted file mode 100644 index 25063ef..0000000 --- a/frontend/src/features/admin/AdminSettings.tsx +++ /dev/null @@ -1,414 +0,0 @@ -import { useState } from "react"; -import { Link } from "react-router-dom"; -import { CalendarDays, LayoutDashboard, Users, Settings, Plus, User, X } from "lucide-react"; - -interface Moderator { - id: string; - name: string; - email: string; - isMain?: boolean; -} - -interface Role { - id: string; - name: string; -} - -interface Department { - id: string; - name: string; -} - -export default function AdminSettings() { - const [moderators, setModerators] = useState([ - { id: "1", name: "Jason Van-Humbeek", email: "jason@example.com", isMain: true }, - { id: "2", name: "Vincenzo Milano", email: "vincenzo@example.com" }, - ]); - - const [roles, setRoles] = useState([ - { id: "1", name: "Software Engineer" }, - { id: "2", name: "Academic Coordinator" }, - ]); - - const [departments, setDepartments] = useState([ - { id: "1", name: "Eng" }, - { id: "2", name: "Academics" }, - ]); - - const [interviewersPerInterviewee, setInterviewersPerInterviewee] = useState(2); - const [maxInterviewsPerDay, setMaxInterviewsPerDay] = useState(5); - - // Input visibility states - const [showRoleInput, setShowRoleInput] = useState(false); - const [newRoleName, setNewRoleName] = useState(""); - const [showDeptInput, setShowDeptInput] = useState(false); - const [newDeptName, setNewDeptName] = useState(""); - const [showModeratorInput, setShowModeratorInput] = useState(false); - const [newModeratorEmail, setNewModeratorEmail] = useState(""); - - const removeModerator = (id: string) => { - setModerators(moderators.filter((mod) => mod.id !== id)); - }; - - const removeRole = (id: string) => { - setRoles(roles.filter((role) => role.id !== id)); - }; - - const removeDepartment = (id: string) => { - setDepartments(departments.filter((dept) => dept.id !== id)); - }; - - const addRole = () => { - if (newRoleName.trim()) { - setRoles([...roles, { id: Date.now().toString(), name: newRoleName.trim() }]); - setNewRoleName(""); - setShowRoleInput(false); - } - }; - - const addDepartment = () => { - if (newDeptName.trim()) { - setDepartments([...departments, { id: Date.now().toString(), name: newDeptName.trim() }]); - setNewDeptName(""); - setShowDeptInput(false); - } - }; - - const inviteModerator = () => { - if (newModeratorEmail.trim()) { - // This would typically make an API call to send an invite email - console.log("Inviting moderator:", newModeratorEmail); - // For demo purposes, add them to the list - setModerators([ - ...moderators, - { - id: Date.now().toString(), - name: "Pending", - email: newModeratorEmail.trim(), - }, - ]); - setNewModeratorEmail(""); - setShowModeratorInput(false); - alert(`Invite sent to ${newModeratorEmail}`); - } - }; - - const handleSave = () => { - // This would typically make an API call to save all settings - console.log("Saving settings:", { - moderators, - roles, - departments, - interviewersPerInterviewee, - maxInterviewsPerDay, - }); - alert("Settings saved successfully!"); - }; - - return ( -
- {/* Left Sidebar */} - - - {/* Main Content Area */} -
- {/* Header / Top Right */} -
-

Admin Settings

-
- -
- Name - Admin -
-
-
- - {/* Settings Content */} -
- {/* Moderators Section */} -
-
-

Team Moderators

- -
- - {showModeratorInput && ( -
- setNewModeratorEmail(e.target.value)} - placeholder="Enter moderator email" - className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" - onKeyPress={(e) => e.key === "Enter" && inviteModerator()} - /> - - -
- )} - -
- {moderators.map((moderator) => ( -
- - {moderator.name} - {moderator.isMain && ( - (main) - )} - - {!moderator.isMain && ( - - )} -
- ))} -
-
- - {/* Auto Scheduling Preferences Section */} -
-

- Auto Scheduling Preferences -

-
-
- - setInterviewersPerInterviewee(parseInt(e.target.value) || 0)} - className="w-full md:w-48 px-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" - placeholder="e.g., 2" - /> -

- Set how many interviewers should meet with each candidate -

-
-
- - setMaxInterviewsPerDay(parseInt(e.target.value) || 0)} - className="w-full md:w-48 px-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" - placeholder="e.g., 5" - /> -

- Limit daily interviews to prevent interviewer burnout -

-
-
-
- - {/* Two Column Layout for Roles and Departments */} -
- {/* Manage Roles Section */} -
-
-

Interview Roles

- -
-

Roles available for interviewees

- - {showRoleInput && ( -
- setNewRoleName(e.target.value)} - placeholder="Enter role name" - className="flex-1 px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" - onKeyPress={(e) => e.key === "Enter" && addRole()} - /> - - -
- )} - -
- {roles.map((role) => ( -
- {role.name} - -
- ))} -
-
- - {/* Manage Departments Section */} -
-
-

Departments

- -
-

Departments for interviewers

- - {showDeptInput && ( -
- setNewDeptName(e.target.value)} - placeholder="Enter department name" - className="flex-1 px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" - onKeyPress={(e) => e.key === "Enter" && addDepartment()} - /> - - -
- )} - -
- {departments.map((dept) => ( -
- {dept.name} - -
- ))} -
-
-
- - {/* Save Button */} -
- -
-
-
-
- ); -} \ No newline at end of file diff --git a/frontend/src/features/admin/components/AdminSettings.tsx b/frontend/src/features/admin/components/AdminSettings.tsx new file mode 100644 index 0000000..2a357a0 --- /dev/null +++ b/frontend/src/features/admin/components/AdminSettings.tsx @@ -0,0 +1,350 @@ +import { useState } from "react"; +import { X } from "lucide-react"; + +interface Moderator { + id: string; + name: string; + email: string; + isMain?: boolean; +} + +interface Role { + id: string; + name: string; +} + +interface Department { + id: string; + name: string; +} + +export default function AdminSettings() { + const [moderators, setModerators] = useState([ + { id: "1", name: "Jason Van-Humbeek", email: "jason@example.com", isMain: true }, + { id: "2", name: "Vincenzo Milano", email: "vincenzo@example.com" }, + ]); + + const [roles, setRoles] = useState([ + { id: "1", name: "Software Engineer" }, + { id: "2", name: "Academic Coordinator" }, + ]); + + const [departments, setDepartments] = useState([ + { id: "1", name: "Eng" }, + { id: "2", name: "Academics" }, + ]); + + const [interviewersPerInterviewee, setInterviewersPerInterviewee] = useState(2); + const [maxInterviewsPerDay, setMaxInterviewsPerDay] = useState(5); + + // Input visibility states + const [showRoleInput, setShowRoleInput] = useState(false); + const [newRoleName, setNewRoleName] = useState(""); + const [showDeptInput, setShowDeptInput] = useState(false); + const [newDeptName, setNewDeptName] = useState(""); + const [showModeratorInput, setShowModeratorInput] = useState(false); + const [newModeratorEmail, setNewModeratorEmail] = useState(""); + + const removeModerator = (id: string) => { + setModerators(moderators.filter((mod) => mod.id !== id)); + }; + + const removeRole = (id: string) => { + setRoles(roles.filter((role) => role.id !== id)); + }; + + const removeDepartment = (id: string) => { + setDepartments(departments.filter((dept) => dept.id !== id)); + }; + + const addRole = () => { + if (newRoleName.trim()) { + setRoles([...roles, { id: Date.now().toString(), name: newRoleName.trim() }]); + setNewRoleName(""); + setShowRoleInput(false); + } + }; + + const addDepartment = () => { + if (newDeptName.trim()) { + setDepartments([...departments, { id: Date.now().toString(), name: newDeptName.trim() }]); + setNewDeptName(""); + setShowDeptInput(false); + } + }; + + const inviteModerator = () => { + if (newModeratorEmail.trim()) { + // This would typically make an API call to send an invite email + console.log("Inviting moderator:", newModeratorEmail); + // For demo purposes, add them to the list + setModerators([ + ...moderators, + { + id: Date.now().toString(), + name: "Pending", + email: newModeratorEmail.trim(), + }, + ]); + setNewModeratorEmail(""); + setShowModeratorInput(false); + alert(`Invite sent to ${newModeratorEmail}`); + } + }; + + const handleSave = () => { + // This would typically make an API call to save all settings + console.log("Saving settings:", { + moderators, + roles, + departments, + interviewersPerInterviewee, + maxInterviewsPerDay, + }); + alert("Settings saved successfully!"); + }; + + return ( +
+ {/* Header */} +

Admin Settings

+ + {/* Moderators Section */} +
+
+

Team Moderators

+ +
+ + {showModeratorInput && ( +
+ setNewModeratorEmail(e.target.value)} + placeholder="Enter moderator email" + className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" + onKeyPress={(e) => e.key === "Enter" && inviteModerator()} + /> + + +
+ )} + +
+ {moderators.map((moderator) => ( +
+ + {moderator.name} + {moderator.isMain && ( + (main) + )} + + {!moderator.isMain && ( + + )} +
+ ))} +
+
+ + {/* Auto Scheduling Preferences Section */} +
+

+ Auto Scheduling Preferences +

+
+
+ + setInterviewersPerInterviewee(parseInt(e.target.value) || 0)} + className="w-full md:w-48 px-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" + placeholder="e.g., 2" + /> +

+ Set how many interviewers should meet with each candidate +

+
+
+ + setMaxInterviewsPerDay(parseInt(e.target.value) || 0)} + className="w-full md:w-48 px-3 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" + placeholder="e.g., 5" + /> +

+ Limit daily interviews to prevent interviewer burnout +

+
+
+
+ + {/* Two Column Layout for Roles and Departments */} +
+ {/* Manage Roles Section */} +
+
+

Interview Roles

+ +
+

Roles available for interviewees

+ + {showRoleInput && ( +
+ setNewRoleName(e.target.value)} + placeholder="Enter role name" + className="flex-1 px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" + onKeyPress={(e) => e.key === "Enter" && addRole()} + /> + + +
+ )} + +
+ {roles.map((role) => ( +
+ {role.name} + +
+ ))} +
+
+ + {/* Manage Departments Section */} +
+
+

Departments

+ +
+

Departments for interviewers

+ + {showDeptInput && ( +
+ setNewDeptName(e.target.value)} + placeholder="Enter department name" + className="flex-1 px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500" + onKeyPress={(e) => e.key === "Enter" && addDepartment()} + /> + + +
+ )} + +
+ {departments.map((dept) => ( +
+ {dept.name} + +
+ ))} +
+
+
+ + {/* Save Button */} +
+ +
+
+ ); +} diff --git a/frontend/src/features/admin/components/index.ts b/frontend/src/features/admin/components/index.ts index ab0c014..f30aa06 100644 --- a/frontend/src/features/admin/components/index.ts +++ b/frontend/src/features/admin/components/index.ts @@ -1 +1 @@ -// \ No newline at end of file +export { default as AdminSettings } from "./AdminSettings"; \ No newline at end of file diff --git a/frontend/src/features/candidate/CandidateDashboard.tsx b/frontend/src/features/candidate/CandidateDashboard.tsx deleted file mode 100644 index 4074508..0000000 --- a/frontend/src/features/candidate/CandidateDashboard.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useAuth } from "@/features/auth/hooks/useAuth"; - -export default function CandidateDashboard() { - const { user } = useAuth(); - - return ( -
- {/* Header */} -
-
-

Candidate Dashboard

-
-
- - {/* Main Content */} -
-
- {/* Welcome Card */} -
-
-

- Welcome, {user?.name}! 👋 -

-

Role: Candidate

-

Email: {user?.email}

-
-
- - {/* Candidate Features Grid */} -
- {/* My Interviews */} -
-
-
-
- - - -
-
-
-
- My Interviews -
-
- View upcoming interviews -
-
-
-
-
-
- - {/* Schedule Request */} -
-
-
-
- - - -
-
-
-
- Schedule Interview -
-
- Request new interview -
-
-
-
-
-
-
- - {/* Info Box */} -
-

- 🎉 Authentication Working! You're logged in as a Candidate. - This dashboard is a placeholder - actual candidate features will be implemented next. -

-
-
-
-
- ); -} - diff --git a/frontend/src/features/dashboard/Dashboard.tsx b/frontend/src/features/dashboard/Dashboard.tsx new file mode 100644 index 0000000..b72ddb5 --- /dev/null +++ b/frontend/src/features/dashboard/Dashboard.tsx @@ -0,0 +1,118 @@ +import { useState } from "react"; +import { + DashboardSidebar, + DashboardHeader, + CalendarStats, + InterviewScheduleSection, + Availability, +} from "./components"; +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 ( +
+ + + {/* Main Content Area */} +
+ + {activePage === "dashboard" ? ( + <> + + + + ) : activePage === "admin-settings" ? ( + + ) : activePage === "availability" ? ( + + ) : null} +
+
+ ); +} diff --git a/frontend/src/features/dashboard/InterviewerDashboard.tsx b/frontend/src/features/dashboard/InterviewerDashboard.tsx deleted file mode 100644 index 0e2a1dc..0000000 --- a/frontend/src/features/dashboard/InterviewerDashboard.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useAuth } from "@/features/auth/hooks/useAuth"; - -export default function InterviewerDashboard() { - const { user } = useAuth(); - - return ( -
- {/* Header */} -
-
-

Interviewer Dashboard

-
-
- - {/* Main Content */} -
-
- {/* Welcome Card */} -
-
-

- Welcome back, {user?.name}! 👋 -

-

Role: Interviewer

-

Email: {user?.email}

-
-
- - {/* Interviewer Features Grid */} -
- {/* Availability */} -
-
-
-
- - - -
-
-
-
- My Availability -
-
- Set your schedule -
-
-
-
-
-
- - {/* Scheduled Interviews */} -
-
-
-
- - - -
-
-
-
- My Interviews -
-
- View scheduled interviews -
-
-
-
-
-
-
- - {/* Info Box */} -
-

- 🎉 Authentication Working! You're logged in as an Interviewer. - This dashboard is a placeholder - actual interviewer features will be implemented next. -

-
-
-
-
- ); -} - diff --git a/frontend/src/features/dashboard/Availability.tsx b/frontend/src/features/dashboard/components/Availability.tsx similarity index 80% rename from frontend/src/features/dashboard/Availability.tsx rename to frontend/src/features/dashboard/components/Availability.tsx index 2f7eac3..f18dada 100644 --- a/frontend/src/features/dashboard/Availability.tsx +++ b/frontend/src/features/dashboard/components/Availability.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import { useState } from "react"; -import { Link } from "react-router-dom"; -import { ArrowLeft, Check } from "lucide-react"; +import { Check } from "lucide-react"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; interface AvailabilityData { @@ -27,7 +26,7 @@ Checkbox.displayName = CheckboxPrimitive.Root.displayName; const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 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 InterviewerAvailability() { +export default function Availability() { const [selectedDate, setSelectedDate] = useState(null); const [availability, setAvailability] = useState({}); const [currentMonth, setCurrentMonth] = useState(new Date().getMonth()); @@ -99,13 +98,10 @@ export default function InterviewerAvailability() { const days = generateCalendarDays(); return ( -
-
- - - Back to Dashboard - +
+

Your Availability

+

Please select all the times you're available to meet for an interview. We'll use this information to schedule your interview.

• Select a day, fill in available times, and then repeat process for all available days.

@@ -144,25 +140,25 @@ export default function InterviewerAvailability() { ))}
+
- {selectedDate && ( -
- -
- {TIME_SLOTS.map((timeSlot) => ( - - ))} -
+ {selectedDate && ( +
+ +
+ {TIME_SLOTS.map((timeSlot) => ( + + ))}
- )} +
+ )} - -
+
); } diff --git a/frontend/src/features/dashboard/components/CalendarStats.tsx b/frontend/src/features/dashboard/components/CalendarStats.tsx new file mode 100644 index 0000000..ae007d2 --- /dev/null +++ b/frontend/src/features/dashboard/components/CalendarStats.tsx @@ -0,0 +1,25 @@ +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}

+
+ ))} +
+
+ ); +} diff --git a/frontend/src/features/dashboard/components/Header.tsx b/frontend/src/features/dashboard/components/Header.tsx new file mode 100644 index 0000000..51ba740 --- /dev/null +++ b/frontend/src/features/dashboard/components/Header.tsx @@ -0,0 +1,15 @@ +import { User } from "lucide-react"; + +export default function DashboardHeader() { + return ( +
+
+ +
+ Name + Interviewer +
+
+
+ ); +} diff --git a/frontend/src/features/dashboard/components/InterviewScheduleSection.tsx b/frontend/src/features/dashboard/components/InterviewScheduleSection.tsx new file mode 100644 index 0000000..fbf4f98 --- /dev/null +++ b/frontend/src/features/dashboard/components/InterviewScheduleSection.tsx @@ -0,0 +1,98 @@ +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}

+
+
+ ))} +
+
+
+ ); +} diff --git a/frontend/src/features/dashboard/components/Sidebar.tsx b/frontend/src/features/dashboard/components/Sidebar.tsx new file mode 100644 index 0000000..fa83761 --- /dev/null +++ b/frontend/src/features/dashboard/components/Sidebar.tsx @@ -0,0 +1,79 @@ +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 ( + + ); +} diff --git a/frontend/src/features/dashboard/components/index.ts b/frontend/src/features/dashboard/components/index.ts index ab0c014..ce86211 100644 --- a/frontend/src/features/dashboard/components/index.ts +++ b/frontend/src/features/dashboard/components/index.ts @@ -1 +1,5 @@ -// \ No newline at end of file +export { default as DashboardSidebar } from "./Sidebar"; +export { default as DashboardHeader } from "./Header"; +export { default as CalendarStats } from "./CalendarStats"; +export { default as InterviewScheduleSection } from "./InterviewScheduleSection"; +export { default as Availability } from "./Availability"; \ No newline at end of file From d6933f716e42dda10d1fed3063a06b71bb7f8089 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Thu, 12 Feb 2026 10:50:44 -0500 Subject: [PATCH 11/12] Fix: join team to comps --- frontend/src/features/candidate/{ => components}/JoinATeam.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/src/features/candidate/{ => components}/JoinATeam.tsx (100%) diff --git a/frontend/src/features/candidate/JoinATeam.tsx b/frontend/src/features/candidate/components/JoinATeam.tsx similarity index 100% rename from frontend/src/features/candidate/JoinATeam.tsx rename to frontend/src/features/candidate/components/JoinATeam.tsx From 5fbb421a884c02262f2e9d6f1bedfedb15b72679 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Thu, 12 Feb 2026 10:53:37 -0500 Subject: [PATCH 12/12] Fix: barrel export & app updated --- frontend/src/App.tsx | 2 +- frontend/src/features/candidate/components/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bfa7eae..cd53745 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -10,7 +10,7 @@ import { ForgotPassword, } from '@/features/auth/components' import Dashboard from '@/features/dashboard/Dashboard' -import JoinATeam from '@/features/candidate/JoinATeam' +import JoinATeam from '@/features/candidate/components/JoinATeam' import CreateOrJoinTeam from '@/features/auth/CreateOrJoinTeam' import Availability from '@/features/dashboard/components/Availability' import { AuthProvider } from '@/provider/AuthProvider' diff --git a/frontend/src/features/candidate/components/index.ts b/frontend/src/features/candidate/components/index.ts index ab0c014..677b843 100644 --- a/frontend/src/features/candidate/components/index.ts +++ b/frontend/src/features/candidate/components/index.ts @@ -1 +1 @@ -// \ No newline at end of file +export { default as JoinATeam } from "./JoinATeam"; \ No newline at end of file