Skip to content

Commit 1633a81

Browse files
feat: linting issues and code quality
1 parent 3e5837c commit 1633a81

File tree

5 files changed

+180
-84
lines changed

5 files changed

+180
-84
lines changed

packages/client/src/App.tsx

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* the Free Software Foundation, either version 3 of the License, or
88
* (at your option) any later version.
99
*/
10-
import { useState, useEffect } from 'react';
10+
import { useState, useEffect, useCallback } from 'react';
1111
import { socket } from './socket';
1212
import StudentView from './components/StudentView';
1313
import TeacherView from './components/TeacherView';
@@ -64,25 +64,39 @@ function App() {
6464
}
6565
};
6666

67+
const checkTour = useCallback(() => {
68+
const tourDone = localStorage.getItem('thoughtswap_tour_completed');
69+
if (!tourDone && authState.isLoggedIn) {
70+
setShowTour(true);
71+
}
72+
}, [authState.isLoggedIn]);
73+
6774
useEffect(() => {
68-
if (window.location.pathname === '/auth/success') {
69-
const params = new URLSearchParams(window.location.search);
70-
const name = params.get('name');
71-
const role = params.get('role') as UserRole;
72-
const email = params.get('email');
73-
74-
if (name && role && email) {
75-
updateAuth({
76-
isLoggedIn: true,
77-
name: decodeURIComponent(name),
78-
email: decodeURIComponent(email),
79-
role: role,
80-
expiry: Date.now() + SESSION_DURATION,
81-
});
75+
// Only run auth success handling on first mount
76+
const handleAuthSuccess = () => {
77+
if (window.location.pathname === '/auth/success') {
78+
const params = new URLSearchParams(window.location.search);
79+
const name = params.get('name');
80+
const role = params.get('role') as UserRole;
81+
const email = params.get('email');
82+
83+
if (name && role && email) {
84+
updateAuth({
85+
isLoggedIn: true,
86+
name: decodeURIComponent(name),
87+
email: decodeURIComponent(email),
88+
role: role,
89+
expiry: Date.now() + SESSION_DURATION,
90+
});
91+
}
92+
window.history.replaceState({}, document.title, '/');
8293
}
83-
window.history.replaceState({}, document.title, '/');
84-
}
94+
};
8595

96+
handleAuthSuccess();
97+
}, []);
98+
99+
useEffect(() => {
86100
const handleAuthError = () => {
87101
setAuthErrorModal(true);
88102
updateAuth({ isLoggedIn: false, name: null, email: null, role: null });
@@ -111,7 +125,7 @@ function App() {
111125
socket.off('AUTH_ERROR', handleAuthError);
112126
socket.off('CONSENT_STATUS', handleConsentStatus);
113127
};
114-
}, [authState.role]);
128+
}, [authState.role, checkTour]);
115129

