Yassine Mhirsi commited on
Commit
2251e03
·
1 Parent(s): 6c8fc4b

fixed typescript adn added logout

Browse files
.cursor/rules/rules.mdc CHANGED
@@ -32,7 +32,7 @@ src/app/
32
  ## Code Patterns
33
 
34
  ### Components
35
- - Use functional components with `React.FC<PropsType>`
36
  - Always type props with interfaces or types
37
  - Use Tailwind CSS for styling (avoid custom CSS files)
38
  - Keep components presentational - pass data via props
@@ -43,11 +43,124 @@ type MyComponentProps = {
43
  count?: number;
44
  };
45
 
46
- const MyComponent: React.FC<MyComponentProps> = ({ title, count = 0 }) => {
47
  return <div>{title}: {count}</div>;
48
  };
49
  ```
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  ### API Calls
52
  - Use `api-wrapper.ts` for direct API calls
53
  - Use `useApi` hook for components needing loading/error states
@@ -93,7 +206,13 @@ import type { Debate } from '../types/debater.types.ts';
93
 
94
  ## Conventions
95
 
96
- 1. **Imports**:
 
 
 
 
 
 
97
  - Use relative paths from `src/`
98
  - **ALWAYS include explicit file extensions** in imports:
99
  - Use `.tsx` for React components (e.g., `import Component from './Component.tsx'`)
@@ -114,11 +233,7 @@ import type { Debate } from '../types/debater.types.ts';
114
  - ❌ Don't create custom CSS files - use Tailwind
115
  - ❌ Don't commit `.env*` files (except `.env.example`)
116
  - ❌ Don't skip TypeScript types
117
- - ❌ **Don't omit file extensions in imports** - Always include `.ts` or `.tsx` extensions
118
- - ❌ Bad: `import Component from './Component'`
119
- - ✅ Good: `import Component from './Component.tsx'`
120
- - ❌ Bad: `export * from './user.utils'`
121
- - ✅ Good: `export * from './user.utils.ts'`
122
 
123
  ## When Adding New Features
124
 
 
32
  ## Code Patterns
33
 
34
  ### Components
35
+ - Use functional components with plain arrow functions (avoid `React.FC` due to local React type limitations)
36
  - Always type props with interfaces or types
37
  - Use Tailwind CSS for styling (avoid custom CSS files)
38
  - Keep components presentational - pass data via props
 
43
  count?: number;
44
  };
45
 
46
+ const MyComponent = ({ title, count = 0 }: MyComponentProps) => {
47
  return <div>{title}: {count}</div>;
48
  };
49
  ```
50
 
