aki-008 commited on
Commit
acceac1
·
1 Parent(s): 1423ea9

chore: fix reload, chat history

Browse files
Frontend/src/api/notesService.ts CHANGED
@@ -14,6 +14,13 @@ export interface ChatMessage {
14
  content: string;
15
  }
16
 
 
 
 
 
 
 
 
17
  // 1. Fetch the list of PDFs for the Sidebar
18
  export const fetchNotes = async (): Promise<Note[]> => {
19
  const response: AxiosResponse<Note[]> = await API.get("/notes/");
@@ -75,6 +82,11 @@ export const fetchChatHistory = async (sessionId: string) => {
75
  return response.data;
76
  };
77
 
 
 
 
 
 
78
  // 7. Stream Chat (Special Handling using fetch API)
79
  export const streamChatRequest = async (
80
  sessionId: string,
 
14
  content: string;
15
  }
16
 
17
+ export interface Session {
18
+ id: string;
19
+ name: string;
20
+ created_at: string;
21
+ pdf_id: number;
22
+ }
23
+
24
  // 1. Fetch the list of PDFs for the Sidebar
25
  export const fetchNotes = async (): Promise<Note[]> => {
26
  const response: AxiosResponse<Note[]> = await API.get("/notes/");
 
82
  return response.data;
83
  };
84
 
85
+ export const fetchSessions = async (pdfId: number): Promise<Session[]> => {
86
+ const response = await API.get(`/notes/sessions/${pdfId}`);
87
+ return response.data;
88
+ };
89
+
90
  // 7. Stream Chat (Special Handling using fetch API)
91
  export const streamChatRequest = async (
92
  sessionId: string,
Frontend/src/components/auth/SignIn.tsx CHANGED
@@ -4,7 +4,7 @@ import API from "../../api/api";
4
  import { useAuth } from "../context/AuthContext";
5
 
6
  interface SignInProps {
7
- onSwitchToSignUp: () => void; // ✅ Add this if switching modal
8
  }
9
 
10
  const SignIn: React.FC<SignInProps> = ({ onSwitchToSignUp }) => {
@@ -20,19 +20,17 @@ const SignIn: React.FC<SignInProps> = ({ onSwitchToSignUp }) => {
20
  try {
21
  const res = await API.post("/auth/login", { email, password });
22
 
23
- // ✅ FIXED use res instead of response
24
- login(res.data.username || res.data.user?.username || "User");
25
-
26
- localStorage.setItem("token", res.data.access_token);
27
-
28
  } catch (err: any) {
29
  console.error("Login error:", err);
30
 
31
  let errorMessage = "Login failed.";
32
-
33
  if (err.response?.data?.detail) {
34
  const detail = err.response.data.detail;
35
-
36
  if (typeof detail === "string") errorMessage = detail;
37
  else if (Array.isArray(detail)) {
38
  const first = detail[0];
@@ -46,7 +44,9 @@ const SignIn: React.FC<SignInProps> = ({ onSwitchToSignUp }) => {
46
 
47
  return (
48
  <>
49
- <h2 className="text-3xl font-bold mb-8 text-center text-white">Welcome Back</h2>
 
 
50
 
51
  {error && <p className="text-red-400 text-center mb-3">{error}</p>}
52
 
@@ -83,7 +83,7 @@ const SignIn: React.FC<SignInProps> = ({ onSwitchToSignUp }) => {
83
 
84
  <button
85
  type="submit"
86
- className="w-full px-5 py-3 rounded-lg bg-linear-to-r from-blue-500 to-gray-500 text-white flex items-center justify-center gap-2"
87
  >
88
  <LogIn className="w-5 h-5" />
89
  Sign In
@@ -100,4 +100,4 @@ const SignIn: React.FC<SignInProps> = ({ onSwitchToSignUp }) => {
100
  );
101
  };
102
 
103
- export default SignIn;
 
4
  import { useAuth } from "../context/AuthContext";
5
 
6
  interface SignInProps {
7
+ onSwitchToSignUp: () => void;
8
  }
9
 
10
  const SignIn: React.FC<SignInProps> = ({ onSwitchToSignUp }) => {
 
20
  try {
21
  const res = await API.post("/auth/login", { email, password });
22
 
23
+ // ✅ UPDATED: Pass token to login() to persist session
24
+ login(
25
+ res.data.username || res.data.user?.username || "User",
26
+ res.data.access_token
27
+ );
28
  } catch (err: any) {
29
  console.error("Login error:", err);
30
 
31
  let errorMessage = "Login failed.";
 
32
  if (err.response?.data?.detail) {
33
  const detail = err.response.data.detail;
 
34
  if (typeof detail === "string") errorMessage = detail;
35
  else if (Array.isArray(detail)) {
36
  const first = detail[0];
 
44
 
45
  return (
46
  <>
47
+ <h2 className="text-3xl font-bold mb-8 text-center text-white">
48
+ Welcome Back
49
+ </h2>
50
 
51
  {error && <p className="text-red-400 text-center mb-3">{error}</p>}
52
 
 
83
 
84
  <button
85
  type="submit"
86
+ className="w-full px-5 py-3 rounded-lg bg-gradient-to-r from-blue-500 to-gray-500 text-white flex items-center justify-center gap-2"
87
  >
88
  <LogIn className="w-5 h-5" />
89
  Sign In
 
100
  );
101
  };
102
 
103
+ export default SignIn;
Frontend/src/components/context/AuthContext.tsx CHANGED
@@ -1,11 +1,9 @@
1
- import React, { createContext, useContext, useState } from "react";
2
 
3
  interface AuthContextType {
4
  isAuthenticated: boolean;
5
- // 1. Added username to the context type
6
  username: string;
7
- // 2. Updated login signature to accept the username string
8
- login: (name: string) => void;
9
  logout: () => void;
10
  }
11
 
@@ -13,19 +11,30 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
13
 
14
  export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
15
  const [isAuthenticated, setIsAuthenticated] = useState(false);
16
- // 3. New state to hold the logged-in user's name
17
- const [username, setUsername] = useState('Guest');
18
 
19
- // 4. Updated login function to accept and set the username
20
- const login = (name: string) => {
 
 
 
 
 
 
 
 
 
 
 
21
  setUsername(name);
22
  setIsAuthenticated(true);
23
  };
24
 
25
  const logout = () => {
26
- // Reset state on logout
 
27
  setIsAuthenticated(false);
28
- setUsername('Guest');
29
  };
30
 
31
  return (
@@ -39,4 +48,4 @@ export const useAuth = () => {
39
  const ctx = useContext(AuthContext);
40
  if (!ctx) throw new Error("useAuth must be inside AuthProvider");
41
  return ctx;
42
- };
 
1
+ import React, { createContext, useContext, useState, useEffect } from "react";
2
 
3
  interface AuthContextType {
4
  isAuthenticated: boolean;
 
5
  username: string;
6
+ login: (name: string, token: string) => void;
 
7
  logout: () => void;
8
  }
9
 
 
11
 
12
  export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
13
  const [isAuthenticated, setIsAuthenticated] = useState(false);
14
+ const [username, setUsername] = useState("Guest");
 
15
 
16
+ // FIX: Check localStorage on mount to persist session
17
+ useEffect(() => {
18
+ const token = localStorage.getItem("token");
19
+ const storedUser = localStorage.getItem("username");
20
+ if (token) {
21
+ setIsAuthenticated(true);
22
+ if (storedUser) setUsername(storedUser);
23
+ }
24
+ }, []);
25
+
26
+ const login = (name: string, token: string) => {
27
+ localStorage.setItem("token", token);
28
+ localStorage.setItem("username", name);
29
  setUsername(name);
30
  setIsAuthenticated(true);
31
  };
32
 
33
  const logout = () => {
34
+ localStorage.removeItem("token");
35
+ localStorage.removeItem("username");
36
  setIsAuthenticated(false);
37
+ setUsername("Guest");
38
  };
39
 
40
  return (
 
48
  const ctx = useContext(AuthContext);
49
  if (!ctx) throw new Error("useAuth must be inside AuthProvider");
50
  return ctx;
51
+ };
Frontend/src/pages/home.tsx CHANGED
@@ -1,409 +1,495 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { Sparkles, Zap, Target, Clock, Award, ChevronRight, Menu, X } from 'lucide-react';
3
- import AuthModal from '../components/auth/AuthModal';
4
- import SignIn from '../components/auth/SignIn';
5
- import SignUp from '../components/auth/SignUp';
6
- import { SplineScene } from '../components/ui/splite';
7
- import { Spotlight } from '../components/ui/spotlight';
 
 
 
 
 
 
 
 
 
8
 
9
  interface Feature {
10
- icon: React.ReactNode;
11
- title: string;
12
- description: string;
13
  }
14
 
15
  interface Step {
16
- step: string;
17
- title: string;
18
- desc: string;
19
  }
20
 
21
- // Add onLogin prop
22
  interface AIInterviewPlatformProps {
23
- onLogin: (username:string) => void;
24
  }
25
 
26
- const AIInterviewPlatform: React.FC<AIInterviewPlatformProps> = ({ onLogin }) => {
27
- const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
28
- const [scrolled, setScrolled] = useState<boolean>(false);
29
- const [modalType, setModalType] = useState<'none' | 'signIn' | 'signUp'>('none');
30
-
31
- // Handlers for opening/closing modals
32
- const openSignInModal = () => {
33
- setModalType('signIn');
34
- setIsMenuOpen(false);
35
- };
36
-
37
- const openSignUpModal = () => {
38
- setModalType('signUp');
39
- setIsMenuOpen(false);
40
- };
41
-
42
- const closeModal = () => setModalType('none');
43
-
44
- // Handle successful authentication
45
- const handleAuthSuccess = (username:string) => {
46
- closeModal();
47
- onLogin(username); // Call the parent function to update auth state
 
 
 
 
 
 
 
 
 
48
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- useEffect(() => {
51
- const handleScroll = (): void => {
52
- setScrolled(window.scrollY > 50);
53
- };
54
- window.addEventListener('scroll', handleScroll);
55
- return () => window.removeEventListener('scroll', handleScroll);
56
- }, []);
57
-
58
- const features: Feature[] = [
59
- {
60
- icon: <Sparkles className="w-8 h-8" />,
61
- title: "AI-Powered Questions",
62
- description: "Dynamic questions adapted to your skill level and role"
63
- },
64
- {
65
- icon: <Zap className="w-8 h-8" />,
66
- title: "Instant Feedback",
67
- description: "Get real-time analysis and improvement suggestions"
68
- },
69
- {
70
- icon: <Target className="w-8 h-8" />,
71
- title: "Role-Specific Prep",
72
- description: "Tailored scenarios for your target position"
73
- },
74
- {
75
- icon: <Clock className="w-8 h-8" />,
76
- title: "Practice Anytime",
77
- description: "24/7 access to unlimited mock interviews"
78
- },
79
- {
80
- icon: <Award className="w-8 h-8" />,
81
- title: "Performance Analytics",
82
- description: "Track your progress with detailed insights"
83
- }
84
- ];
85
-
86
- const steps: Step[] = [
87
- { step: "01", title: "Choose Your Role", desc: "Select the job position you're preparing for" },
88
- { step: "02", title: "Practice Interview", desc: "Answer AI-generated questions in real-time" },
89
- { step: "03", title: "Get Feedback", desc: "Receive detailed analysis and improvement tips" }
90
- ];
91
-
92
- const heroRef = useRef<HTMLDivElement>(null);
93
-
94
- return (
95
- <div className="w-full min-h-screen bg-linear-to-br from-blue-900 to-gray-400 text-white overflow-x-hidden font-opensans">
96
- {/* Navigation Bar */}
97
- <nav className={`fixed top-0 left-0 right-0 w-full z-50 transition-all duration-300 ${scrolled ? 'bg-slate-900/95 backdrop-blur-md shadow-xl' : 'bg-slate-900/80 backdrop-blur-sm'}`}>
98
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
99
- <div className="flex justify-between items-center h-20">
100
- {/* Logo */}
101
- <div className="flex items-center space-x-3">
102
- <div className="bg-linear-to-br from-blue-500 to-gray-400 p-2 rounded-lg">
103
- <Sparkles className="w-6 h-6 text-white" />
104
- </div>
105
- <span className="text-2xl font-bold bg-linear-to-br from-blue-500 to-gray-400 bg-clip-text text-transparent">
106
- InterviewAI
107
- </span>
108
- </div>
109
-
110
- {/* Desktop Navigation */}
111
- <div className="hidden lg:flex items-center space-x-8">
112
- <a href="#home" className="text-gray-200 hover:text-blue-400 transition-colors font-medium">
113
- Home
114
- </a>
115
- <a href="#about" className="text-gray-200 hover:text-blue-400 transition-colors font-medium">
116
- About
117
- </a>
118
- <a href="#features" className="text-gray-200 hover:text-blue-400 transition-colors font-medium">
119
- Features
120
- </a>
121
- <a href="#how-it-works" className="text-gray-200 hover:text-blue-400 transition-colors font-medium">
122
- How It Works
123
- </a>
124
- <button onClick={openSignInModal} className="px-6 py-2.5 rounded-lg border-2 border-blue-400 text-blue-400 hover:bg-blue-400/10 transition-all font-medium">
125
- Sign In
126
- </button>
127
- <button onClick={openSignUpModal} className="px-6 py-2.5 rounded-lg bg-linear-to-br from-blue-500 to-blue-500 hover:from-blue-600 hover:to-blue-900 transition-all shadow-lg shadow-blue-500/30 font-medium">
128
- Sign Up
129
- </button>
130
- </div>
131
-
132
- {/* Mobile Menu Button */}
133
- <button
134
- className="lg:hidden p-2 rounded-lg hover:bg-white/10 transition-colors"
135
- onClick={() => setIsMenuOpen(!isMenuOpen)}
136
- >
137
- {isMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
138
- </button>
139
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  </div>
141
-
142
- {/* Mobile Menu */}
143
- {isMenuOpen && (
144
- <div className="lg:hidden border-t border-white/10 bg-slate-900/98 backdrop-blur-md">
145
- <div className="px-4 py-4 space-y-3 max-w-7xl mx-auto">
146
- <a
147
- href="#home"
148
- className="block py-3 px-4 rounded-lg text-gray-200 hover:bg-blue-500/10 hover:text-blue-400 transition-all font-medium"
149
- onClick={() => setIsMenuOpen(false)}
150
- >
151
- Home
152
- </a>
153
- <a
154
- href="#about"
155
- className="block py-3 px-4 rounded-lg text-gray-200 hover:bg-blue-500/10 hover:text-blue-400 transition-all font-medium"
156
- onClick={() => setIsMenuOpen(false)}
157
- >
158
- About
159
- </a>
160
- <a
161
- href="#features"
162
- className="block py-3 px-4 rounded-lg text-gray-200 hover:bg-blue-500/10 hover:text-blue-400 transition-all font-medium"
163
- onClick={() => setIsMenuOpen(false)}
164
- >
165
- Features
166
- </a>
167
- <a
168
- href="#how-it-works"
169
- className="block py-3 px-4 rounded-lg text-gray-200 hover:bg-blue-500/10 hover:text-blue-400 transition-all font-medium"
170
- onClick={() => setIsMenuOpen(false)}
171
- >
172
- How It Works
173
- </a>
174
- <button onClick={openSignInModal} className="w-full py-3 rounded-lg border-2 border-blue-400 text-blue-400 hover:bg-blue-400/10 transition-all font-medium">
175
- Sign In
176
- </button>
177
- <button onClick={openSignUpModal} className="w-full py-3 rounded-lg bg-linear-to-br from-blue-500 to-blue-500 hover:from-blue-600 hover:to-blue-900 transition-all font-medium">
178
- Sign Up
179
- </button>
180
- </div>
181
- </div>
182
- )}
183
- </nav>
184
-
185
- {/* Auth Modals */}
186
- <AuthModal isOpen={modalType === 'signIn'} onClose={closeModal}>
187
- <SignIn
188
- onClose={closeModal}
189
- onSwitchToSignUp={openSignUpModal}
190
- onAuthSuccess={handleAuthSuccess}
191
- />
192
- </AuthModal>
193
-
194
- <AuthModal isOpen={modalType === 'signUp'} onClose={closeModal}>
195
- <SignUp
196
- onClose={closeModal}
197
- onSwitchToSignIn={openSignInModal}
198
- onAuthSuccess={handleAuthSuccess}
199
- />
200
- </AuthModal>
201
-
202
- {/* Hero Section */}
203
- <section ref={heroRef} id="home" className="min-h-[calc(100vh-68px)] flex items-center pt-20 pb-20 px-4 sm:px-6 lg:px-8 relative overflow-hidden bg-black">
204
- {/* Spotlight applied directly to the container as per the demo structure */}
205
- <Spotlight parentRef={heroRef} color="#4b9fff" className="mix-blend-screen" />
206
- <div className="max-w-7xl mx-auto w-full relative z-10">
207
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
208
- {/* Left side - Text content */}
209
- <div>
210
- <h1 className="text-5xl sm:text-6xl lg:text-7xl font-bold mb-6 leading-tight">
211
- Master Your Next
212
- <span className="block bg-linear-to-br from-blue-500 via-gray-400 to-blue-500 bg-clip-text text-transparent">
213
- Interview with AI
214
- </span>
215
- </h1>
216
- <p className="text-xl text-gray-300 mb-10 max-w-lg">
217
- Practice with our intelligent AI interviewer, get instant feedback, and land your dream job with confidence.
218
- </p>
219
- <button
220
- onClick={openSignUpModal}
221
- className="px-8 py-4 rounded-xl bg-linear-to-br from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 transition duration-300 shadow-xl shadow-blue-600/30 flex items-center gap-2 text-lg font-semibold transform hover:scale-[1.03]"
222
- >
223
- Start Free Trial
224
- <ChevronRight className="w-5 h-5" />
225
- </button>
226
- </div>
227
-
228
- {/* Right side - 3D Scene */}
229
- <div className="h-[500px] lg:h-[600px] relative">
230
- <SplineScene
231
- scene="https://prod.spline.design/kZDDjO5HuC9GJUM2/scene.splinecode"
232
- className="w-full h-full"
233
- />
234
- </div>
235
- </div>
236
  </div>
237
- </section>
238
-
239
- {/* About Section */}
240
- <section id="about" className="py-20 px-4 sm:px-6 lg:px-8 bg-black/40">
241
- <div className="max-w-6xl mx-auto">
242
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
243
- <div>
244
- <h2 className="text-4xl sm:text-5xl font-bold mb-6">
245
- About InterviewAI
246
- </h2>
247
- <p className="text-lg text-gray-300 mb-6">
248
- We're revolutionizing interview preparation with cutting-edge AI technology. Our platform helps candidates practice, learn, and succeed in their job interviews.
249
- </p>
250
- <p className="text-lg text-gray-300 mb-6">
251
- Founded by industry experts and powered by advanced machine learning, InterviewAI provides personalized interview experiences that adapt to your unique needs and goals.
252
- </p>
253
- <div className="space-y-4">
254
- <div className="flex items-center gap-3">
255
- <div className="w-2 h-2 bg-blue-400 rounded-full"></div>
256
- <span className="text-gray-300">Advanced AI interview simulations</span>
257
- </div>
258
- <div className="flex items-center gap-3">
259
- <div className="w-2 h-2 bg-blue-600 rounded-full"></div>
260
- <span className="text-gray-300">Personalized feedback and coaching</span>
261
- </div>
262
- <div className="flex items-center gap-3">
263
- <div className="w-2 h-2 bg-blue-400 rounded-full"></div>
264
- <span className="text-gray-300">Industry-leading success rates</span>
265
- </div>
266
- </div>
267
- </div>
268
- <div className="bg-linear-to-br from-blue-900/50 to-gray-900/50 rounded-2xl p-8 border border-gray-200/30">
269
- <div className="space-y-6">
270
- <div className="bg-white/5 backdrop-blur-sm rounded-lg p-6">
271
- <div className="text-3xl font-bold text-blue-400 mb-2">2020</div>
272
- <div className="text-gray-400">Founded</div>
273
- </div>
274
- <div className="bg-white/5 backdrop-blur-sm rounded-lg p-6">
275
- <div className="text-3xl font-bold text-blue-500 mb-2">50K+</div>
276
- <div className="text-gray-400">Happy Users</div>
277
- </div>
278
- <div className="bg-white/5 backdrop-blur-sm rounded-lg p-6">
279
- <div className="text-3xl font-bold text-blue-400 mb-2">500+</div>
280
- <div className="text-gray-400">Companies Trust Us</div>
281
- </div>
282
- </div>
283
- </div>
284
- </div>
285
  </div>
286
- </section>
287
-
288
- {/* Features Section */}
289
- <section id="features" className="py-20 px-4 sm:px-6 lg:px-8 bg-black">
290
- <div className="max-w-7xl mx-auto">
291
- <div className="text-center mb-16">
292
- <h2 className="text-4xl sm:text-5xl font-bold mb-4">
293
- Why Choose InterviewAI?
294
- </h2>
295
- <p className="text-xl text-gray-400">
296
- Everything you need to ace your next interview
297
- </p>
298
- </div>
299
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
300
- {features.map((feature: Feature, index: number) => (
301
- <div
302
- key={index}
303
- className="bg-linear-to-br from-blue-900/50 to-gray-900/50 backdrop-blur-sm rounded-xl p-8 border border-blue-500/20 hover:border-gray-500/50 transition-all duration-300 hover:transform hover:scale-105"
304
- >
305
- <div className="bg-linear-to-br from-blue-500 to-gray-400 w-16 h-16 rounded-lg flex items-center justify-center mb-4 shadow-lg shadow-blue-500/50">
306
- {feature.icon}
307
- </div>
308
- <h3 className="text-xl font-bold mb-3">{feature.title}</h3>
309
- <p className="text-gray-400">{feature.description}</p>
310
- </div>
311
- ))}
312
- </div>
313
  </div>
314
- </section>
315
-
316
- {/* How It Works */}
317
- <section id="how-it-works" className="py-20 px-4 sm:px-6 lg:px-8 bg-linear-to-br from-blue-900/50 to-gray-900/50 rounded-2xl border border-gray-200/30">
318
- <div className="max-w-7xl mx-auto">
319
- <div className="text-center mb-16">
320
- <h2 className="text-4xl sm:text-5xl font-bold mb-4">
321
- How It Works
322
- </h2>
323
- <p className="text-xl text-gray-400">
324
- Get started in three simple steps
325
- </p>
326
- </div>
327
-
328
- <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
329
- {steps.map((item: Step, index: number) => (
330
- <div key={index} className="relative text-center">
331
- <div className="text-7xl font-bold text-white mb-4">
332
- {item.step}
333
- </div>
334
- <h3 className="text-2xl font-bold mb-3">{item.title}</h3>
335
- <p className="text-gray-400">{item.desc}</p>
336
-
337
- {index < 2 && (
338
- <ChevronRight className="hidden md:block absolute top-12 -right-12 w-8 h-8 text-white" />
339
- )}
340
- </div>
341
- ))}
342
- </div>
343
  </div>
344
- </section>
345
-
346
-
347
- {/* CTA Section */}
348
- <section className="py-20 px-4 sm:px-6 lg:px-8 bg-black">
349
- <div className="max-w-4xl mx-auto text-center">
350
- <h2 className="text-4xl sm:text-5xl font-bold mb-6">
351
- Ready to Ace Your Interview?
352
- </h2>
353
- <p className="text-xl text-gray-300 mb-10">
354
- Join thousands of successful candidates who prepared with InterviewAI
355
- </p>
356
- <button
357
- onClick={openSignUpModal}
358
- className="px-10 py-5 rounded-lg bg-linear-to-r from-blue-400 to-blue-700 hover:from-blue-600 hover:to-blue-900 transition shadow-lg shadow-blue-500/50 text-lg font-semibold"
359
- >
360
- Start Your Free Trial Today
361
- </button>
362
  </div>
363
- </section>
364
-
365
- {/* Footer */}
366
- <footer className="w-full py-6 px-6 bg-slate-900/95 backdrop-blur-md text-gray-200 flex flex-col sm:flex-row items-center justify-between shadow-inner">
367
- {/* Left Side */}
368
- <div className="flex items-center space-x-3">
369
- {/* Logo */}
370
- <div className="bg-linear-to-br from-blue-500 to-gray-400 p-2 rounded-lg">
371
- <Sparkles className="w-6 h-6 text-white" />
372
- </div>
373
-
374
- <p className="text-sm">
375
- Copyright © {new Date().getFullYear()} — All rights reserved
376
- </p>
 
 
 
 
 
 
 
 
 
 
 
377
  </div>
378
-
379
- {/* Right Side */}
380
- <div className="flex items-center gap-5 mt-4 sm:mt-0">
381
- {/* Twitter */}
382
- <a href="#" className="hover:text-blue-400 transition">
383
- <svg
384
- xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-current">
385
- <path d="M24 4.557a9.93 9.93 0 01-2.828.775A4.93 4.93 0 0023.337 3a9.864 9.864 0 01-3.127 1.195A4.92 4.92 0 0016.616 3c-2.72 0-4.924 2.21-4.924 4.932 0 .39.042.765.124 1.126C7.728 8.89 4.1 6.91 1.67 3.917a4.936 4.936 0 00-.665 2.48c0 1.71.86 3.213 2.17 4.096A4.9 4.9 0 01.96 9.96v.06c0 2.387 1.68 4.374 3.91 4.828a4.93 4.93 0 01-2.224.086c.626 1.956 2.444 3.384 4.6 3.425A9.874 9.874 0 010 21.54 13.945 13.945 0 007.548 24c9.056 0 14.01-7.512 14.01-14.015 0-.213-.005-.426-.015-.637A9.94 9.94 0 0024 4.557z" />
386
- </svg>
387
- </a>
388
-
389
- {/* YouTube */}
390
- <a href="#" className="hover:text-red-500 transition">
391
- <svg
392
- xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-current">
393
- <path d="M19.615 3.184C21.403 3.67 22 5.84 22 12s-.597 8.33-2.385 8.816C17.42 21.27 12 21.27 12 21.27s-5.42 0-7.615-.454C2.597 20.33 2 18.16 2 12s.597-8.33 2.385-8.816C6.58 2.73 12 2.73 12 2.73s5.42 0 7.615.454zM10 8.5l6 3.5-6 3.5v-7z" />
394
- </svg>
395
- </a>
396
-
397
- {/* Facebook */}
398
- <a href="#" className="hover:text-blue-500 transition">
399
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" className="fill-current">
400
- <path d="M9 8H6v4h3v12h5V12h3.642L18 8h-4V6.333C14 5.378 14.2 5 15.112 5H18V0h-3.667C10.55 0 9 1.517 9 4.308V8z" />
401
- </svg>
402
- </a>
 
 
403
  </div>
404
- </footer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  </div>
406
- );
 
 
407
  };
408
 
409
- export default AIInterviewPlatform;
 
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import {
3
+ Sparkles,
4
+ Zap,
5
+ Target,
6
+ Clock,
7
+ Award,
8
+ ChevronRight,
9
+ Menu,
10
+ X,
11
+ } from "lucide-react";
12
+ import AuthModal from "../components/auth/AuthModal";
13
+ import SignIn from "../components/auth/SignIn";
14
+ import SignUp from "../components/auth/SignUp";
15
+ import { SplineScene } from "../components/ui/splite";
16
+ import { Spotlight } from "../components/ui/spotlight";
17
 
18
  interface Feature {
19
+ icon: React.ReactNode;
20
+ title: string;
21
+ description: string;
22
  }
23
 
24
  interface Step {
25
+ step: string;
26
+ title: string;
27
+ desc: string;
28
  }
29
 
30
+ // UPDATED: Interface now accepts token
31
  interface AIInterviewPlatformProps {
32
+ onLogin: (username: string, token: string) => void;
33
  }
34
 
35
+ const AIInterviewPlatform: React.FC<AIInterviewPlatformProps> = ({
36
+ onLogin,
37
+ }) => {
38
+ const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
39
+ const [scrolled, setScrolled] = useState<boolean>(false);
40
+ const [modalType, setModalType] = useState<"none" | "signIn" | "signUp">(
41
+ "none"
42
+ );
43
+
44
+ // Handlers for opening/closing modals
45
+ const openSignInModal = () => {
46
+ setModalType("signIn");
47
+ setIsMenuOpen(false);
48
+ };
49
+
50
+ const openSignUpModal = () => {
51
+ setModalType("signUp");
52
+ setIsMenuOpen(false);
53
+ };
54
+
55
+ const closeModal = () => setModalType("none");
56
+
57
+ // ✅ UPDATED: Handler now accepts and passes token
58
+ const handleAuthSuccess = (username: string, token: string) => {
59
+ closeModal();
60
+ onLogin(username, token);
61
+ };
62
+
63
+ useEffect(() => {
64
+ const handleScroll = (): void => {
65
+ setScrolled(window.scrollY > 50);
66
  };
67
+ window.addEventListener("scroll", handleScroll);
68
+ return () => window.removeEventListener("scroll", handleScroll);
69
+ }, []);
70
+
71
+ const features: Feature[] = [
72
+ {
73
+ icon: <Sparkles className="w-8 h-8" />,
74
+ title: "AI-Powered Questions",
75
+ description: "Dynamic questions adapted to your skill level and role",
76
+ },
77
+ {
78
+ icon: <Zap className="w-8 h-8" />,
79
+ title: "Instant Feedback",
80
+ description: "Get real-time analysis and improvement suggestions",
81
+ },
82
+ {
83
+ icon: <Target className="w-8 h-8" />,
84
+ title: "Role-Specific Prep",
85
+ description: "Tailored scenarios for your target position",
86
+ },
87
+ {
88
+ icon: <Clock className="w-8 h-8" />,
89
+ title: "Practice Anytime",
90
+ description: "24/7 access to unlimited mock interviews",
91
+ },
92
+ {
93
+ icon: <Award className="w-8 h-8" />,
94
+ title: "Performance Analytics",
95
+ description: "Track your progress with detailed insights",
96
+ },
97
+ ];
98
+
99
+ const steps: Step[] = [
100
+ {
101
+ step: "01",
102
+ title: "Choose Your Role",
103
+ desc: "Select the job position you're preparing for",
104
+ },
105
+ {
106
+ step: "02",
107
+ title: "Practice Interview",
108
+ desc: "Answer AI-generated questions in real-time",
109
+ },
110
+ {
111
+ step: "03",
112
+ title: "Get Feedback",
113
+ desc: "Receive detailed analysis and improvement tips",
114
+ },
115
+ ];
116
+
117
+ const heroRef = useRef<HTMLDivElement>(null);
118
+
119
+ return (
120
+ <div className="w-full min-h-screen bg-gradient-to-br from-blue-900 to-gray-400 text-white overflow-x-hidden font-opensans">
121
+ {/* Navigation Bar */}
122
+ <nav
123
+ className={`fixed top-0 left-0 right-0 w-full z-50 transition-all duration-300 ${
124
+ scrolled
125
+ ? "bg-slate-900/95 backdrop-blur-md shadow-xl"
126
+ : "bg-slate-900/80 backdrop-blur-sm"
127
+ }`}
128
+ >
129
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
130
+ <div className="flex justify-between items-center h-20">
131
+ {/* Logo */}
132
+ <div className="flex items-center space-x-3">
133
+ <div className="bg-gradient-to-br from-blue-500 to-gray-400 p-2 rounded-lg">
134
+ <Sparkles className="w-6 h-6 text-white" />
135
+ </div>
136
+ <span className="text-2xl font-bold bg-gradient-to-br from-blue-500 to-gray-400 bg-clip-text text-transparent">
137
+ InterviewAI
138
+ </span>
139
+ </div>
140
+
141
+ {/* Desktop Navigation */}
142
+ <div className="hidden lg:flex items-center space-x-8">
143
+ <a
144
+ href="#home"
145
+ className="text-gray-200 hover:text-blue-400 transition-colors font-medium"
146
+ >
147
+ Home
148
+ </a>
149
+ <a
150
+ href="#about"
151
+ className="text-gray-200 hover:text-blue-400 transition-colors font-medium"
152
+ >
153
+ About
154
+ </a>
155
+ <a
156
+ href="#features"
157
+ className="text-gray-200 hover:text-blue-400 transition-colors font-medium"
158
+ >
159
+ Features
160
+ </a>
161
+ <a
162
+ href="#how-it-works"
163
+ className="text-gray-200 hover:text-blue-400 transition-colors font-medium"
164
+ >
165
+ How It Works
166
+ </a>
167
+ <button
168
+ onClick={openSignInModal}
169
+ className="px-6 py-2.5 rounded-lg border-2 border-blue-400 text-blue-400 hover:bg-blue-400/10 transition-all font-medium"
170
+ >
171
+ Sign In
172
+ </button>
173
+ <button
174
+ onClick={openSignUpModal}
175
+ className="px-6 py-2.5 rounded-lg bg-gradient-to-br from-blue-500 to-blue-500 hover:from-blue-600 hover:to-blue-900 transition-all shadow-lg shadow-blue-500/30 font-medium"
176
+ >
177
+ Sign Up
178
+ </button>
179
+ </div>
180
+
181
+ {/* Mobile Menu Button */}
182
+ <button
183
+ className="lg:hidden p-2 rounded-lg hover:bg-white/10 transition-colors"
184
+ onClick={() => setIsMenuOpen(!isMenuOpen)}
185
+ >
186
+ {isMenuOpen ? (
187
+ <X className="w-6 h-6" />
188
+ ) : (
189
+ <Menu className="w-6 h-6" />
190
+ )}
191
+ </button>
192
+ </div>
193
+ </div>
194
 
195
+ {/* Mobile Menu */}
196
+ {isMenuOpen && (
197
+ <div className="lg:hidden border-t border-white/10 bg-slate-900/98 backdrop-blur-md">
198
+ <div className="px-4 py-4 space-y-3 max-w-7xl mx-auto">
199
+ <a
200
+ href="#home"
201
+ className="block py-3 px-4 rounded-lg text-gray-200 hover:bg-blue-500/10 hover:text-blue-400 transition-all font-medium"
202
+ onClick={() => setIsMenuOpen(false)}
203
+ >
204
+ Home
205
+ </a>
206
+ <a
207
+ href="#about"
208
+ className="block py-3 px-4 rounded-lg text-gray-200 hover:bg-blue-500/10 hover:text-blue-400 transition-all font-medium"
209
+ onClick={() => setIsMenuOpen(false)}
210
+ >
211
+ About
212
+ </a>
213
+ <a
214
+ href="#features"
215
+ className="block py-3 px-4 rounded-lg text-gray-200 hover:bg-blue-500/10 hover:text-blue-400 transition-all font-medium"
216
+ onClick={() => setIsMenuOpen(false)}
217
+ >
218
+ Features
219
+ </a>
220
+ <a
221
+ href="#how-it-works"
222
+ className="block py-3 px-4 rounded-lg text-gray-200 hover:bg-blue-500/10 hover:text-blue-400 transition-all font-medium"
223
+ onClick={() => setIsMenuOpen(false)}
224
+ >
225
+ How It Works
226
+ </a>
227
+ <button
228
+ onClick={openSignInModal}
229
+ className="w-full py-3 rounded-lg border-2 border-blue-400 text-blue-400 hover:bg-blue-400/10 transition-all font-medium"
230
+ >
231
+ Sign In
232
+ </button>
233
+ <button
234
+ onClick={openSignUpModal}
235
+ className="w-full py-3 rounded-lg bg-gradient-to-br from-blue-500 to-blue-500 hover:from-blue-600 hover:to-blue-900 transition-all font-medium"
236
+ >
237
+ Sign Up
238
+ </button>
239
+ </div>
240
+ </div>
241
+ )}
242
+ </nav>
243
+
244
+ {/* Auth Modals */}
245
+ <AuthModal isOpen={modalType === "signIn"} onClose={closeModal}>
246
+ <SignIn
247
+ onSwitchToSignUp={openSignUpModal}
248
+ // We don't need onAuthSuccess for SignIn because it uses useAuth() hook directly,
249
+ // but if you unified them, you could pass it.
250
+ // Currently SignIn handles logic internally.
251
+ />
252
+ </AuthModal>
253
+
254
+ <AuthModal isOpen={modalType === "signUp"} onClose={closeModal}>
255
+ <SignUp
256
+ onClose={closeModal}
257
+ onSwitchToSignIn={openSignInModal}
258
+ onAuthSuccess={handleAuthSuccess}
259
+ />
260
+ </AuthModal>
261
+
262
+ {/* Hero Section */}
263
+ <section
264
+ ref={heroRef}
265
+ id="home"
266
+ className="min-h-[calc(100vh-68px)] flex items-center pt-20 pb-20 px-4 sm:px-6 lg:px-8 relative overflow-hidden bg-black"
267
+ >
268
+ <Spotlight parentRef={heroRef} className="mix-blend-screen" />
269
+ <div className="max-w-7xl mx-auto w-full relative z-10">
270
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
271
+ {/* Left side */}
272
+ <div>
273
+ <h1 className="text-5xl sm:text-6xl lg:text-7xl font-bold mb-6 leading-tight">
274
+ Master Your Next{" "}
275
+ <span className="block bg-gradient-to-br from-blue-500 via-gray-400 to-blue-500 bg-clip-text text-transparent">
276
+ Interview with AI
277
+ </span>
278
+ </h1>
279
+ <p className="text-xl text-gray-300 mb-10 max-w-lg">
280
+ Practice with our intelligent AI interviewer, get instant
281
+ feedback, and land your dream job with confidence.
282
+ </p>
283
+ <button
284
+ onClick={openSignUpModal}
285
+ className="px-8 py-4 rounded-xl bg-gradient-to-br from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 transition duration-300 shadow-xl shadow-blue-600/30 flex items-center gap-2 text-lg font-semibold transform hover:scale-[1.03]"
286
+ >
287
+ Start Free Trial <ChevronRight className="w-5 h-5" />
288
+ </button>
289
+ </div>
290
+
291
+ {/* Right side - 3D Scene */}
292
+ <div className="h-[500px] lg:h-[600px] relative">
293
+ <SplineScene
294
+ scene="https://prod.spline.design/kZDDjO5HuC9GJUM2/scene.splinecode"
295
+ className="w-full h-full"
296
+ />
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </section>
301
+
302
+ {/* About Section */}
303
+ <section id="about" className="py-20 px-4 sm:px-6 lg:px-8 bg-black/40">
304
+ <div className="max-w-6xl mx-auto">
305
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
306
+ <div>
307
+ <h2 className="text-4xl sm:text-5xl font-bold mb-6">
308
+ About InterviewAI
309
+ </h2>
310
+ <p className="text-lg text-gray-300 mb-6">
311
+ We're revolutionizing interview preparation with cutting-edge AI
312
+ technology. Our platform helps candidates practice, learn, and
313
+ succeed in their job interviews.
314
+ </p>
315
+ <p className="text-lg text-gray-300 mb-6">
316
+ Founded by industry experts and powered by advanced machine
317
+ learning, InterviewAI provides personalized interview
318
+ experiences that adapt to your unique needs and goals.
319
+ </p>
320
+ <div className="space-y-4">
321
+ <div className="flex items-center gap-3">
322
+ <div className="w-2 h-2 bg-blue-400 rounded-full"></div>
323
+ <span className="text-gray-300">
324
+ Advanced AI interview simulations
325
+ </span>
326
  </div>
327
+ <div className="flex items-center gap-3">
328
+ <div className="w-2 h-2 bg-blue-600 rounded-full"></div>
329
+ <span className="text-gray-300">
330
+ Personalized feedback and coaching
331
+ </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  </div>
333
+ <div className="flex items-center gap-3">
334
+ <div className="w-2 h-2 bg-blue-400 rounded-full"></div>
335
+ <span className="text-gray-300">
336
+ Industry-leading success rates
337
+ </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  </div>
339
+ </div>
340
+ </div>
341
+ <div className="bg-gradient-to-br from-blue-900/50 to-gray-900/50 rounded-2xl p-8 border border-gray-200/30">
342
+ <div className="space-y-6">
343
+ <div className="bg-white/5 backdrop-blur-sm rounded-lg p-6">
344
+ <div className="text-3xl font-bold text-blue-400 mb-2">
345
+ 2020
346
+ </div>
347
+ <div className="text-gray-400">Founded</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  </div>
349
+ <div className="bg-white/5 backdrop-blur-sm rounded-lg p-6">
350
+ <div className="text-3xl font-bold text-blue-500 mb-2">
351
+ 50K+
352
+ </div>
353
+ <div className="text-gray-400">Happy Users</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  </div>
355
+ <div className="bg-white/5 backdrop-blur-sm rounded-lg p-6">
356
+ <div className="text-3xl font-bold text-blue-400 mb-2">
357
+ 500+
358
+ </div>
359
+ <div className="text-gray-400">Companies Trust Us</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  </div>
361
+ </div>
362
+ </div>
363
+ </div>
364
+ </div>
365
+ </section>
366
+
367
+ {/* Features Section */}
368
+ <section id="features" className="py-20 px-4 sm:px-6 lg:px-8 bg-black">
369
+ <div className="max-w-7xl mx-auto">
370
+ <div className="text-center mb-16">
371
+ <h2 className="text-4xl sm:text-5xl font-bold mb-4">
372
+ Why Choose InterviewAI?
373
+ </h2>
374
+ <p className="text-xl text-gray-400">
375
+ Everything you need to ace your next interview
376
+ </p>
377
+ </div>
378
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
379
+ {features.map((feature: Feature, index: number) => (
380
+ <div
381
+ key={index}
382
+ className="bg-gradient-to-br from-blue-900/50 to-gray-900/50 backdrop-blur-sm rounded-xl p-8 border border-blue-500/20 hover:border-gray-500/50 transition-all duration-300 hover:transform hover:scale-105"
383
+ >
384
+ <div className="bg-gradient-to-br from-blue-500 to-gray-400 w-16 h-16 rounded-lg flex items-center justify-center mb-4 shadow-lg shadow-blue-500/50">
385
+ {feature.icon}
386
  </div>
387
+ <h3 className="text-xl font-bold mb-3">{feature.title}</h3>
388
+ <p className="text-gray-400">{feature.description}</p>
389
+ </div>
390
+ ))}
391
+ </div>
392
+ </div>
393
+ </section>
394
+
395
+ {/* How It Works */}
396
+ <section
397
+ id="how-it-works"
398
+ className="py-20 px-4 sm:px-6 lg:px-8 bg-gradient-to-br from-blue-900/50 to-gray-900/50 rounded-2xl border border-gray-200/30"
399
+ >
400
+ <div className="max-w-7xl mx-auto">
401
+ <div className="text-center mb-16">
402
+ <h2 className="text-4xl sm:text-5xl font-bold mb-4">
403
+ How It Works
404
+ </h2>
405
+ <p className="text-xl text-gray-400">
406
+ Get started in three simple steps
407
+ </p>
408
+ </div>
409
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
410
+ {steps.map((item: Step, index: number) => (
411
+ <div key={index} className="relative text-center">
412
+ <div className="text-7xl font-bold text-white mb-4">
413
+ {item.step}
414
  </div>
415
+ <h3 className="text-2xl font-bold mb-3">{item.title}</h3>
416
+ <p className="text-gray-400">{item.desc}</p>
417
+ {index < 2 && (
418
+ <ChevronRight className="hidden md:block absolute top-12 -right-12 w-8 h-8 text-white" />
419
+ )}
420
+ </div>
421
+ ))}
422
+ </div>
423
+ </div>
424
+ </section>
425
+
426
+ {/* CTA Section */}
427
+ <section className="py-20 px-4 sm:px-6 lg:px-8 bg-black">
428
+ <div className="max-w-4xl mx-auto text-center">
429
+ <h2 className="text-4xl sm:text-5xl font-bold mb-6">
430
+ Ready to Ace Your Interview?
431
+ </h2>
432
+ <p className="text-xl text-gray-300 mb-10">
433
+ Join thousands of successful candidates who prepared with
434
+ InterviewAI
435
+ </p>
436
+ <button
437
+ onClick={openSignUpModal}
438
+ className="px-10 py-5 rounded-lg bg-gradient-to-r from-blue-400 to-blue-700 hover:from-blue-600 hover:to-blue-900 transition shadow-lg shadow-blue-500/50 text-lg font-semibold"
439
+ >
440
+ Start Your Free Trial Today
441
+ </button>
442
+ </div>
443
+ </section>
444
+
445
+ {/* Footer */}
446
+ <footer className="w-full py-6 px-6 bg-slate-900/95 backdrop-blur-md text-gray-200 flex flex-col sm:flex-row items-center justify-between shadow-inner">
447
+ <div className="flex items-center space-x-3">
448
+ <div className="bg-gradient-to-br from-blue-500 to-gray-400 p-2 rounded-lg">
449
+ <Sparkles className="w-6 h-6 text-white" />
450
+ </div>
451
+ <p className="text-sm">
452
+ Copyright © {new Date().getFullYear()} — All rights reserved
453
+ </p>
454
+ </div>
455
+ <div className="flex items-center gap-5 mt-4 sm:mt-0">
456
+ <a href="#" className="hover:text-blue-400 transition">
457
+ <svg
458
+ xmlns="http://www.w3.org/2000/svg"
459
+ width="24"
460
+ height="24"
461
+ viewBox="0 0 24 24"
462
+ className="fill-current"
463
+ >
464
+ <path d="M24 4.557a9.93 9.93 0 01-2.828.775A4.93 4.93 0 0023.337 3a9.864 9.864 0 01-3.127 1.195A4.92 4.92 0 0016.616 3c-2.72 0-4.924 2.21-4.924 4.932 0 .39.042.765.124 1.126C7.728 8.89 4.1 6.91 1.67 3.917a4.936 4.936 0 00-.665 2.48c0 1.71.86 3.213 2.17 4.096A4.9 4.9 0 01.96 9.96v.06c0 2.387 1.68 4.374 3.91 4.828a4.93 4.93 0 01-2.224.086c.626 1.956 2.444 3.384 4.6 3.425A9.874 9.874 0 010 21.54 13.945 13.945 0 007.548 24c9.056 0 14.01-7.512 14.01-14.015 0-.213-.005-.426-.015-.637A9.94 9.94 0 0024 4.557z" />
465
+ </svg>
466
+ </a>
467
+ <a href="#" className="hover:text-red-500 transition">
468
+ <svg
469
+ xmlns="http://www.w3.org/2000/svg"
470
+ width="24"
471
+ height="24"
472
+ viewBox="0 0 24 24"
473
+ className="fill-current"
474
+ >
475
+ <path d="M19.615 3.184C21.403 3.67 22 5.84 22 12s-.597 8.33-2.385 8.816C17.42 21.27 12 21.27 12 21.27s-5.42 0-7.615-.454C2.597 20.33 2 18.16 2 12s.597-8.33 2.385-8.816C6.58 2.73 12 2.73 12 2.73s5.42 0 7.615.454zM10 8.5l6 3.5-6 3.5v-7z" />
476
+ </svg>
477
+ </a>
478
+ <a href="#" className="hover:text-blue-500 transition">
479
+ <svg
480
+ xmlns="http://www.w3.org/2000/svg"
481
+ width="24"
482
+ height="24"
483
+ viewBox="0 0 24 24"
484
+ className="fill-current"
485
+ >
486
+ <path d="M9 8H6v4h3v12h5V12h3.642L18 8h-4V6.333C14 5.378 14.2 5 15.112 5H18V0h-3.667C10.55 0 9 1.517 9 4.308V8z" />
487
+ </svg>
488
+ </a>
489
  </div>
490
+ </footer>
491
+ </div>
492
+ );
493
  };
494
 
495
+ export default AIInterviewPlatform;
Frontend/src/pages/note.tsx CHANGED
@@ -1,17 +1,14 @@
1
  import ReactMarkdown from "react-markdown";
2
  import remarkGfm from "remark-gfm";
3
- import React, { useEffect, useState, useRef } from "react";
4
  import {
5
- History,
6
- RefreshCw,
7
- Plus,
8
  Upload,
9
  Menu,
10
  X,
11
  Send,
12
- MessageSquare,
13
  Loader2,
14
  FileText,
 
15
  } from "lucide-react";
16
  import {
17
  fetchNotes,
@@ -20,6 +17,7 @@ import {
20
  createChatSession,
21
  streamChatRequest,
22
  fetchChatHistory,
 
23
  type Note,
24
  type ChatMessage,
25
  } from "../api/notesService";
@@ -31,6 +29,10 @@ const Notes: React.FC = () => {
31
  const [isUploading, setIsUploading] = useState(false);
32
  const fileInputRef = useRef<HTMLInputElement>(null);
33
 
 
 
 
 
34
  // --- Data State ---
35
  const [notes, setNotes] = useState<Note[]>([]);
36
  const [currentNote, setCurrentNote] = useState<Note | null>(null);
@@ -47,6 +49,32 @@ const Notes: React.FC = () => {
47
  loadNotes();
48
  }, []);
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  const loadNotes = async () => {
51
  try {
52
  const data = await fetchNotes();
@@ -56,7 +84,6 @@ const Notes: React.FC = () => {
56
  }
57
  };
58
 
59
- // 2. Handle File Upload
60
  const handleUploadClick = () => fileInputRef.current?.click();
61
 
62
  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -66,8 +93,8 @@ const Notes: React.FC = () => {
66
  setIsUploading(true);
67
  try {
68
  const newNote = await uploadNote(file);
69
- setNotes([newNote, ...notes]); // Add new note to top of list
70
- handleNoteSelect(newNote); // Auto-select the uploaded note
71
  } catch (error) {
72
  console.error("Upload failed", error);
73
  alert("Failed to upload PDF");
@@ -76,14 +103,15 @@ const Notes: React.FC = () => {
76
  }
77
  };
78
 
79
- // 3. Handle Note Selection (Viewer & Chat Init)
80
  const handleNoteSelect = async (note: Note) => {
81
  setCurrentNote(note);
82
- setPdfUrl(null); // Clear previous PDF to show loading state
83
- setMessages([]); // Clear previous chat
84
  setSessionId(null);
 
85
 
86
- // A. Fetch PDF Blob for Viewer
87
  try {
88
  const blob = await fetchNoteBlob(note.id);
89
  const url = URL.createObjectURL(blob);
@@ -92,59 +120,81 @@ const Notes: React.FC = () => {
92
  console.error("Failed to load PDF content", error);
93
  }
94
 
95
- // B. Initialize Chat Session
96
  try {
97
- // Create a new session for this file
98
- // (In a real app, you might check for existing sessions first)
99
- const session = await createChatSession(
100
- note.id,
101
- `Chat - ${note.filename}`
102
- );
103
- setSessionId(session.id);
104
 
105
- // Add a system welcome message
106
- setMessages([
107
- { role: "assistant", content: `Ready to chat about ${note.filename}!` },
108
- ]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  } catch (error) {
110
  console.error("Failed to init chat session", error);
111
  }
112
  };
113
 
114
- // 4. Handle Chat Streaming
115
  const handleSendMessage = async () => {
116
  if (!inputMessage.trim() || !sessionId) return;
117
 
118
  const userMsg = inputMessage;
119
- setInputMessage(""); // Clear input
120
 
121
- // Add User Message Optimistically
122
  setMessages((prev) => [...prev, { role: "user", content: userMsg }]);
123
- setIsChatLoading(true);
124
 
125
- // Placeholder for AI response
126
  setMessages((prev) => [...prev, { role: "assistant", content: "" }]);
127
 
128
- await streamChatRequest(
129
- sessionId,
130
- userMsg,
131
- (chunk) => {
132
- setMessages((prev) => {
133
- const newArr = [...prev];
134
- const lastIndex = newArr.length - 1;
135
- newArr[lastIndex] = {
136
- ...newArr[lastIndex],
137
- content: newArr[lastIndex].content + chunk,
138
- };
139
-
140
- return newArr;
141
- });
142
- },
143
- (err) => {
144
- console.error("Stream error", err);
145
- setIsChatLoading(false);
146
- }
147
- );
 
 
 
 
 
 
148
  };
149
 
150
  return (
@@ -160,8 +210,6 @@ const Notes: React.FC = () => {
160
  <h3 className="text-xl font-bold text-white mb-2 border-b border-gray-700 pb-2">
161
  My Notes
162
  </h3>
163
-
164
- {/* Hidden Input for Upload */}
165
  <input
166
  type="file"
167
  ref={fileInputRef}
@@ -169,7 +217,6 @@ const Notes: React.FC = () => {
169
  accept="application/pdf"
170
  onChange={handleFileChange}
171
  />
172
-
173
  <button
174
  onClick={handleUploadClick}
175
  disabled={isUploading}
@@ -182,7 +229,6 @@ const Notes: React.FC = () => {
182
  )}
183
  {isUploading ? "Uploading..." : "Upload New PDF"}
184
  </button>
185
-
186
  <div className="mt-4 pt-4 border-t border-gray-700 space-y-2 overflow-y-auto">
187
  <p className="text-sm text-gray-400 uppercase tracking-wider">
188
  History
@@ -229,7 +275,6 @@ const Notes: React.FC = () => {
229
  )}
230
  </header>
231
 
232
- {/* PDF Frame */}
233
  <div className="flex-1 w-full h-full pt-16">
234
  {pdfUrl ? (
235
  <iframe
@@ -246,16 +291,27 @@ const Notes: React.FC = () => {
246
  </div>
247
  </div>
248
 
249
- {/* --- Right: Chat Panel --- */}
 
 
 
 
 
 
 
 
 
 
 
250
  <div
251
- className={`flex flex-col bg-gray-900 border-l border-gray-700 flex-shrink-0 transition-all duration-300 ${
252
- isChatOpen ? "w-96" : "w-0"
253
- }`}
254
  >
255
  {isChatOpen && (
256
  <>
257
  <header className="flex justify-between items-center p-4 border-b border-gray-700">
258
  <h3 className="text-lg font-bold text-white">AI Chat</h3>
 
259
  <button
260
  onClick={() => setIsChatOpen(false)}
261
  className="text-gray-400 hover:text-white"
@@ -264,7 +320,6 @@ const Notes: React.FC = () => {
264
  </button>
265
  </header>
266
 
267
- {/* Messages */}
268
  <div className="flex-1 overflow-y-auto p-4 space-y-4">
269
  {messages.length === 0 && (
270
  <p className="text-gray-500 text-center text-sm mt-10">
@@ -285,7 +340,6 @@ const Notes: React.FC = () => {
285
  : "bg-gray-700 text-gray-200 prose prose-invert max-w-none"
286
  }`}
287
  >
288
- {/* --- MARKDOWN RENDERING CHANGE IS HERE --- */}
289
  {msg.role === "assistant" ? (
290
  <ReactMarkdown remarkPlugins={[remarkGfm]}>
291
  {msg.content}
@@ -305,7 +359,6 @@ const Notes: React.FC = () => {
305
  )}
306
  </div>
307
 
308
- {/* Input */}
309
  <div className="p-4 border-t border-gray-700">
310
  <div className="flex gap-2">
311
  <input
@@ -315,12 +368,12 @@ const Notes: React.FC = () => {
315
  onKeyDown={(e) => e.key === "Enter" && handleSendMessage()}
316
  placeholder="Type your question..."
317
  className="flex-1 bg-gray-800 text-white rounded-lg px-4 py-2 border border-gray-700 focus:outline-none focus:border-blue-500"
318
- disabled={!sessionId}
319
  />
320
  <button
321
  onClick={handleSendMessage}
322
  disabled={!sessionId || isChatLoading}
323
- className="bg-blue-600 p-2 rounded-lg text-white hover:bg-blue-700 disabled:opacity-50"
324
  >
325
  <Send size={20} />
326
  </button>
 
1
  import ReactMarkdown from "react-markdown";
2
  import remarkGfm from "remark-gfm";
3
+ import React, { useEffect, useState, useRef, useCallback } from "react";
4
  import {
 
 
 
5
  Upload,
6
  Menu,
7
  X,
8
  Send,
 
9
  Loader2,
10
  FileText,
11
+ MessageSquare,
12
  } from "lucide-react";
13
  import {
14
  fetchNotes,
 
17
  createChatSession,
18
  streamChatRequest,
19
  fetchChatHistory,
20
+ fetchSessions, // ✅ Import this
21
  type Note,
22
  type ChatMessage,
23
  } from "../api/notesService";
 
29
  const [isUploading, setIsUploading] = useState(false);
30
  const fileInputRef = useRef<HTMLInputElement>(null);
31
 
32
+ // ✅ Resizable Chat State
33
+ const [chatWidth, setChatWidth] = useState(450); // Default width
34
+ const [isResizing, setIsResizing] = useState(false);
35
+
36
  // --- Data State ---
37
  const [notes, setNotes] = useState<Note[]>([]);
38
  const [currentNote, setCurrentNote] = useState<Note | null>(null);
 
49
  loadNotes();
50
  }, []);
51
 
52
+ // ✅ Handle Resizing Logic
53
+ const startResizing = useCallback(() => setIsResizing(true), []);
54
+ const stopResizing = useCallback(() => setIsResizing(false), []);
55
+
56
+ const resize = useCallback(
57
+ (mouseMoveEvent: MouseEvent) => {
58
+ if (isResizing) {
59
+ // Calculate new width based on mouse position from the right edge
60
+ const newWidth = document.body.clientWidth - mouseMoveEvent.clientX;
61
+ if (newWidth > 300 && newWidth < 800) {
62
+ setChatWidth(newWidth);
63
+ }
64
+ }
65
+ },
66
+ [isResizing]
67
+ );
68
+
69
+ useEffect(() => {
70
+ window.addEventListener("mousemove", resize);
71
+ window.addEventListener("mouseup", stopResizing);
72
+ return () => {
73
+ window.removeEventListener("mousemove", resize);
74
+ window.removeEventListener("mouseup", stopResizing);
75
+ };
76
+ }, [resize, stopResizing]);
77
+
78
  const loadNotes = async () => {
79
  try {
80
  const data = await fetchNotes();
 
84
  }
85
  };
86
 
 
87
  const handleUploadClick = () => fileInputRef.current?.click();
88
 
89
  const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
 
93
  setIsUploading(true);
94
  try {
95
  const newNote = await uploadNote(file);
96
+ setNotes([newNote, ...notes]);
97
+ handleNoteSelect(newNote);
98
  } catch (error) {
99
  console.error("Upload failed", error);
100
  alert("Failed to upload PDF");
 
103
  }
104
  };
105
 
106
+ // FIX: Load History Logic (Issue 1)
107
  const handleNoteSelect = async (note: Note) => {
108
  setCurrentNote(note);
109
+ setPdfUrl(null);
110
+ setMessages([]);
111
  setSessionId(null);
112
+ setIsChatOpen(true); // Auto open chat on select
113
 
114
+ // A. Fetch PDF Blob
115
  try {
116
  const blob = await fetchNoteBlob(note.id);
117
  const url = URL.createObjectURL(blob);
 
120
  console.error("Failed to load PDF content", error);
121
  }
122
 
123
+ // B. Check for existing sessions -> Get History OR Create New
124
  try {
125
+ const existingSessions = await fetchSessions(note.id);
 
 
 
 
 
 
126
 
127
+ if (existingSessions.length > 0) {
128
+ // Load the most recent session
129
+ const lastSession = existingSessions[0];
130
+ setSessionId(lastSession.id);
131
+
132
+ // Fetch actual messages
133
+ const history = await fetchChatHistory(lastSession.id);
134
+ // Map backend history to frontend format
135
+ const formattedHistory: ChatMessage[] = history.map((msg: any) => ({
136
+ role: msg.role,
137
+ content: msg.content,
138
+ }));
139
+ setMessages(formattedHistory);
140
+ } else {
141
+ // No session exists, create one
142
+ const session = await createChatSession(
143
+ note.id,
144
+ `Chat - ${note.filename}`
145
+ );
146
+ setSessionId(session.id);
147
+ setMessages([
148
+ {
149
+ role: "assistant",
150
+ content: `Ready to chat about ${note.filename}!`,
151
+ },
152
+ ]);
153
+ }
154
  } catch (error) {
155
  console.error("Failed to init chat session", error);
156
  }
157
  };
158
 
159
+ // FIX: Loading State Bug (Issue 4)
160
  const handleSendMessage = async () => {
161
  if (!inputMessage.trim() || !sessionId) return;
162
 
163
  const userMsg = inputMessage;
164
+ setInputMessage("");
165
 
 
166
  setMessages((prev) => [...prev, { role: "user", content: userMsg }]);
167
+ setIsChatLoading(true); // Start loading
168
 
169
+ // Placeholder
170
  setMessages((prev) => [...prev, { role: "assistant", content: "" }]);
171
 
172
+ try {
173
+ await streamChatRequest(
174
+ sessionId,
175
+ userMsg,
176
+ (chunk) => {
177
+ setMessages((prev) => {
178
+ const newArr = [...prev];
179
+ const lastIndex = newArr.length - 1;
180
+ newArr[lastIndex] = {
181
+ ...newArr[lastIndex],
182
+ content: newArr[lastIndex].content + chunk,
183
+ };
184
+ return newArr;
185
+ });
186
+ },
187
+ (err) => {
188
+ console.error("Stream error", err);
189
+ // Don't set loading false here, let finally handle it
190
+ }
191
+ );
192
+ } catch (e) {
193
+ console.error("Chat Request Error", e);
194
+ } finally {
195
+ // ✅ Ensure loading stops regardless of success/fail so button enables
196
+ setIsChatLoading(false);
197
+ }
198
  };
199
 
200
  return (
 
210
  <h3 className="text-xl font-bold text-white mb-2 border-b border-gray-700 pb-2">
211
  My Notes
212
  </h3>
 
 
213
  <input
214
  type="file"
215
  ref={fileInputRef}
 
217
  accept="application/pdf"
218
  onChange={handleFileChange}
219
  />
 
220
  <button
221
  onClick={handleUploadClick}
222
  disabled={isUploading}
 
229
  )}
230
  {isUploading ? "Uploading..." : "Upload New PDF"}
231
  </button>
 
232
  <div className="mt-4 pt-4 border-t border-gray-700 space-y-2 overflow-y-auto">
233
  <p className="text-sm text-gray-400 uppercase tracking-wider">
234
  History
 
275
  )}
276
  </header>
277
 
 
278
  <div className="flex-1 w-full h-full pt-16">
279
  {pdfUrl ? (
280
  <iframe
 
291
  </div>
292
  </div>
293
 
294
+ {/* --- Resizable Chat Panel (Issue 3) --- */}
295
+ {isChatOpen && (
296
+ // Drag Handle
297
+ <div
298
+ className="w-1.5 cursor-col-resize bg-gray-800 hover:bg-blue-500 transition-colors z-20 flex items-center justify-center"
299
+ onMouseDown={startResizing}
300
+ >
301
+ {/* Tiny indicator for grip */}
302
+ <div className="h-8 w-0.5 bg-gray-600 rounded"></div>
303
+ </div>
304
+ )}
305
+
306
  <div
307
+ style={{ width: isChatOpen ? chatWidth : 0 }}
308
+ className={`flex flex-col bg-gray-900 border-l border-gray-700 flex-shrink-0 transition-all duration-75 ease-out`}
 
309
  >
310
  {isChatOpen && (
311
  <>
312
  <header className="flex justify-between items-center p-4 border-b border-gray-700">
313
  <h3 className="text-lg font-bold text-white">AI Chat</h3>
314
+ {/* Close Button is here */}
315
  <button
316
  onClick={() => setIsChatOpen(false)}
317
  className="text-gray-400 hover:text-white"
 
320
  </button>
321
  </header>
322
 
 
323
  <div className="flex-1 overflow-y-auto p-4 space-y-4">
324
  {messages.length === 0 && (
325
  <p className="text-gray-500 text-center text-sm mt-10">
 
340
  : "bg-gray-700 text-gray-200 prose prose-invert max-w-none"
341
  }`}
342
  >
 
343
  {msg.role === "assistant" ? (
344
  <ReactMarkdown remarkPlugins={[remarkGfm]}>
345
  {msg.content}
 
359
  )}
360
  </div>
361
 
 
362
  <div className="p-4 border-t border-gray-700">
363
  <div className="flex gap-2">
364
  <input
 
368
  onKeyDown={(e) => e.key === "Enter" && handleSendMessage()}
369
  placeholder="Type your question..."
370
  className="flex-1 bg-gray-800 text-white rounded-lg px-4 py-2 border border-gray-700 focus:outline-none focus:border-blue-500"
371
+ disabled={!sessionId || isChatLoading}
372
  />
373
  <button
374
  onClick={handleSendMessage}
375
  disabled={!sessionId || isChatLoading}
376
+ className="bg-blue-600 p-2 rounded-lg text-white hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
377
  >
378
  <Send size={20} />
379
  </button>