| import { useState, useRef, useEffect } from 'react' |
|
|
| function RenameModal({ current, onSave, onClose }) { |
| const [val, setVal] = useState(current) |
| return ( |
| <div className="modal-overlay" onClick={e => e.target === e.currentTarget && onClose()}> |
| <div className="modal-card" style={{ maxWidth: 360 }}> |
| <div className="modal-header"> |
| <span className="modal-title">Rename Chat</span> |
| <button className="modal-close" onClick={onClose}> |
| <svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> |
| </button> |
| </div> |
| <div className="modal-body"> |
| <input className="field-input" value={val} onChange={e => setVal(e.target.value)} |
| onKeyDown={e => e.key === 'Enter' && onSave(val)} autoFocus maxLength={80} /> |
| </div> |
| <div className="modal-footer"> |
| <button className="btn" onClick={onClose}>Cancel</button> |
| <button className="btn btn-primary" onClick={() => onSave(val)}>Save</button> |
| </div> |
| </div> |
| </div> |
| ) |
| } |
|
|
| export default function TopBar({ |
| onToggleSidebar, sessionTitle, user, |
| onRename, onLogin, onLogout, onFeedback, onSettings, theme, onTheme, currentSessionId, |
| }) { |
| const [dropOpen, setDropOpen] = useState(false) |
| const [renaming, setRenaming] = useState(false) |
| const dropRef = useRef(null) |
|
|
| useEffect(() => { |
| const h = e => { if (dropRef.current && !dropRef.current.contains(e.target)) setDropOpen(false) } |
| document.addEventListener('mousedown', h) |
| return () => document.removeEventListener('mousedown', h) |
| }, []) |
|
|
| const cycleTheme = () => onTheme(theme === 'dark' ? 'light' : 'dark') |
|
|
| const initial = user ? (user.username[0] || 'U').toUpperCase() : '?' |
|
|
| return ( |
| <> |
| <div className="topbar"> |
| <div className="topbar-left"> |
| {/* Sidebar toggle */} |
| <button className="icon-btn" onClick={onToggleSidebar} title="Toggle sidebar"> |
| <svg viewBox="0 0 24 24"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg> |
| </button> |
| |
| <span className="session-title-display">{sessionTitle}</span> |
| |
| {user && currentSessionId && ( |
| <button className="icon-btn" onClick={() => setRenaming(true)} title="Rename"> |
| <svg viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> |
| </button> |
| )} |
| </div> |
| |
| <div className="topbar-right"> |
| {/* Theme toggle */} |
| <button className="icon-btn" onClick={cycleTheme} title={theme === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode'} style={{ transition: 'transform 0.4s var(--spring)' }}> |
| {theme === 'dark' ? ( |
| <svg viewBox="0 0 24 24" style={{ transform: 'rotate(-20deg)' }}><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/></svg> |
| ) : ( |
| <svg viewBox="0 0 24 24" style={{ transform: 'rotate(90deg)' }}><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/></svg> |
| )} |
| </button> |
| |
| {user ? ( |
| <div className="avatar-menu" ref={dropRef}> |
| <button |
| className="avatar-btn" |
| onClick={() => setDropOpen(o => !o)} |
| title="Account" |
| style={user.avatar_url ? { backgroundImage: `url(${user.avatar_url})`, backgroundSize: 'cover', backgroundPosition: 'center', color: 'transparent' } : {}} |
| > |
| {!user.avatar_url && initial} |
| <span className="avatar-online" /> |
| </button> |
| {dropOpen && ( |
| <div className="avatar-dropdown"> |
| <div className="avd-user"> |
| <div className="avd-name">{user.username}</div> |
| <div className="avd-sub">Free Plan</div> |
| </div> |
| <div className="avd-sep" /> |
| <button className="avd-item" onClick={() => { setDropOpen(false); onSettings() }}> |
| <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"/></svg> |
| Settings |
| </button> |
| <button className="avd-item" onClick={() => { setDropOpen(false); onFeedback() }}> |
| <svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> |
| Send Feedback |
| </button> |
| <div className="avd-sep" /> |
| <button className="avd-item danger" onClick={() => { setDropOpen(false); onLogout() }}> |
| <svg viewBox="0 0 24 24"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg> |
| Sign Out |
| </button> |
| </div> |
| )} |
| </div> |
| ) : ( |
| <button className="btn btn-primary" onClick={onLogin}>Sign In</button> |
| )} |
| </div> |
| </div> |
| |
| {renaming && ( |
| <RenameModal |
| current={sessionTitle} |
| onSave={t => { onRename(t); setRenaming(false) }} |
| onClose={() => setRenaming(false)} |
| /> |
| )} |
| </> |
| ) |
| } |
|
|