51
+ ### Authentication & State Management
52
+ - Use `useAuth` hook for reactive authentication state instead of static `isUserRegistered()` calls
53
+ - Always use `setUserIdAndNotify()` when logging in/registering to ensure immediate UI updates
54
+ - Never call `isUserRegistered()` directly in components - use the `useAuth` hook instead
55
+ - Authentication state should be reactive across all components
56
+ - Use custom hooks for any state that needs to be shared across multiple components
57
+
58
+ ```typescript
59
+ // src/app/hooks/useAuth.ts - Reactive authentication hook
60
+ import { useState, useEffect } from 'react';
61
+ import { isUserRegistered } from '../utils/index.ts';
62
+
63
+ export const useAuth = () => {
64
+ const [isAuthenticated, setIsAuthenticated] = useState(isUserRegistered());
65
+
66
+ useEffect(() => {
67
+ const checkAuth = () => setIsAuthenticated(isUserRegistered());
68
+
69
+ // Listen for auth changes
70
+ window.addEventListener('storage', (e) => {
71
+ if (e.key === 'user_id') checkAuth();
72
+ });
73
+ window.addEventListener('authChange', checkAuth);
74
+
75
+ return () => {
76
+ window.removeEventListener('storage', checkAuth);
77
+ window.removeEventListener('authChange', checkAuth);
78
+ };
79
+ }, []);
80
+
81
+ return { isAuthenticated };
82
+ };
83
+
84
+ // Usage in components
85
+ const MyComponent = () => {
86
+ const { isAuthenticated } = useAuth();
87
+ // Component will re-render when authentication state changes
88
+ };
89
+
90
+ // When setting user authentication, use the notifying version
91
+ import { setUserIdAndNotify } from '../utils/index.ts';
92
+ setUserIdAndNotify(user.id); // Triggers immediate UI updates
93
+
94
+ // When logging out, use the notifying version
95
+ import { logoutAndNotify } from '../utils/index.ts';
96
+ logoutAndNotify(); // Clears user data and triggers immediate UI updates
97
+ ```
98
+
99
+ ### Route Protection
100
+ - Use `ProtectedRoute` component in `components/common/` for authentication-based routing
101
+ - Always type `children` as optional (`children?: any`) for route wrapper components
102
+ - Wrap protected content in React Fragment (`<>{children}</>`) when returning children
103
+
104
+ ```typescript
105
+ // src/app/components/common/ProtectedRoute.tsx
106
+ import React from 'react';
107
+ import { Navigate } from 'react-router-dom';
108
+ import { isUserRegistered } from '../../utils/index.ts';
109
+
110
+ type ProtectedRouteProps = {
111
+ children?: any; // Optional children prop for JSX content
112
+ };
113
+
114
+ const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
115
+ const isAuthenticated = isUserRegistered();
116
+
117
+ if (!isAuthenticated) {
118
+ return <Navigate to="/" replace />;
119
+ }
120
+
121
+ return <>{children}</>; // Wrap in fragment for proper React element return
122
+ };
123
+
124
+ export default ProtectedRoute;
125
+ ```
126
+
127
+ ```typescript
128
+ // Usage in App.tsx routing
129
+ <Route
130
+ path="/protected"
131
+ element={
132
+ <ProtectedRoute>
133
+ <ProtectedPage />
134
+ </ProtectedRoute>
135
+ }
136
+ />
137
+ ```
138
+
139
+ ### React & TypeScript Typing Constraints (Local Setup)
140
+
141
+ - **React component types**
142
+ - ❌ Avoid `React.FC`, `ReactNode`, and React-specific event types (`FormEvent`, `ChangeEvent`, etc.) – they are not available in this project's React type setup and will cause errors like "Namespace 'react' has no exported member 'FC'".
143
+ - ✅ Prefer plain arrow components with explicitly typed props:
144
+ - `type Props = { ... }`
145
+ - `const Component = (props: Props) => { ... }`
146
+ - For `children` props:
147
+ - **Route wrapper components** (like `ProtectedRoute`): Use `children?: any` (optional)
148
+ - **Regular components**: Use `children: any` (required) or omit if not needed
149
+
150
+ - **Hooks and generics**
151
+ - ❌ Do **not** pass generic type parameters to React hooks such as `useState` (e.g., `useState<string>()` or `useState<MyType>()`). This triggers “Expected 0 type arguments, but got 1.”
152
+ - ✅ Let hooks infer types from initial values, and when generics are needed:
153
+ - Use a type assertion on the initial state:
154
+ - `const [state, setState] = useState({ ... } as MyStateType);`
155
+ - Or, for tuple-like state:
156
+ - `const [value, setValue] = useState(() => initialValue) as [MyType, (next: MyType | ((prev: MyType) => MyType)) => void];`
157
+
158
+ - **Error boundary**
159
+ - The only class component should be `ErrorBoundary`, and it should:
160
+ - Import only `Component` from React (no `ErrorInfo`, `ReactNode`).
161
+ - Extend `Component` without generic parameters: `class ErrorBoundary extends Component { ... }`.
162
+ - Declare its `state` field explicitly with a typed object (e.g., `state = { hasError: false, error: null }`).
163
+
164
  ### API Calls
165
  - Use `api-wrapper.ts` for direct API calls
166
  - Use `useApi` hook for components needing loading/error states
 
206
 
207
  ## Conventions
208
 
209
+ 1. **Authentication**:
210
+ - Use `setUserIdAndNotify()` for login/registration (immediate UI updates)
211
+ - Use `logoutAndNotify()` for logout (immediate UI updates)
212
+ - Use `setUserId()` only for background operations or initialization
213
+ - Always use `useAuth()` hook in components instead of `isUserRegistered()`
214
+
215
+ 2. **Imports**:
216
  - Use relative paths from `src/`
217
  - **ALWAYS include explicit file extensions** in imports:
218
  - Use `.tsx` for React components (e.g., `import Component from './Component.tsx'`)
 
233
  - ❌ Don't create custom CSS files - use Tailwind
234
  - ❌ Don't commit `.env*` files (except `.env.example`)
235
  - ❌ Don't skip TypeScript types
236
+ - ❌ Don't call `isUserRegistered()` directly in components - use `useAuth()` hook instead
 
 
 
 
237
 
238
  ## When Adding New Features
239
 
.gitignore CHANGED
@@ -87,3 +87,5 @@ dist
87
  # Temporary folders
88
  tmp/
89
  temp/
 
 
 
87
  # Temporary folders
88
  tmp/
89
  temp/
