From 300cf5d869006080a9527d16e7072066cb61fbe5 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Sun, 22 Mar 2026 14:49:35 -0400 Subject: [PATCH] v0.11.21 bug fixes --- backend/package.json | 2 +- build.sh | 2 +- frontend/package.json | 2 +- frontend/src/components/ChatWindow.jsx | 4 +- frontend/src/components/MessageInput.jsx | 4 +- frontend/src/pages/Chat.jsx | 105 ++++++++++++----------- 6 files changed, 65 insertions(+), 54 deletions(-) diff --git a/backend/package.json b/backend/package.json index 1c168e0..1f51774 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.11.20", + "version": "0.11.21", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index 15c8ddb..4002e60 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.11.20}" +VERSION="${1:-0.11.21}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index 4f13189..604d199 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.11.20", + "version": "0.11.21", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/ChatWindow.jsx b/frontend/src/components/ChatWindow.jsx index 340ca0d..5b7836a 100644 --- a/frontend/src/components/ChatWindow.jsx +++ b/frontend/src/components/ChatWindow.jsx @@ -14,7 +14,7 @@ function nameToColor(name) { return AVATAR_COLORS[(name || '').charCodeAt(0) % AVATAR_COLORS.length]; } -export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMessage, onMessageDeleted, onlineUserIds = new Set() }) { +export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMessage, onMessageDeleted, onHasTextChange, onlineUserIds = new Set() }) { const { user: currentUser } = useAuth(); const { socket } = useSocket(); const { toast } = useToast(); @@ -330,7 +330,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMess This channel is read-only ) : ( - setReplyTo(null)} onTyping={() => {}} /> + setReplyTo(null)} onTyping={() => {}} onTextChange={val => onHasTextChange?.(!!val.trim())} /> )} {showInfo && ( diff --git a/frontend/src/components/MessageInput.jsx b/frontend/src/components/MessageInput.jsx index 0c456ef..dd0baba 100644 --- a/frontend/src/components/MessageInput.jsx +++ b/frontend/src/components/MessageInput.jsx @@ -12,7 +12,7 @@ function isEmojiOnly(str) { return emojiRegex.test(str.trim()); } -export default function MessageInput({ group, replyTo, onCancelReply, onSend, onTyping, onlineUserIds = new Set() }) { +export default function MessageInput({ group, replyTo, onCancelReply, onSend, onTyping, onTextChange, onlineUserIds = new Set() }) { const [text, setText] = useState(''); const [imageFile, setImageFile] = useState(null); const [imagePreview, setImagePreview] = useState(null); @@ -89,6 +89,7 @@ export default function MessageInput({ group, replyTo, onCancelReply, onSend, on const val = e.target.value; setText(val); handleTypingChange(val); + onTextChange?.(val); const el = e.target; el.style.height = 'auto'; @@ -157,6 +158,7 @@ export default function MessageInput({ group, replyTo, onCancelReply, onSend, on setImagePreview(null); wasTyping.current = false; onTyping(false); + onTextChange?.(''); if (inputRef.current) { inputRef.current.style.height = 'auto'; inputRef.current.style.overflowY = 'hidden'; diff --git a/frontend/src/pages/Chat.jsx b/frontend/src/pages/Chat.jsx index b0c15dc..05bda75 100644 --- a/frontend/src/pages/Chat.jsx +++ b/frontend/src/pages/Chat.jsx @@ -36,6 +36,7 @@ export default function Chat() { const [groups, setGroups] = useState({ publicGroups: [], privateGroups: [] }); const [onlineUserIds, setOnlineUserIds] = useState(new Set()); const [activeGroupId, setActiveGroupId] = useState(null); + const [chatHasText, setChatHasText] = useState(false); const [notifications, setNotifications] = useState([]); const [unreadGroups, setUnreadGroups] = useState(new Map()); const [modal, setModal] = useState(null); // 'profile' | 'users' | 'settings' | 'newchat' | 'help' | 'groupmanager' @@ -309,6 +310,12 @@ export default function Chat() { }, [socket, toast, activeGroupId, user, isMobile, loadGroups]); const selectGroup = (id) => { + // Warn if there's unsaved text in the message input and the user is switching conversations + if (chatHasText && id !== activeGroupId) { + const ok = window.confirm('You have unsaved text in the message box.\n\nContinue to discard it and open the new conversation, or Cancel to stay.'); + if (!ok) return; + setChatHasText(false); + } setActiveGroupId(id); if (isMobile) { setShowSidebar(false); @@ -366,14 +373,14 @@ export default function Chat() { setDrawerOpen(false)} - onMessages={() => { setDrawerOpen(false); setPage('chat'); }} - onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} - onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} - onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} + onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }} + onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }} + onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }} - onUsers={() => { setDrawerOpen(false); setPage('users'); }} - onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} + onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} + onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} features={features} currentPage={page} isMobile={isMobile} /> {modal === 'profile' && setModal(null)} />} {modal === 'settings' && setModal(null)} onFeaturesChanged={setFeatures} />} @@ -393,24 +400,24 @@ export default function Chat() { setDrawerOpen(false)} - onMessages={() => { setDrawerOpen(false); setPage('chat'); }} - onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} - onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} + onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }} + onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }} - onUsers={() => { setDrawerOpen(false); setPage('users'); }} - onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} + onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} + onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} features={features} currentPage={page} isMobile={isMobile} /> setDrawerOpen(false)} - onMessages={() => { setDrawerOpen(false); setPage('chat'); }} - onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} - onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} - onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} + onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }} + onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }} + onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }} - onUsers={() => { setDrawerOpen(false); setPage('users'); }} - onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} + onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} + onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} features={features} currentPage={page} isMobile={isMobile} /> {modal === 'profile' && setModal(null)} />} {modal === 'settings' && setModal(null)} onFeaturesChanged={setFeatures} />} @@ -435,10 +442,10 @@ export default function Chat() { unreadGroups={unreadGroups} onNewChat={() => setModal('newchat')} onProfile={() => setModal('profile')} - onUsers={() => setPage('users')} + onUsers={() => { setActiveGroupId(null); setChatHasText(false); setPage('users'); }} onSettings={() => setModal('settings')} onBranding={() => setModal('branding')} - onGroupManager={() => setPage('groups')} + onGroupManager={() => { setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} features={features} onGroupsUpdated={loadGroups} isMobile={isMobile} @@ -454,19 +461,20 @@ export default function Chat() { onGroupUpdated={loadGroups} onDirectMessage={(g) => { loadGroups(); selectGroup(g.id); }} onMessageDeleted={handleMessageDeleted} + onHasTextChange={setChatHasText} onlineUserIds={onlineUserIds} /> )} setDrawerOpen(false)} - onMessages={() => { setDrawerOpen(false); setPage('chat'); }} - onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} - onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} - onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} + onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }} + onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }} + onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }} - onUsers={() => { setDrawerOpen(false); setPage('users'); }} - onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} + onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} + onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} features={features} currentPage={page} isMobile={isMobile} /> {modal === 'profile' && setModal(null)} />} {modal === 'settings' && setModal(null)} onFeaturesChanged={setFeatures} />} @@ -488,15 +496,15 @@ export default function Chat() { setDrawerOpen(false)} - onMessages={() => { setDrawerOpen(false); setPage('chat'); }} - onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} - onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} - onScheduleManager={() => { setDrawerOpen(false); setPage('schedule'); }} - onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} + onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }} + onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }} + onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onScheduleManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }} - onUsers={() => { setDrawerOpen(false); setPage('users'); }} - onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} + onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} + onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} features={features} currentPage={page} isMobile={isMobile} /> @@ -525,15 +533,15 @@ export default function Chat() { setDrawerOpen(false)} - onMessages={() => { setDrawerOpen(false); setPage('chat'); }} - onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} - onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} - onScheduleManager={() => { setDrawerOpen(false); setPage('schedule'); }} - onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} + onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }} + onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }} + onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onScheduleManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }} - onUsers={() => { setDrawerOpen(false); setPage('users'); }} - onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} + onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} + onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} features={features} currentPage={page} isMobile={isMobile} /> @@ -567,10 +575,10 @@ export default function Chat() { unreadGroups={unreadGroups} onNewChat={() => setModal('newchat')} onProfile={() => setModal('profile')} - onUsers={() => setPage('users')} + onUsers={() => { setActiveGroupId(null); setChatHasText(false); setPage('users'); }} onSettings={() => setModal('settings')} onBranding={() => setModal('branding')} - onGroupManager={() => setPage('groups')} + onGroupManager={() => { setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} features={features} onGroupsUpdated={loadGroups} isMobile={isMobile} @@ -587,6 +595,7 @@ export default function Chat() { onGroupUpdated={loadGroups} onDirectMessage={(g) => { loadGroups(); selectGroup(g.id); }} onMessageDeleted={handleMessageDeleted} + onHasTextChange={setChatHasText} onlineUserIds={onlineUserIds} /> )} @@ -594,15 +603,15 @@ export default function Chat() { setDrawerOpen(false)} - onMessages={() => { setDrawerOpen(false); setPage('chat'); }} - onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} - onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} - onScheduleManager={() => { setDrawerOpen(false); setPage('schedule'); }} - onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} + onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }} + onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }} + onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onScheduleManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }} + onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }} - onUsers={() => { setDrawerOpen(false); setPage('users'); }} - onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} + onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} + onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} features={features} currentPage={page} isMobile={isMobile} />