Girish Jeswani commited on
Commit
cc28dcb
·
1 Parent(s): b2d06d9

update mobile toggle

Browse files
phd-advisor-frontend/src/components/Sidebar.js CHANGED
@@ -21,14 +21,16 @@ const Sidebar = ({
21
  onNewChat,
22
  onSignOut,
23
  authToken,
24
- onSidebarToggle // New prop to notify parent of sidebar state
 
 
25
  }) => {
26
  const [chatSessions, setChatSessions] = useState([]);
27
  const [searchTerm, setSearchTerm] = useState('');
28
  const [isLoading, setIsLoading] = useState(true);
29
  const [showUserMenu, setShowUserMenu] = useState(false);
30
  const [isCollapsed, setIsCollapsed] = useState(false);
31
- const [isCreatingNewChat, setIsCreatingNewChat] = useState(false); // Add loading state for new chat creation
32
 
33
  useEffect(() => {
34
  if (authToken) {
@@ -36,6 +38,20 @@ const Sidebar = ({
36
  }
37
  }, [authToken]);
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  // Notify parent when sidebar state changes
40
  useEffect(() => {
41
  if (onSidebarToggle) {
@@ -54,6 +70,7 @@ const Sidebar = ({
54
  }
55
  }, [currentSessionId, authToken]);
56
 
 
57
  const fetchChatSessions = async () => {
58
  try {
59
  const response = await fetch('http://localhost:8000/api/chat-sessions', {
@@ -145,159 +162,168 @@ const Sidebar = ({
145
  };
146
 
147
  return (
148
- <div className={`sidebar ${isCollapsed ? 'collapsed' : ''}`}>
149
- {/* Header */}
150
- <div className="sidebar-header">
151
- {!isCollapsed && (
152
- <>
153
- <div className="user-section">
154
- <div className="user-info">
155
- <div className="user-avatar">
156
- <User size={20} />
157
- </div>
158
- <div className="user-details">
159
- <span className="user-name">{user.firstName} {user.lastName}</span>
160
- <span className="user-email">{user.email}</span>
 
 
161
  </div>
162
- </div>
163
-
164
- <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
165
- {/* Toggle button next to user menu when expanded */}
166
- <button
167
- className="sidebar-toggle"
168
- onClick={toggleSidebar}
169
- title="Collapse sidebar"
170
- >
171
- <ChevronLeft size={16} />
172
- </button>
173
 
174
- <div className="user-menu-container">
 
175
  <button
176
- className="user-menu-button"
177
- onClick={() => setShowUserMenu(!showUserMenu)}
 
178
  >
179
- <MoreVertical size={16} />
180
  </button>
181
 
182
- {showUserMenu && (
183
- <div className="user-menu">
184
- <button className="user-menu-item">
185
- <Settings size={16} />
186
- <span>Settings</span>
187
- </button>
188
- <button className="user-menu-item sign-out" onClick={onSignOut}>
189
- <LogOut size={16} />
190
- <span>Sign Out</span>
191
- </button>
192
- </div>
193
- )}
 
 
 
 
 
 
 
 
 
194
  </div>
195
  </div>
196
- </div>
197
 
198
- <button
199
- className="new-chat-button"
200
- onClick={handleNewChat}
201
- disabled={isCreatingNewChat}
202
- >
203
- <Plus size={16} />
204
- <span>{isCreatingNewChat ? 'Creating...' : 'New Chat'}</span>
205
- </button>
206
- </>
207
- )}
208
-
209
- {isCollapsed && (
210
- <div className="collapsed-header">
211
- {/* Toggle button replaces user avatar when collapsed */}
212
- <button
213
- className="collapsed-toggle-avatar"
214
- onClick={toggleSidebar}
215
- title="Expand sidebar"
216
- >
217
- <ChevronRight size={20} />
218
- </button>
219
- <button
220
- className="collapsed-new-chat"
221
- onClick={handleNewChat}
222
- title="New Chat"
223
- disabled={isCreatingNewChat}
224
- >
225
- <Plus size={20} />
226
- </button>
227
- </div>
228
- )}
229
- </div>
230
 
231
- {/* Search - only show when expanded */}
232
- {!isCollapsed && (
233
- <div className="sidebar-search">
234
- <div className="search-container">
235
- <Search size={16} className="search-icon" />
236
- <input
237
- type="text"
238
- placeholder="Search chats..."
239
- value={searchTerm}
240
- onChange={(e) => setSearchTerm(e.target.value)}
241
- className="search-input"
242
- />
243
- </div>
 
 
 
 
 
 
 
244
  </div>
245
- )}
246
 
247
- {/* Chat Sessions */}
248
- <div className="chat-sessions">
249
- {isLoading ? (
250
- <div className="loading-sessions">
251
- <div className="loading-spinner"></div>
252
- {!isCollapsed && <span>Loading chats...</span>}
253
- </div>
254
- ) : isCreatingNewChat ? (
255
- <div className="loading-sessions">
256
- <div className="loading-spinner"></div>
257
- {!isCollapsed && <span>Creating new chat...</span>}
258
- </div>
259
- ) : filteredSessions.length === 0 ? (
260
- <div className="no-sessions">
261
- {!isCollapsed && (searchTerm ? 'No chats found' : 'No chats yet')}
262
  </div>
263
- ) : (
264
- <div className="sessions-list">
265
- {filteredSessions.map((session) => (
266
- <div
267
- key={session.id}
268
- className={`session-item ${currentSessionId === session.id ? 'active' : ''} ${isCollapsed ? 'collapsed' : ''}`}
269
- onClick={() => onSelectSession(session.id)}
270
- title={isCollapsed ? session.title : ''}
271
- >
272
- <div className="session-content">
273
- <div className="session-icon">
274
- <MessageSquare size={16} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  </div>
 
276
  {!isCollapsed && (
277
- <div className="session-details">
278
- <div className="session-title">{session.title}</div>
279
- <div className="session-meta">
280
- <span className="session-date">{formatDate(session.updated_at)}</span>
281
- <span className="session-messages">{session.message_count} messages</span>
282
- </div>
283
- </div>
284
  )}
285
  </div>
286
-
287
- {!isCollapsed && (
288
- <button
289
- className="session-menu-button"
290
- onClick={(e) => handleDeleteSession(session.id, e)}
291
- >
292
- <Trash2 size={14} />
293
- </button>
294
- )}
295
- </div>
296
- ))}
297
- </div>
298
- )}
299
  </div>
300
- </div>
 
 
 
 
 
 
 
301
  );
302
  };
303
 
 
21
  onNewChat,
22
  onSignOut,
23
  authToken,
24
+ onSidebarToggle,
25
+ isMobileOpen = false,
26
+ onMobileToggle
27
  }) => {
28
  const [chatSessions, setChatSessions] = useState([]);
29
  const [searchTerm, setSearchTerm] = useState('');
30
  const [isLoading, setIsLoading] = useState(true);
31
  const [showUserMenu, setShowUserMenu] = useState(false);
32
  const [isCollapsed, setIsCollapsed] = useState(false);
33
+ const [isCreatingNewChat, setIsCreatingNewChat] = useState(false);
34
 
35
  useEffect(() => {
36
  if (authToken) {
 
38
  }
39
  }, [authToken]);
40
 
41
+ useEffect(() => {
42
+ const handleOverlayClick = (e) => {
43
+ // Only close if clicking the overlay itself, not the sidebar
44
+ if (e.target.classList.contains('mobile-sidebar-overlay')) {
45
+ onMobileToggle(false);
46
+ }
47
+ };
48
+
49
+ if (isMobileOpen) {
50
+ document.addEventListener('click', handleOverlayClick);
51
+ return () => document.removeEventListener('click', handleOverlayClick);
52
+ }
53
+ }, [isMobileOpen, onMobileToggle]);
54
+
55
  // Notify parent when sidebar state changes
56
  useEffect(() => {
57
  if (onSidebarToggle) {
 
70
  }
71
  }, [currentSessionId, authToken]);
72
 
73
+
74
  const fetchChatSessions = async () => {
75
  try {
76
  const response = await fetch('http://localhost:8000/api/chat-sessions', {
 
162
  };
163
 
164
  return (
165
+ <>
166
+ <div className={`sidebar ${isCollapsed ? 'collapsed' : ''} ${isMobileOpen ? 'mobile-open' : ''}`}>
167
+ {/* Header */}
168
+ <div className="sidebar-header">
169
+ {!isCollapsed && (
170
+ <>
171
+ <div className="user-section">
172
+ <div className="user-info">
173
+ <div className="user-avatar">
174
+ <User size={20} />
175
+ </div>
176
+ <div className="user-details">
177
+ <span className="user-name">{user.firstName} {user.lastName}</span>
178
+ <span className="user-email">{user.email}</span>
179
+ </div>
180
  </div>
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
183
+ {/* Toggle button next to user menu when expanded */}
184
  <button
185
+ className="sidebar-toggle"
186
+ onClick={toggleSidebar}
187
+ title="Collapse sidebar"
188
  >
189
+ <ChevronLeft size={16} />
190
  </button>
191
 
192
+ <div className="user-menu-container">
193
+ <button
194
+ className="user-menu-button"
195
+ onClick={() => setShowUserMenu(!showUserMenu)}
196
+ >
197
+ <MoreVertical size={16} />
198
+ </button>
199
+
200
+ {showUserMenu && (
201
+ <div className="user-menu">
202
+ <button className="user-menu-item">
203
+ <Settings size={16} />
204
+ <span>Settings</span>
205
+ </button>
206
+ <button className="user-menu-item sign-out" onClick={onSignOut}>
207
+ <LogOut size={16} />
208
+ <span>Sign Out</span>
209
+ </button>
210
+ </div>
211
+ )}
212
+ </div>
213
  </div>
214
  </div>
 
215
 
216
+ <button
217
+ className="new-chat-button"
218
+ onClick={handleNewChat}
219
+ disabled={isCreatingNewChat}
220
+ >
221
+ <Plus size={16} />
222
+ <span>{isCreatingNewChat ? 'Creating...' : 'New Chat'}</span>
223
+ </button>
224
+ </>
225
+ )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ {isCollapsed && (
228
+ <div className="collapsed-header">
229
+ {/* Toggle button replaces user avatar when collapsed */}
230
+ <button
231
+ className="collapsed-toggle-avatar"
232
+ onClick={toggleSidebar}
233
+ title="Expand sidebar"
234
+ >
235
+ <ChevronRight size={20} />
236
+ </button>
237
+ <button
238
+ className="collapsed-new-chat"
239
+ onClick={handleNewChat}
240
+ title="New Chat"
241
+ disabled={isCreatingNewChat}
242
+ >
243
+ <Plus size={20} />
244
+ </button>
245
+ </div>
246
+ )}
247
  </div>
 
248
 
249
+ {/* Search - only show when expanded */}
250
+ {!isCollapsed && (
251
+ <div className="sidebar-search">
252
+ <div className="search-container">
253
+ <Search size={16} className="search-icon" />
254
+ <input
255
+ type="text"
256
+ placeholder="Search chats..."
257
+ value={searchTerm}
258
+ onChange={(e) => setSearchTerm(e.target.value)}
259
+ className="search-input"
260
+ />
261
+ </div>
 
 
262
  </div>
263
+ )}
264
+
265
+ {/* Chat Sessions */}
266
+ <div className="chat-sessions">
267
+ {isLoading ? (
268
+ <div className="loading-sessions">
269
+ <div className="loading-spinner"></div>
270
+ {!isCollapsed && <span>Loading chats...</span>}
271
+ </div>
272
+ ) : isCreatingNewChat ? (
273
+ <div className="loading-sessions">
274
+ <div className="loading-spinner"></div>
275
+ {!isCollapsed && <span>Creating new chat...</span>}
276
+ </div>
277
+ ) : filteredSessions.length === 0 ? (
278
+ <div className="no-sessions">
279
+ {!isCollapsed && (searchTerm ? 'No chats found' : 'No chats yet')}
280
+ </div>
281
+ ) : (
282
+ <div className="sessions-list">
283
+ {filteredSessions.map((session) => (
284
+ <div
285
+ key={session.id}
286
+ className={`session-item ${currentSessionId === session.id ? 'active' : ''} ${isCollapsed ? 'collapsed' : ''}`}
287
+ onClick={() => onSelectSession(session.id)}
288
+ title={isCollapsed ? session.title : ''}
289
+ >
290
+ <div className="session-content">
291
+ <div className="session-icon">
292
+ <MessageSquare size={16} />
293
+ </div>
294
+ {!isCollapsed && (
295
+ <div className="session-details">
296
+ <div className="session-title">{session.title}</div>
297
+ <div className="session-meta">
298
+ <span className="session-date">{formatDate(session.updated_at)}</span>
299
+ <span className="session-messages">{session.message_count} messages</span>
300
+ </div>
301
+ </div>
302
+ )}
303
  </div>
304
+
305
  {!isCollapsed && (
306
+ <button
307
+ className="session-menu-button"
308
+ onClick={(e) => handleDeleteSession(session.id, e)}
309
+ >
310
+ <Trash2 size={14} />
311
+ </button>
 
312
  )}
313
  </div>
314
+ ))}
315
+ </div>
316
+ )}
317
+ </div>
 
 
 
 
 
 
 
 
 
318
  </div>
319
+
320
+ {isMobileOpen && (
321
+ <div
322
+ className="mobile-sidebar-overlay visible"
323
+ onClick={() => onMobileToggle(false)}
324
+ />
325
+ )}
326
+ </>
327
  );
328
  };
329
 
phd-advisor-frontend/src/styles/Sidebar.css CHANGED
@@ -593,6 +593,21 @@
593
  position: relative;
594
  }
595
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
  .session-item.collapsed:hover::after {
597
  content: attr(title);
598
  position: absolute;
@@ -627,17 +642,19 @@
627
  width: 280px;
628
  transform: translateX(-100%);
629
  transition: transform 0.3s ease, width 0.3s ease;
 
630
  }
631
 
632
  .sidebar.collapsed {
633
  width: 70px;
 
634
  }
635
 
636
- .sidebar.open {
637
  transform: translateX(0);
638
  }
639
-
640
- .sidebar-toggle {
641
- display: none; /* Hide toggle on mobile, use overlay instead */
642
  }
643
  }
 
593
  position: relative;
594
  }
595
 
596
+ .mobile-sidebar-overlay {
597
+ position: fixed;
598
+ top: 0;
599
+ left: 0;
600
+ right: 0;
601
+ bottom: 0;
602
+ background: rgba(0, 0, 0, 0.5);
603
+ z-index: 150;
604
+ display: none;
605
+ }
606
+
607
+ .mobile-sidebar-overlay.visible {
608
+ display: block;
609
+ }
610
+
611
  .session-item.collapsed:hover::after {
612
  content: attr(title);
613
  position: absolute;
 
642
  width: 280px;
643
  transform: translateX(-100%);
644
  transition: transform 0.3s ease, width 0.3s ease;
645
+ z-index: 200; /* Higher z-index for mobile */
646
  }
647
 
648
  .sidebar.collapsed {
649
  width: 70px;
650
+ transform: translateX(-100%); /* Keep hidden when collapsed on mobile */
651
  }
652
 
653
+ .sidebar.mobile-open {
654
  transform: translateX(0);
655
  }
656
+
657
+ .mobile-sidebar-overlay.visible {
658
+ display: block;
659
  }
660
  }