90
+ QWEN.md
91
+ .qwen/rules.md
src/app/App.tsx CHANGED
@@ -4,11 +4,12 @@ import MainLayout from './layouts/MainLayout.tsx';
4
  import HomePage from './pages/HomePage.tsx';
5
  import AnalysisPage from './pages/AnalysisPage.tsx';
6
  import ChatPage from './pages/ChatPage.tsx';
7
- import { isUserRegistered } from './utils/index.ts';
 
8
 
9
- const App: React.FC = () => {
10
- // Check if user is already registered
11
- const userRegistered = isUserRegistered();
12
 
13
  return (
14
  <BrowserRouter>
@@ -17,15 +18,29 @@ const App: React.FC = () => {
17
  <Route
18
  path="/"
19
  element={
20
- userRegistered ? (
21
  <Navigate to="/analysis" replace />
22
  ) : (
23
  <HomePage />
24
  )
25
  }
26
  />
27
- <Route path="/analysis" element={<AnalysisPage />} />
28
- <Route path="/chat" element={<ChatPage />} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  <Route path="*" element={<Navigate to="/" replace />} />
30
  </Routes>
31
  </MainLayout>
 
4
  import HomePage from './pages/HomePage.tsx';
5
  import AnalysisPage from './pages/AnalysisPage.tsx';
6
  import ChatPage from './pages/ChatPage.tsx';
7
+ import ProtectedRoute from './components/common/ProtectedRoute.tsx';
8
+ import { useAuth } from './hooks/index.ts';
9
 
10
+ const App = () => {
11
+ // Use reactive authentication state
12
+ const { isAuthenticated } = useAuth();
13
 
14
  return (
15
  <BrowserRouter>
 
18
  <Route
19
  path="/"
20
  element={
21
+ isAuthenticated ? (
22
  <Navigate to="/analysis" replace />
23
  ) : (
24
  <HomePage />
25
  )
26
  }
27
  />
28
+ <Route
29
+ path="/analysis"
30
+ element={
31
+ <ProtectedRoute>
32
+ <AnalysisPage />
33
+ </ProtectedRoute>
34
+ }
35
+ />
36
+ <Route
37
+ path="/chat"
38
+ element={
39
+ <ProtectedRoute>
40
+ <ChatPage />
41
+ </ProtectedRoute>
42
+ }
43
+ />
44
  <Route path="*" element={<Navigate to="/" replace />} />
45
  </Routes>
46
  </MainLayout>
src/app/components/analysis/StanceDistributionChart.tsx CHANGED
@@ -11,7 +11,7 @@ const COLORS = {
11
  CON: '#ef4444', // red-500
12
  };
13
 
14
- const StanceDistributionChart: React.FC<StanceDistributionChartProps> = ({ stats }) => {
15
  const data = [
16
  { name: 'PRO', value: stats.pro, percentage: stats.proPercentage },
17
  { name: 'CON', value: stats.con, percentage: stats.conPercentage },
 
11
  CON: '#ef4444', // red-500
12
  };
13
 
14
+ const StanceDistributionChart = ({ stats }: StanceDistributionChartProps) => {
15
  const data = [
16
  { name: 'PRO', value: stats.pro, percentage: stats.proPercentage },
17
  { name: 'CON', value: stats.con, percentage: stats.conPercentage },
src/app/components/analysis/TimeSeriesChart.tsx CHANGED
@@ -6,7 +6,7 @@ type TimeSeriesChartProps = {
6
  data: TimeStats[];
7
  };
8
 
9
- const TimeSeriesChart: React.FC<TimeSeriesChartProps> = ({ data }) => {
10
  // Format date for display
11
  const chartData = data.map((item) => ({
12
  ...item,
 
6
  data: TimeStats[];
7
  };
8
 
9
+ const TimeSeriesChart = ({ data }: TimeSeriesChartProps) => {
10
  // Format date for display
11
  const chartData = data.map((item) => ({
12
  ...item,
src/app/components/analysis/TopicFrequencyChart.tsx CHANGED
@@ -6,7 +6,7 @@ type TopicFrequencyChartProps = {
6
  data: TopicFrequency[];
7
  };
8
 
9
- const TopicFrequencyChart: React.FC<TopicFrequencyChartProps> = ({ data }) => {
10
  // Truncate long topic names for display
11
  const chartData = data.map((item) => ({
12
  ...item,
 
6
  data: TopicFrequency[];
7
  };
8
 
9
+ const TopicFrequencyChart = ({ data }: TopicFrequencyChartProps) => {
10
  // Truncate long topic names for display
11
  const chartData = data.map((item) => ({
12
  ...item,
src/app/components/chat/ChatInput.tsx CHANGED
@@ -6,14 +6,11 @@ type ChatInputProps = {
6
  placeholder?: string;
7
  };
8
 
9
- const ChatInput: React.FC<ChatInputProps> = ({
10
- onSubmit,
11
- placeholder = 'Ask a follow-up...',
12
- }) => {
13
  const [input, setInput] = useState('');
14
  const [isRecording, setIsRecording] = useState(false);
15
 
16
- const handleSubmit = (e: React.FormEvent) => {
17
  e.preventDefault();
18
  if (input.trim()) {
19
  if (onSubmit) {
@@ -41,7 +38,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
41
  setInput('When speech to text feature ?');
42
  };
43
 
44
- const WaveAnimation: React.FC = () => {
45
  const [animationKey, setAnimationKey] = useState(0);
46
 
47
  useEffect(() => {
 
6
  placeholder?: string;
7
  };
8
 
9
+ const ChatInput = ({ onSubmit, placeholder = 'Ask a follow-up...' }: ChatInputProps) => {
 
 
 
10
  const [input, setInput] = useState('');
11
  const [isRecording, setIsRecording] = useState(false);
12
 
13
+ const handleSubmit = (e: any) => {
14
  e.preventDefault();
15
  if (input.trim()) {
16
  if (onSubmit) {
 
38
  setInput('When speech to text feature ?');
39
  };
40
 
41
+ const WaveAnimation = () => {
42
  const [animationKey, setAnimationKey] = useState(0);
43
 
44
  useEffect(() => {
src/app/components/common/ErrorBoundary.tsx CHANGED
@@ -1,29 +1,18 @@
1
- import React, { Component, ErrorInfo, ReactNode } from 'react';
2
-
3
- type Props = {
4
- children: ReactNode;
5
- };
6
 
7
  type State = {
8
  hasError: boolean;
9
  error: Error | null;
10
  };
11
 
12
- /**
13
- * Error Boundary component - catches React errors and displays fallback UI
14
- * Note: Error boundaries must be class components (React limitation)
15
- */
16
- class ErrorBoundary extends Component<Props, State> {
17
- constructor(props: Props) {
18
- super(props);
19
- this.state = { hasError: false, error: null };
20
- }
21
 
22
  static getDerivedStateFromError(error: Error): State {
23
  return { hasError: true, error };
24
  }
25
 
26
- componentDidCatch(error: Error, errorInfo: ErrorInfo) {
27
  console.error('ErrorBoundary caught an error:', error, errorInfo);
28
  }
29
 
 
1
+ import React, { Component } from 'react';
 
 
 
 
2
 
3
  type State = {
4
  hasError: boolean;
5
  error: Error | null;
6
  };
7
 
8
+ class ErrorBoundary extends Component {
9
+ state: State = { hasError: false, error: null };
 
 
 
 
 
 
 
10
 
11
  static getDerivedStateFromError(error: Error): State {
12
  return { hasError: true, error };
13
  }
14
 
15
+ componentDidCatch(error: Error, errorInfo: any) {
16
  console.error('ErrorBoundary caught an error:', error, errorInfo);
17
  }
18
 
src/app/components/common/Loading.tsx CHANGED
@@ -8,7 +8,7 @@ type LoadingProps = {
8
  /**
9
  * Loading spinner component
10
  */
11
- const Loading: React.FC<LoadingProps> = ({ size = 'md', text }) => {
12
  const sizeClasses = {
13
  sm: 'h-4 w-4',
14
  md: 'h-8 w-8',
 
8
  /**
9
  * Loading spinner component
10
  */
11
+ const Loading = ({ size = 'md', text }: LoadingProps) => {
12
  const sizeClasses = {
13
  sm: 'h-4 w-4',
14
  md: 'h-8 w-8',
src/app/components/common/ProtectedRoute.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Navigate } from 'react-router-dom';
3
+ import { isUserRegistered } from '../../utils/index.ts';
4
+
5
+ type ProtectedRouteProps = {
6
+ children?: any;
7
+ };
8
+
9
+ const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
10
+ const isAuthenticated = isUserRegistered();
11
+
12
+ if (!isAuthenticated) {
13
+ return <Navigate to="/" replace />;
14
+ }
15
+
16
+ return <>{children}</>;
17
+ };
18
+
19
+ export default ProtectedRoute;
src/app/components/common/ThemeToggle.tsx CHANGED
@@ -2,7 +2,7 @@ import React from 'react';
2
  import { Moon, Sun } from 'lucide-react';
3
  import { useTheme } from '../../hooks/useTheme.ts';
4
 
5
- const ThemeToggle: React.FC = () => {
6
  const { theme, toggleTheme } = useTheme();
7
 
8
  return (
 
2
  import { Moon, Sun } from 'lucide-react';
3
  import { useTheme } from '../../hooks/useTheme.ts';
4
 
5
+ const ThemeToggle = () => {
6
  const { theme, toggleTheme } = useTheme();
7
 
8
  return (
src/app/components/navigation/Navigation.tsx CHANGED
@@ -1,34 +1,41 @@
1
  import React from 'react';
2
  import { NavLink } from 'react-router-dom';
 
 
 
 
3
 
4
- const Navigation: React.FC = () => {
5
  return (
6
  <nav className="fixed top-4 left-1/2 transform -translate-x-1/2 z-40">
7
  <div className="flex items-center gap-2">
8
- <NavLink
9
- to="/analysis"
10
- className={({ isActive }) =>
11
- `px-4 py-2 text-sm font-medium rounded-lg border transition-all duration-200 ${
12
- isActive
13
- ? 'text-zinc-900 dark:text-white border-zinc-300 dark:border-zinc-600 bg-zinc-50 dark:bg-zinc-900'
14
- : 'text-zinc-500 dark:text-zinc-500 border-zinc-200 dark:border-zinc-700 hover:text-zinc-700 dark:hover:text-zinc-300 hover:border-zinc-300 dark:hover:border-zinc-600'
15
- }`
16
- }
17
- >
18
- Analysis
19
- </NavLink>
20
- <NavLink
21
- to="/chat"
22
- className={({ isActive }) =>
23
- `px-4 py-2 text-sm font-medium rounded-lg border transition-all duration-200 ${
24
- isActive
25
- ? 'text-zinc-900 dark:text-white border-zinc-300 dark:border-zinc-600 bg-zinc-50 dark:bg-zinc-900'
26
- : 'text-zinc-500 dark:text-zinc-500 border-zinc-200 dark:border-zinc-700 hover:text-zinc-700 dark:hover:text-zinc-300 hover:border-zinc-300 dark:hover:border-zinc-600'
27
- }`
28
- }
29
- >
30
- Chatbot
31
- </NavLink>
 
 
 
 
32
  </div>
33
  </nav>
34
  );
 
1
  import React from 'react';
2
  import { NavLink } from 'react-router-dom';
3
+ import { useAuth } from '../../hooks/index.ts';
4
+
5
+ const Navigation = () => {
6
+ const { isAuthenticated } = useAuth();
7
 
 
8
  return (
9
  <nav className="fixed top-4 left-1/2 transform -translate-x-1/2 z-40">
10
  <div className="flex items-center gap-2">
11
+ {isAuthenticated && (
12
+ <>
13
+ <NavLink
14
+ to="/analysis"
15
+ className={({ isActive }) =>
16
+ `px-4 py-2 text-sm font-medium rounded-lg border transition-all duration-200 ${
17
+ isActive
18
+ ? 'text-zinc-900 dark:text-white border-zinc-300 dark:border-zinc-600 bg-zinc-50 dark:bg-zinc-900'
19
+ : 'text-zinc-500 dark:text-zinc-500 border-zinc-200 dark:border-zinc-700 hover:text-zinc-700 dark:hover:text-zinc-300 hover:border-zinc-300 dark:hover:border-zinc-600'
20
+ }`
21
+ }
22
+ >
23
+ Analysis
24
+ </NavLink>
25
+ <NavLink
26
+ to="/chat"
27
+ className={({ isActive }) =>
28
+ `px-4 py-2 text-sm font-medium rounded-lg border transition-all duration-200 ${
29
+ isActive
30
+ ? 'text-zinc-900 dark:text-white border-zinc-300 dark:border-zinc-600 bg-zinc-50 dark:bg-zinc-900'
31
+ : 'text-zinc-500 dark:text-zinc-500 border-zinc-200 dark:border-zinc-700 hover:text-zinc-700 dark:hover:text-zinc-300 hover:border-zinc-300 dark:hover:border-zinc-600'
32
+ }`
33
+ }
34
+ >
35
+ Chatbot
36
+ </NavLink>
37
+ </>
38
+ )}
39
  </div>
40
  </nav>
41
  );
src/app/hooks/index.ts CHANGED
@@ -5,4 +5,5 @@
5
  export { default as useApi } from './useApi.ts';
6
  export { useApi as useApiHook } from './useApi.ts';
7
  export { useTheme } from './useTheme.ts';
 
8
 
 
5
  export { default as useApi } from './useApi.ts';
6
  export { useApi as useApiHook } from './useApi.ts';
7
  export { useTheme } from './useTheme.ts';
8
+ export { useAuth } from './useAuth.ts';
9
 
src/app/hooks/useApi.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { useState, useEffect } from 'react';
2
- import api from '../services/api-wrapper';
3
  import type { ApiError } from '../types/api.types';
4
 
5
  type UseApiState<T> = {
@@ -26,11 +26,11 @@ export function useApi<T>(
26
  url: string,
27
  options: UseApiOptions = { immediate: true }
28
  ): UseApiState<T> & { execute: () => Promise<void>; refetch: () => Promise<void> } {
29
- const [state, setState] = useState<UseApiState<T>>({
30
  data: null,
31
  loading: options.immediate ?? true,
32
  error: null,
33
- });
34
 
35
  const execute = async () => {
36
  setState((prev) => ({ ...prev, loading: true, error: null }));
 
1
  import { useState, useEffect } from 'react';
2
+ import api from '../services/api-wrapper.ts';
3
  import type { ApiError } from '../types/api.types';
4
 
5
  type UseApiState<T> = {
 
26
  url: string,
27
  options: UseApiOptions = { immediate: true }
28
  ): UseApiState<T> & { execute: () => Promise<void>; refetch: () => Promise<void> } {
29
+ const [state, setState] = useState({
30
  data: null,
31
  loading: options.immediate ?? true,
32
  error: null,
33
+ } as UseApiState<T>);
34
 
35
  const execute = async () => {
36
  setState((prev) => ({ ...prev, loading: true, error: null }));
src/app/hooks/useAuth.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+ import { isUserRegistered } from '../utils/index.ts';
3
+
4
+ /**
5
+ * Custom hook for reactive authentication state
6
+ * Provides current authentication status and updates when it changes
7
+ */
8
+ export const useAuth = () => {
9
+ const [isAuthenticated, setIsAuthenticated] = useState(isUserRegistered());
10
+
11
+ useEffect(() => {
12
+ // Check authentication status on mount and when storage changes
13
+ const checkAuth = () => {
14
+ setIsAuthenticated(isUserRegistered());
15
+ };
16
+
17
+ // Listen for storage changes (when user_id is set/cleared)
18
+ const handleStorageChange = (e: StorageEvent) => {
19
+ if (e.key === 'user_id') {
20
+ checkAuth();
21
+ }
22
+ };
23
+
24
+ // Listen for custom auth events (for immediate updates after login/logout)
25
+ const handleAuthChange = () => {
26
+ checkAuth();
27
+ };
28
+
29
+ window.addEventListener('storage', handleStorageChange);
30
+ window.addEventListener('authChange', handleAuthChange);
31
+
32
+ // Initial check
33
+ checkAuth();
34
+
35
+ return () => {
36
+ window.removeEventListener('storage', handleStorageChange);
37
+ window.removeEventListener('authChange', handleAuthChange);
38
+ };
39
+ }, []);
40
+
41
+ return { isAuthenticated };
42
+ };
src/app/hooks/useTheme.ts CHANGED
@@ -5,14 +5,14 @@ type Theme = 'light' | 'dark';
5
  const THEME_STORAGE_KEY = 'app-theme';
6
 
7
  export const useTheme = () => {
8
- const [theme, setTheme] = useState<Theme>(() => {
9
  // Get theme from localStorage or default to 'dark'
10
  if (typeof window !== 'undefined') {
11
  const stored = localStorage.getItem(THEME_STORAGE_KEY) as Theme | null;
12
  return stored || 'dark';
13
  }
14
  return 'dark';
15
- });
16
 
17
  useEffect(() => {
18
  // Apply theme to document
 
5
  const THEME_STORAGE_KEY = 'app-theme';
6
 
7
  export const useTheme = () => {
8
+ const [theme, setTheme] = useState(() => {
9
  // Get theme from localStorage or default to 'dark'
10
  if (typeof window !== 'undefined') {
11
  const stored = localStorage.getItem(THEME_STORAGE_KEY) as Theme | null;
12
  return stored || 'dark';
13
  }
14
  return 'dark';
15
+ }) as [Theme, (value: Theme | ((prev: Theme) => Theme)) => void];
16
 
17
  useEffect(() => {
18
  // Apply theme to document
src/app/layouts/MainLayout.tsx CHANGED
@@ -1,14 +1,24 @@
1
  import React, { useEffect } from 'react';
 
2
  import { useTheme } from '../hooks/useTheme.ts';
 
 
3
  import ThemeToggle from '../components/common/ThemeToggle.tsx';
4
  import Navigation from '../components/navigation/Navigation.tsx';
5
 
6
  type MainLayoutProps = {
7
- children: React.ReactNode;
8
  };
9
 
10
- const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
11
  const { theme } = useTheme();
 
 
 
 
 
 
 
12
 
13
  useEffect(() => {
14
  // Ensure theme is applied on mount
@@ -23,7 +33,15 @@ const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
23
  return (
24
  <div className="min-h-screen bg-white dark:bg-black transition-colors duration-200 relative">
25
  <Navigation />
26
- <div className="fixed top-4 right-4 z-50">
 
 
 
 
 
 
 
 
27
  <ThemeToggle />
28
  </div>
29
  {children}
 
1
  import React, { useEffect } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
  import { useTheme } from '../hooks/useTheme.ts';
4
+ import { useAuth } from '../hooks/index.ts';
5
+ import { logoutAndNotify } from '../utils/index.ts';
6
  import ThemeToggle from '../components/common/ThemeToggle.tsx';
7
  import Navigation from '../components/navigation/Navigation.tsx';
8
 
9
  type MainLayoutProps = {
10
+ children?: any;
11
  };
12
 
13
+ const MainLayout = ({ children }: MainLayoutProps) => {
14
  const { theme } = useTheme();
15
+ const { isAuthenticated } = useAuth();
16
+ const navigate = useNavigate();
17
+
18
+ const handleLogout = () => {
19
+ logoutAndNotify();
20
+ navigate('/');
21
+ };
22
 
23
  useEffect(() => {
24
  // Ensure theme is applied on mount
 
33
  return (
34
  <div className="min-h-screen bg-white dark:bg-black transition-colors duration-200 relative">
35
  <Navigation />
36
+ <div className="fixed top-4 right-4 z-50 flex items-center gap-2">
37
+ {isAuthenticated && (
38
+ <button
39
+ onClick={handleLogout}
40
+ className="px-3 py-2 text-sm font-medium rounded-lg bg-red-500 hover:bg-red-600 text-white transition-all duration-200 hover:scale-105"
41
+ >
42
+ Logout
43
+ </button>
44
+ )}
45
  <ThemeToggle />
46
  </div>
47
  {children}
src/app/pages/AnalysisPage.tsx CHANGED
@@ -17,19 +17,19 @@ import TopicFrequencyChart from '../components/analysis/TopicFrequencyChart.tsx'
17
  import TimeSeriesChart from '../components/analysis/TimeSeriesChart.tsx';
18
  import Loading from '../components/common/Loading.tsx';
19
 
20
- const AnalysisPage: React.FC = () => {
21
- const [selectedFile, setSelectedFile] = useState<File | null>(null);
22
- const [headers, setHeaders] = useState<string[]>(['id', 'argument']);
23
- const [rows, setRows] = useState<CsvRow[]>([]);
24
- const [error, setError] = useState<string | null>(null);
25
- const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false);
26
- const [analysisResults, setAnalysisResults] = useState<AnalysisResult[]>([]);
27
 
28
  // User's historical analysis data
29
- const [userAnalysisData, setUserAnalysisData] = useState<AnalysisResult[]>([]);
30
- const [isLoadingStats, setIsLoadingStats] = useState<boolean>(true);
31
- const [isRefreshingStats, setIsRefreshingStats] = useState<boolean>(false);
32
- const [statsError, setStatsError] = useState<string | null>(null);
33
 
34
  const fileName = useMemo(
35
  () => selectedFile?.name ?? 'Select a CSV file with arguments',
@@ -101,7 +101,7 @@ const AnalysisPage: React.FC = () => {
101
  [userAnalysisData]
102
  );
103
 
104
- const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
105
  const file = event.target.files?.[0] ?? null;
106
  setSelectedFile(file);
107
  setError(null);
 
17
  import TimeSeriesChart from '../components/analysis/TimeSeriesChart.tsx';
18
  import Loading from '../components/common/Loading.tsx';
19
 
20
+ const AnalysisPage = () => {
21
+ const [selectedFile, setSelectedFile] = useState(null as File | null);
22
+ const [headers, setHeaders] = useState(['id', 'argument'] as string[]);
23
+ const [rows, setRows] = useState([] as CsvRow[]);
24
+ const [error, setError] = useState(null as string | null);
25
+ const [isAnalyzing, setIsAnalyzing] = useState(false);
26
+ const [analysisResults, setAnalysisResults] = useState([] as AnalysisResult[]);
27
 
28
  // User's historical analysis data
29
+ const [userAnalysisData, setUserAnalysisData] = useState([] as AnalysisResult[]);
30
+ const [isLoadingStats, setIsLoadingStats] = useState(true);
31
+ const [isRefreshingStats, setIsRefreshingStats] = useState(false);
32
+ const [statsError, setStatsError] = useState(null as string | null);
33
 
34
  const fileName = useMemo(
35
  () => selectedFile?.name ?? 'Select a CSV file with arguments',
 
101
  [userAnalysisData]
102
  );
103
 
104
+ const handleFileChange = async (event: any) => {
105
  const file = event.target.files?.[0] ?? null;
106
  setSelectedFile(file);
107
  setError(null);
src/app/pages/ChatPage.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import React from 'react';
2
  import ChatInput from '../components/chat/ChatInput.tsx';
3
 
4
- const ChatPage: React.FC = () => {
5
  const handleMessageSubmit = (message: string) => {
6
  console.log('Message submitted:', message);
7
  // TODO: Implement chat message handling
 
1
  import React from 'react';
2
  import ChatInput from '../components/chat/ChatInput.tsx';
3
 
4
+ const ChatPage = () => {
5
  const handleMessageSubmit = (message: string) => {
6
  console.log('Message submitted:', message);
7
  // TODO: Implement chat message handling
src/app/pages/HomePage.tsx CHANGED
@@ -1,16 +1,16 @@
1
  import React, { useState } from 'react';
2
  import { useNavigate } from 'react-router-dom';
3
  import { registerUser } from '../services/user.service.ts';
4
- import { getOrCreateUniqueId, setUserId, formatError } from '../utils/index.ts';
5
  import Loading from '../components/common/Loading.tsx';
6
 
7
- const HomePage: React.FC = () => {
8
- const [name, setName] = useState<string>('');
9
- const [isLoading, setIsLoading] = useState<boolean>(false);
10
- const [error, setError] = useState<string | null>(null);
11
  const navigate = useNavigate();
12
 
13
- const handleSubmit = async (e: React.FormEvent) => {
14
  e.preventDefault();
15
  await registerAndNavigate(name.trim() || null);
16
  };
@@ -30,8 +30,8 @@ const HomePage: React.FC = () => {
30
  name: userName,
31
  });
32
 
33
- // Store user_id for future API calls
34
- setUserId(user.id);
35
 
36
  // Navigate to analysis page
37
  navigate('/analysis');
 
1
  import React, { useState } from 'react';
2
  import { useNavigate } from 'react-router-dom';
3
  import { registerUser } from '../services/user.service.ts';
4
+ import { getOrCreateUniqueId, setUserIdAndNotify, formatError } from '../utils/index.ts';
5
  import Loading from '../components/common/Loading.tsx';
6
 
7
+ const HomePage = () => {
8
+ const [name, setName] = useState('');
9
+ const [isLoading, setIsLoading] = useState(false);
10
+ const [error, setError] = useState(null as string | null);
11
  const navigate = useNavigate();
12
 
13
+ const handleSubmit = async (e: any) => {
14
  e.preventDefault();
15
  await registerAndNavigate(name.trim() || null);
16
  };
 
30
  name: userName,
31
  });
32
 
33
+ // Store user_id for future API calls and notify components of auth change
34
+ setUserIdAndNotify(user.id);
35
 
36
  // Navigate to analysis page
37
  navigate('/analysis');
src/app/utils/user.utils.ts CHANGED
@@ -34,6 +34,26 @@ export function setUserId(userId: string): void {
34
  localStorage.setItem(USER_ID_KEY, userId);
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  /**
38
  * Clear user data from localStorage
39
  */
 
34
  localStorage.setItem(USER_ID_KEY, userId);
35
  }
36
 
37
+ /**
38
+ * Set user ID and notify components of authentication change
39
+ * Use this when logging in or registering to ensure immediate UI updates
40
+ */
41
+ export function setUserIdAndNotify(userId: string): void {
42
+ setUserId(userId);
43
+ // Dispatch custom event to immediately update all components using useAuth hook
44
+ window.dispatchEvent(new CustomEvent('authChange'));
45
+ }
46
+
47
+ /**
48
+ * Logout user by clearing data and notifying components
49
+ * Use this when logging out to ensure immediate UI updates
50
+ */
51
+ export function logoutAndNotify(): void {
52
+ clearUserData();
53
+ // Dispatch custom event to immediately update all components using useAuth hook
54
+ window.dispatchEvent(new CustomEvent('authChange'));
55
+ }
56
+
57
  /**
58
  * Clear user data from localStorage
59
  */