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' ;
1111import { socket } from '../socket' ;
1212import {
1313 Loader2 ,
@@ -23,14 +23,47 @@ import Modal from './Modal';
2323import type { ModalType } from './Modal' ;
2424import StudentResponseInput from './StudentResponseInput' ;
2525
26+ interface AuthData {
27+ name : string | null ;
28+ email : string | null ;
29+ role : string | null ;
30+ }
31+
2632interface StudentViewProps {
2733 joinCode : string ;
28- auth : any ;
34+ auth : AuthData ;
2935 onJoin : ( code : string ) => void ;
3036}
3137
3238type 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+
3467export 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 ) {
0 commit comments