InterviewAI / src /app /interview /page.tsx
viraj-421's picture
Initial commit: AI-Powered Mock Interview Platform with Auth, Certificates, and Timer
a0da743
Raw History Blame Contribute Delete
13.8 kB
'use client';
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useRouter } from 'next/navigation';
import { useInterview } from '@/context/InterviewContext';
import { INTERVIEW_ROLES } from '@/lib/roles';
import { InterviewRole } from '@/types/interview';
import {
ArrowRight,
Gauge,
Hash,
Sparkles,
ChevronRight,
Clock,
Timer,
} from 'lucide-react';
type Difficulty = 'beginner' | 'intermediate' | 'advanced';
const difficultyConfig: Record<Difficulty, { label: string; description: string; color: string; icon: string }> = {
beginner: {
label: 'Beginner',
description: 'Fundamentals & basic concepts',
color: 'var(--success)',
icon: '🌱',
},
intermediate: {
label: 'Intermediate',
description: 'Practical application & problem-solving',
color: 'var(--warning)',
icon: '⚡',
},
advanced: {
label: 'Advanced',
description: 'System design & deep expertise',
color: 'var(--danger)',
icon: '🔥',
},
};
const questionCounts = [5, 8, 10, 15];
const timeLimits = [
{ value: 0, label: 'No Limit', icon: '♾️' },
{ value: 15, label: '15 min', icon: '⏱️' },
{ value: 29, label: '29 min', icon: '🔥' },
{ value: 45, label: '45 min', icon: '⏰' },
];
export default function InterviewPage() {
const router = useRouter();
const { startSession } = useInterview();
const [selectedRole, setSelectedRole] = useState<InterviewRole | null>(null);
const [difficulty, setDifficulty] = useState<Difficulty>('intermediate');
const [questionCount, setQuestionCount] = useState(5);
const [timeLimit, setTimeLimit] = useState(29);
const [step, setStep] = useState<1 | 2>(1);
const handleStartInterview = () => {
if (!selectedRole) return;
startSession({
role: selectedRole,
difficulty,
questionCount,
timeLimit,
});
router.push('/interview/session');
};
return (
<div className="min-h-screen py-12 px-6">
<div className="max-w-6xl mx-auto">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-12"
>
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-4"
style={{ background: 'rgba(108, 92, 231, 0.1)', border: '1px solid rgba(108, 92, 231, 0.2)' }}
>
<Sparkles className="w-4 h-4" style={{ color: 'var(--primary-light)' }} />
<span className="text-sm font-medium" style={{ color: 'var(--primary-light)' }}>
Step {step} of 2
</span>
</div>
<h1 className="text-4xl font-bold mb-3">
{step === 1 ? (
<>Choose Your <span className="text-gradient">Interview Role</span></>
) : (
<>Configure Your <span className="text-gradient">Interview</span></>
)}
</h1>
<p style={{ color: 'var(--muted-light)' }}>
{step === 1
? 'Select the position you want to practice interviewing for'
: 'Set your difficulty level and number of questions'}
</p>
</motion.div>
<AnimatePresence mode="wait">
{step === 1 ? (
/* Step 1: Role Selection */
<motion.div
key="step1"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4"
>
{INTERVIEW_ROLES.map((role, i) => (
<motion.button
key={role.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }}
onClick={() => {
setSelectedRole(role);
setStep(2);
}}
className="group text-left p-5 rounded-2xl transition-all duration-300 cursor-pointer"
style={{
background: selectedRole?.id === role.id ? `${role.color}10` : 'var(--card)',
border: `1px solid ${selectedRole?.id === role.id ? role.color : 'var(--border)'}`,
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = role.color;
e.currentTarget.style.background = `${role.color}08`;
e.currentTarget.style.transform = 'translateY(-4px)';
e.currentTarget.style.boxShadow = `0 8px 30px ${role.color}20`;
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = selectedRole?.id === role.id ? role.color : 'var(--border)';
e.currentTarget.style.background = selectedRole?.id === role.id ? `${role.color}10` : 'var(--card)';
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = 'none';
}}
>
<div className="flex items-start justify-between mb-3">
<span className="text-3xl">{role.icon}</span>
<ChevronRight
className="w-5 h-5 opacity-0 group-hover:opacity-100 transition-all duration-300 group-hover:translate-x-1"
style={{ color: role.color }}
/>
</div>
<h3 className="font-semibold text-base mb-1">{role.title}</h3>
<p className="text-xs leading-relaxed mb-3" style={{ color: 'var(--muted-light)' }}>
{role.description}
</p>
<div className="flex flex-wrap gap-1.5">
{role.topics.slice(0, 3).map((topic) => (
<span
key={topic}
className="px-2 py-0.5 rounded-md text-xs"
style={{
background: `${role.color}10`,
color: role.color,
}}
>
{topic}
</span>
))}
{role.topics.length > 3 && (
<span className="px-2 py-0.5 rounded-md text-xs" style={{ color: 'var(--muted)' }}>
+{role.topics.length - 3}
</span>
)}
</div>
</motion.button>
))}
</motion.div>
) : (
/* Step 2: Configuration */
<motion.div
key="step2"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
className="max-w-2xl mx-auto space-y-8"
>
{/* Selected Role Card */}
{selectedRole && (
<div
className="flex items-center gap-4 p-5 rounded-2xl cursor-pointer"
style={{
background: `${selectedRole.color}08`,
border: `1px solid ${selectedRole.color}40`,
}}
onClick={() => setStep(1)}
>
<span className="text-3xl">{selectedRole.icon}</span>
<div className="flex-1">
<h3 className="font-semibold">{selectedRole.title}</h3>
<p className="text-sm" style={{ color: 'var(--muted-light)' }}>
{selectedRole.description}
</p>
</div>
<span className="text-sm" style={{ color: 'var(--primary-light)' }}>
Change →
</span>
</div>
)}
{/* Difficulty Selection */}
<div>
<label className="flex items-center gap-2 text-sm font-medium mb-3" style={{ color: 'var(--muted-light)' }}>
<Gauge className="w-4 h-4" />
Difficulty Level
</label>
<div className="grid grid-cols-3 gap-3">
{(Object.entries(difficultyConfig) as [Difficulty, typeof difficultyConfig.beginner][]).map(([key, config]) => (
<button
key={key}
onClick={() => setDifficulty(key)}
className="p-4 rounded-xl text-left transition-all duration-300 cursor-pointer"
style={{
background: difficulty === key ? `${config.color}10` : 'var(--card)',
border: `1px solid ${difficulty === key ? config.color : 'var(--border)'}`,
boxShadow: difficulty === key ? `0 0 20px ${config.color}20` : 'none',
}}
>
<span className="text-xl mb-2 block">{config.icon}</span>
<span className="font-semibold text-sm block" style={{ color: difficulty === key ? config.color : 'var(--foreground)' }}>
{config.label}
</span>
<span className="text-xs block mt-1" style={{ color: 'var(--muted)' }}>
{config.description}
</span>
</button>
))}
</div>
</div>
{/* Question Count */}
<div>
<label className="flex items-center gap-2 text-sm font-medium mb-3" style={{ color: 'var(--muted-light)' }}>
<Hash className="w-4 h-4" />
Number of Questions
</label>
<div className="flex gap-3">
{questionCounts.map((count) => (
<button
key={count}
onClick={() => setQuestionCount(count)}
className="flex-1 py-3 rounded-xl font-semibold text-center transition-all duration-300 cursor-pointer"
style={{
background: questionCount === count ? 'var(--primary)' : 'var(--card)',
border: `1px solid ${questionCount === count ? 'var(--primary)' : 'var(--border)'}`,
color: questionCount === count ? 'white' : 'var(--foreground)',
boxShadow: questionCount === count ? '0 0 20px rgba(108, 92, 231, 0.3)' : 'none',
}}
>
{count}
</button>
))}
</div>
</div>
{/* Exam Timer */}
<div>
<label className="flex items-center gap-2 text-sm font-medium mb-3" style={{ color: 'var(--muted-light)' }}>
<Clock className="w-4 h-4" />
Exam Timer
</label>
<div className="grid grid-cols-4 gap-3">
{timeLimits.map((tl) => (
<button
key={tl.value}
onClick={() => setTimeLimit(tl.value)}
className="p-3 rounded-xl text-center transition-all duration-300 cursor-pointer"
style={{
background: timeLimit === tl.value ? 'rgba(0, 206, 201, 0.1)' : 'var(--card)',
border: `1px solid ${timeLimit === tl.value ? 'var(--secondary)' : 'var(--border)'}`,
boxShadow: timeLimit === tl.value ? '0 0 20px rgba(0, 206, 201, 0.2)' : 'none',
}}
>
<span className="text-lg block mb-1">{tl.icon}</span>
<span className="text-xs font-semibold block" style={{ color: timeLimit === tl.value ? 'var(--secondary)' : 'var(--foreground)' }}>
{tl.label}
</span>
</button>
))}
</div>
{timeLimit > 0 && (
<p className="text-xs mt-2 flex items-center gap-1" style={{ color: 'var(--warning)' }}>
<Timer className="w-3 h-3" />
Interview will auto-submit after {timeLimit} minutes
</p>
)}
</div>
{/* Start Button */}
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={handleStartInterview}
className="w-full py-4 rounded-xl font-bold text-lg flex items-center justify-center gap-3 cursor-pointer"
style={{
background: 'var(--gradient-primary)',
color: 'white',
boxShadow: '0 8px 30px rgba(108, 92, 231, 0.3)',
}}
>
<Sparkles className="w-5 h-5" />
Start Interview
<ArrowRight className="w-5 h-5" />
</motion.button>
{/* Back button */}
<button
onClick={() => setStep(1)}
className="w-full py-3 rounded-xl text-sm font-medium transition-all duration-300 cursor-pointer"
style={{
color: 'var(--muted)',
border: '1px solid var(--border)',
}}
>
← Back to Role Selection
</button>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
);
}