116130
useEffect(() => {
117131
if (authState.isLoggedIn && !socket.connected) {
@@ -124,13 +138,6 @@ function App() {
124138
}
125139
}, [authState]);
126140

127-
const checkTour = () => {
128-
const tourDone = localStorage.getItem('thoughtswap_tour_completed');
129-
if (!tourDone && authState.isLoggedIn) {
130-
setShowTour(true);
131-
}
132-
};
133-
134141
const handleConsentResponse = (gaveConsent: boolean) => {
135142
socket.emit('UPDATE_CONSENT', { consentGiven: gaveConsent });
136143
setShowConsentModal(false);
@@ -184,7 +191,7 @@ function App() {
184191

185192
// Handle tab/window close
186193
useEffect(() => {
187-
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
194+
const handleBeforeUnload = () => {
188195
if (authState.role === 'TEACHER' && authState.isLoggedIn) {
189196
const activeJoinCode = localStorage.getItem('thoughtswap_joinCode');
190197
const activeTeacher = localStorage.getItem('thoughtswap_teacher_active');

packages/client/src/components/AdminView.tsx

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,42 @@ import {
2222
} from 'lucide-react';
2323
import Modal from './Modal';
2424

25+
interface Session {
26+
id: string;
27+
joinCode: string;
28+
teacherName: string;
29+
createdAt: string;
30+
participantCount: number;
31+
}
32+
33+
interface Thought {
34+
id: string;
35+
content: string;
36+
authorName: string;
37+
sessionId: string;
38+
}
39+
40+
interface Swap {
41+
id: string;
42+
fromUser: string;
43+
toUser: string;
44+
thoughtContent: string;
45+
timestamp: string;
46+
}
47+
48+
interface Log {
49+
id: string;
50+
timestamp: string;
51+
action: string;
52+
userId: string;
53+
details: string;
54+
}
55+
2556
interface AdminData {
26-
sessions: any[];
27-
thoughts: any[];
28-
swaps: any[];
29-
logs: any[];
57+
sessions: Session[];
58+
thoughts: Thought[];
59+
swaps: Swap[];
60+
logs: Log[];
3061
stats: {
3162
totalConsented: number;
3263
totalUsers: number;
@@ -304,7 +335,7 @@ export default function AdminView({ onExit }: { onExit: () => void }) {
304335
Active Classrooms
305336
</h2>
306337
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
307-
{data.sessions.map((session: any) => (
338+
{data.sessions.map((session: Session) => (
308339
<div
309340
key={session.id}
310341
className="bg-slate-900 border border-slate-800 rounded-lg p-5 hover:border-indigo-500/50 transition-colors"
@@ -363,7 +394,7 @@ export default function AdminView({ onExit }: { onExit: () => void }) {
363394
</div>
364395

365396
<div className="grid gap-4">
366-
{data.thoughts.map((thought: any) => (
397+
{data.thoughts.map((thought: Thought) => (
367398
<div
368399
key={thought.id}
369400
className="bg-slate-900 border border-slate-800 p-6 rounded-lg hover:border-indigo-500/30 transition-colors"
@@ -416,7 +447,7 @@ export default function AdminView({ onExit }: { onExit: () => void }) {
416447
</div>
417448

418449
<div className="grid gap-4">
419-
{data.swaps.map((swap: any) => (
450+
{data.swaps.map((swap: Swap) => (
420451
<div
421452
key={swap.id}
422453
className="bg-slate-900 border border-slate-800 p-4 rounded-lg hover:border-indigo-500/30 transition-colors"
@@ -480,7 +511,7 @@ export default function AdminView({ onExit }: { onExit: () => void }) {
480511
</tr>
481512
</thead>
482513
<tbody className="divide-y divide-slate-800">
483-
{data.logs.map((log: any) => (
514+
{data.logs.map((log: Log) => (
484515
<tr
485516
key={log.id}
486517
className="hover:bg-slate-900/50 transition-colors"

packages/client/src/components/StudentView.tsx

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* the Free Software Foundation, either version 3 of the License, or
88
* (at your option) any later version.
99
*/
10-
import React, { useState, useEffect } from 'react';
10+
import React, { useState, useEffect, useCallback } from 'react';
1111
import { socket } from '../socket';
1212
import {
1313
Loader2,
@@ -23,14 +23,47 @@ import Modal from './Modal';
2323
import type { ModalType } from './Modal';
2424
import StudentResponseInput from './StudentResponseInput';
2525

26+
interface AuthData {
27+
name: string | null;
28+
email: string | null;
29+
role: string | null;
30+
}
31+
2632
interface StudentViewProps {
2733
joinCode: string;
28-
auth: any;
34+
auth: AuthData;
2935
onJoin: (code: string) => void;
3036
}
3137

3238
type Status = 'IDLE' | 'JOINED' | 'ANSWERING' | 'SUBMITTED' | 'DISCUSSING';
3339

40+
interface NewPromptData {
41+
content: string;
42+
promptUseId: string;
43+
type?: 'TEXT' | 'MC' | 'SCALE';
44+
options?: string[];
45+
}
46+
47+
interface ReceiveSwapData {
48+
content: string;
49+
}
50+
51+
interface ThoughtDeletedData {
52+
message: string;
53+
}
54+
55+
interface SessionEndedData {
56+
surveyLink?: string;
57+
}
58+
59+
interface RestoreStateData {
60+
prompt: string;
61+
promptUseId: string;
62+
type?: 'TEXT' | 'MC' | 'SCALE';
63+
options?: string[];
64+
status: Status;
65+
}
66+
3467
export default function StudentView({ joinCode, auth, onJoin }: StudentViewProps) {
3568
const [status, setStatus] = useState<Status>('IDLE');
3669
const [inputCode, setInputCode] = useState(joinCode);
@@ -55,16 +88,31 @@ export default function StudentView({ joinCode, auth, onJoin }: StudentViewProps
5588
children?: React.ReactNode;
5689
}>({ isOpen: false, type: 'info', title: '', message: '' });
5790

91+
const sendNotification = useCallback(
92+
(title: string, body: string) => {
93+
if (notificationsEnabled && document.hidden) {
94+
new Notification(title, { body, icon: '/vite.svg' });
95+
}
96+
},
97+
[notificationsEnabled]
98+
);
99+
100+
const requestNotificationPermission = () => {
101+
if (!('Notification' in window)) return;
102+
Notification.requestPermission().then((permission) => {
103+
setNotificationsEnabled(permission === 'granted');
104+
});
105+
};
106+
58107
useEffect(() => {
59108
const storedJoinCode = localStorage.getItem('thoughtswap_joinCode');
60-
if (storedJoinCode && status === 'IDLE') {
61-
setInputCode(storedJoinCode);
109+
if (storedJoinCode && status === 'IDLE' && inputCode !== storedJoinCode) {
62110
onJoin(storedJoinCode);
63111
if (!socket.auth) socket.auth = { name: auth.name, role: auth.role, email: auth.email };
64112
if (!socket.connected) socket.connect();
65113
socket.emit('JOIN_ROOM', { joinCode: storedJoinCode });
66114
}
67-
}, [auth, status]);
115+
}, [auth, status, onJoin, inputCode]);
68116

69117
useEffect(() => {
70118
if (auth && !socket.auth)
@@ -84,15 +132,15 @@ export default function StudentView({ joinCode, auth, onJoin }: StudentViewProps
84132
}
85133
};
86134

87-
const handleRestore = (data: any) => {
135+
const handleRestore = (data: RestoreStateData) => {
88136
setPrompt(data.prompt);
89137
setPromptUseId(data.promptUseId);
90138
setPromptType(data.type || 'TEXT');
91139
setPromptOptions(data.options || []);
92140
setStatus(data.status);
93141
};
94142

95-
const handleNewPrompt = (data: any) => {
143+
const handleNewPrompt = (data: NewPromptData) => {
96144
setPrompt(data.content);
97145
setPromptUseId(data.promptUseId);
98146
setPromptType(data.type || 'TEXT');
@@ -103,7 +151,7 @@ export default function StudentView({ joinCode, auth, onJoin }: StudentViewProps
103151
sendNotification('New Prompt!', 'The teacher has sent a new prompt.');
104152
};
105153

106-
const handleReceiveSwap = (data: { content: string }) => {
154+
const handleReceiveSwap = (data: ReceiveSwapData) => {
107155
setSwappedThought(data.content);
108156
setStatus('DISCUSSING');
109157
sendNotification(
@@ -112,7 +160,7 @@ export default function StudentView({ joinCode, auth, onJoin }: StudentViewProps
112160
);
113161
};
114162

115-
const handleThoughtDeleted = (data: { message: string }) => {
163+
const handleThoughtDeleted = (data: ThoughtDeletedData) => {
116164
setModal({
117165
isOpen: true,
118166
type: 'warning',
@@ -123,7 +171,7 @@ export default function StudentView({ joinCode, auth, onJoin }: StudentViewProps
123171
setResponseInput('');
124172
};
125173

126-
const handleSessionEnded = (data: { surveyLink?: string }) => {
174+
const handleSessionEnded = (data: SessionEndedData) => {
127175
setStatus('IDLE');
128176
setInputCode('');
129177
setPrompt('');
@@ -180,20 +228,7 @@ export default function StudentView({ joinCode, auth, onJoin }: StudentViewProps
180228
socket.off('SESSION_ENDED', handleSessionEnded);
181229
socket.off('RESET_CLIENT_STATE', handleResetState);
182230
};
183-
}, [status, auth]);
184-
185-
const requestNotificationPermission = () => {
186-
if (!('Notification' in window)) return;
187-
Notification.requestPermission().then((permission) => {
188-
setNotificationsEnabled(permission === 'granted');
189-
});
190-
};
191-
192-
const sendNotification = (title: string, body: string) => {
193-
if (notificationsEnabled && document.hidden) {
194-
new Notification(title, { body, icon: '/vite.svg' });
195-
}
196-
};
231+
}, [status, auth, sendNotification]);
197232

198233
const handleJoinClick = () => {
199234
if (inputCode.length > 0) {

packages/client/src/components/TeacherPromptBank.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ export default function TeacherPromptBank({
4040
isIdle,
4141
onSaveNew,
4242
}: TeacherPromptBankProps) {
43-
if (!isOpen) return null;
44-
4543
// Local state for the "Quick Create" form in idle mode
4644
const [newContent, setNewContent] = useState('');
4745
const [newType, setNewType] = useState<'TEXT' | 'MC' | 'SCALE'>('TEXT');
4846
const [newOptions, setNewOptions] = useState(['', '']);
4947

48+
if (!isOpen) return null;
49+
5050
const handleSave = () => {
5151
if (!newContent.trim()) return;
5252
if (onSaveNew) {

0 commit comments

Comments
 (0)