v0.11.21 bug fixes

This commit is contained in:
2026-03-22 14:49:35 -04:00
parent 8116b307f7
commit 300cf5d869
6 changed files with 65 additions and 54 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "jama-backend", "name": "jama-backend",
"version": "0.11.20", "version": "0.11.21",
"description": "TeamChat backend server", "description": "TeamChat backend server",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {

View File

@@ -13,7 +13,7 @@
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
set -euo pipefail set -euo pipefail
VERSION="${1:-0.11.20}" VERSION="${1:-0.11.21}"
ACTION="${2:-}" ACTION="${2:-}"
REGISTRY="${REGISTRY:-}" REGISTRY="${REGISTRY:-}"
IMAGE_NAME="jama" IMAGE_NAME="jama"

View File

@@ -1,6 +1,6 @@
{ {
"name": "jama-frontend", "name": "jama-frontend",
"version": "0.11.20", "version": "0.11.21",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -14,7 +14,7 @@ function nameToColor(name) {
return AVATAR_COLORS[(name || '').charCodeAt(0) % AVATAR_COLORS.length]; 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 { user: currentUser } = useAuth();
const { socket } = useSocket(); const { socket } = useSocket();
const { toast } = useToast(); const { toast } = useToast();
@@ -330,7 +330,7 @@ export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMess
This channel is read-only This channel is read-only
</div> </div>
) : ( ) : (
<MessageInput group={group} currentUser={currentUser} onSend={handleSend} socket={socket} replyTo={replyTo} onCancelReply={() => setReplyTo(null)} onTyping={() => {}} /> <MessageInput group={group} currentUser={currentUser} onSend={handleSend} socket={socket} replyTo={replyTo} onCancelReply={() => setReplyTo(null)} onTyping={() => {}} onTextChange={val => onHasTextChange?.(!!val.trim())} />
)} )}
</div> </div>
{showInfo && ( {showInfo && (

View File

@@ -12,7 +12,7 @@ function isEmojiOnly(str) {
return emojiRegex.test(str.trim()); 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 [text, setText] = useState('');
const [imageFile, setImageFile] = useState(null); const [imageFile, setImageFile] = useState(null);
const [imagePreview, setImagePreview] = 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; const val = e.target.value;
setText(val); setText(val);
handleTypingChange(val); handleTypingChange(val);
onTextChange?.(val);
const el = e.target; const el = e.target;
el.style.height = 'auto'; el.style.height = 'auto';
@@ -157,6 +158,7 @@ export default function MessageInput({ group, replyTo, onCancelReply, onSend, on
setImagePreview(null); setImagePreview(null);
wasTyping.current = false; wasTyping.current = false;
onTyping(false); onTyping(false);
onTextChange?.('');
if (inputRef.current) { if (inputRef.current) {
inputRef.current.style.height = 'auto'; inputRef.current.style.height = 'auto';
inputRef.current.style.overflowY = 'hidden'; inputRef.current.style.overflowY = 'hidden';

View File

@@ -36,6 +36,7 @@ export default function Chat() {
const [groups, setGroups] = useState({ publicGroups: [], privateGroups: [] }); const [groups, setGroups] = useState({ publicGroups: [], privateGroups: [] });
const [onlineUserIds, setOnlineUserIds] = useState(new Set()); const [onlineUserIds, setOnlineUserIds] = useState(new Set());
const [activeGroupId, setActiveGroupId] = useState(null); const [activeGroupId, setActiveGroupId] = useState(null);
const [chatHasText, setChatHasText] = useState(false);
const [notifications, setNotifications] = useState([]); const [notifications, setNotifications] = useState([]);
const [unreadGroups, setUnreadGroups] = useState(new Map()); const [unreadGroups, setUnreadGroups] = useState(new Map());
const [modal, setModal] = useState(null); // 'profile' | 'users' | 'settings' | 'newchat' | 'help' | 'groupmanager' 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]); }, [socket, toast, activeGroupId, user, isMobile, loadGroups]);
const selectGroup = (id) => { 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); setActiveGroupId(id);
if (isMobile) { if (isMobile) {
setShowSidebar(false); setShowSidebar(false);
@@ -366,14 +373,14 @@ export default function Chat() {
</div> </div>
<NavDrawer <NavDrawer
open={drawerOpen} onClose={() => setDrawerOpen(false)} open={drawerOpen} onClose={() => setDrawerOpen(false)}
onMessages={() => { setDrawerOpen(false); setPage('chat'); }} onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }}
onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }}
onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }}
onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }}
onUsers={() => { setDrawerOpen(false); setPage('users'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }}
features={features} currentPage={page} isMobile={isMobile} /> features={features} currentPage={page} isMobile={isMobile} />
{modal === 'profile' && <ProfileModal onClose={() => setModal(null)} />} {modal === 'profile' && <ProfileModal onClose={() => setModal(null)} />}
{modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />} {modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />}
@@ -393,24 +400,24 @@ export default function Chat() {
</div> </div>
<NavDrawer <NavDrawer
open={drawerOpen} onClose={() => setDrawerOpen(false)} open={drawerOpen} onClose={() => setDrawerOpen(false)}
onMessages={() => { setDrawerOpen(false); setPage('chat'); }} onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }}
onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }}
onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }}
onUsers={() => { setDrawerOpen(false); setPage('users'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }}
features={features} currentPage={page} isMobile={isMobile} /> features={features} currentPage={page} isMobile={isMobile} />
<NavDrawer <NavDrawer
open={drawerOpen} onClose={() => setDrawerOpen(false)} open={drawerOpen} onClose={() => setDrawerOpen(false)}
onMessages={() => { setDrawerOpen(false); setPage('chat'); }} onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }}
onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }}
onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }}
onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }}
onUsers={() => { setDrawerOpen(false); setPage('users'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }}
features={features} currentPage={page} isMobile={isMobile} /> features={features} currentPage={page} isMobile={isMobile} />
{modal === 'profile' && <ProfileModal onClose={() => setModal(null)} />} {modal === 'profile' && <ProfileModal onClose={() => setModal(null)} />}
{modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />} {modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />}
@@ -435,10 +442,10 @@ export default function Chat() {
unreadGroups={unreadGroups} unreadGroups={unreadGroups}
onNewChat={() => setModal('newchat')} onNewChat={() => setModal('newchat')}
onProfile={() => setModal('profile')} onProfile={() => setModal('profile')}
onUsers={() => setPage('users')} onUsers={() => { setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onSettings={() => setModal('settings')} onSettings={() => setModal('settings')}
onBranding={() => setModal('branding')} onBranding={() => setModal('branding')}
onGroupManager={() => setPage('groups')} onGroupManager={() => { setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
features={features} features={features}
onGroupsUpdated={loadGroups} onGroupsUpdated={loadGroups}
isMobile={isMobile} isMobile={isMobile}
@@ -454,19 +461,20 @@ export default function Chat() {
onGroupUpdated={loadGroups} onGroupUpdated={loadGroups}
onDirectMessage={(g) => { loadGroups(); selectGroup(g.id); }} onDirectMessage={(g) => { loadGroups(); selectGroup(g.id); }}
onMessageDeleted={handleMessageDeleted} onMessageDeleted={handleMessageDeleted}
onHasTextChange={setChatHasText}
onlineUserIds={onlineUserIds} /> onlineUserIds={onlineUserIds} />
)} )}
</div> </div>
<NavDrawer <NavDrawer
open={drawerOpen} onClose={() => setDrawerOpen(false)} open={drawerOpen} onClose={() => setDrawerOpen(false)}
onMessages={() => { setDrawerOpen(false); setPage('chat'); }} onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }}
onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }}
onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }}
onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }}
onUsers={() => { setDrawerOpen(false); setPage('users'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }}
features={features} currentPage={page} isMobile={isMobile} /> features={features} currentPage={page} isMobile={isMobile} />
{modal === 'profile' && <ProfileModal onClose={() => setModal(null)} />} {modal === 'profile' && <ProfileModal onClose={() => setModal(null)} />}
{modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />} {modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />}
@@ -488,15 +496,15 @@ export default function Chat() {
<NavDrawer <NavDrawer
open={drawerOpen} open={drawerOpen}
onClose={() => setDrawerOpen(false)} onClose={() => setDrawerOpen(false)}
onMessages={() => { setDrawerOpen(false); setPage('chat'); }} onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }}
onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }}
onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onScheduleManager={() => { setDrawerOpen(false); setPage('schedule'); }} onScheduleManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }}
onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }}
onUsers={() => { setDrawerOpen(false); setPage('users'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }}
features={features} features={features}
currentPage={page} currentPage={page}
isMobile={isMobile} /> isMobile={isMobile} />
@@ -525,15 +533,15 @@ export default function Chat() {
<NavDrawer <NavDrawer
open={drawerOpen} open={drawerOpen}
onClose={() => setDrawerOpen(false)} onClose={() => setDrawerOpen(false)}
onMessages={() => { setDrawerOpen(false); setPage('chat'); }} onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }}
onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }}
onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onScheduleManager={() => { setDrawerOpen(false); setPage('schedule'); }} onScheduleManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }}
onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }}
onUsers={() => { setDrawerOpen(false); setPage('users'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }}
features={features} features={features}
currentPage={page} currentPage={page}
isMobile={isMobile} /> isMobile={isMobile} />
@@ -567,10 +575,10 @@ export default function Chat() {
unreadGroups={unreadGroups} unreadGroups={unreadGroups}
onNewChat={() => setModal('newchat')} onNewChat={() => setModal('newchat')}
onProfile={() => setModal('profile')} onProfile={() => setModal('profile')}
onUsers={() => setPage('users')} onUsers={() => { setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onSettings={() => setModal('settings')} onSettings={() => setModal('settings')}
onBranding={() => setModal('branding')} onBranding={() => setModal('branding')}
onGroupManager={() => setPage('groups')} onGroupManager={() => { setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
features={features} features={features}
onGroupsUpdated={loadGroups} onGroupsUpdated={loadGroups}
isMobile={isMobile} isMobile={isMobile}
@@ -587,6 +595,7 @@ export default function Chat() {
onGroupUpdated={loadGroups} onGroupUpdated={loadGroups}
onDirectMessage={(g) => { loadGroups(); selectGroup(g.id); }} onDirectMessage={(g) => { loadGroups(); selectGroup(g.id); }}
onMessageDeleted={handleMessageDeleted} onMessageDeleted={handleMessageDeleted}
onHasTextChange={setChatHasText}
onlineUserIds={onlineUserIds} /> onlineUserIds={onlineUserIds} />
)} )}
</div> </div>
@@ -594,15 +603,15 @@ export default function Chat() {
<NavDrawer <NavDrawer
open={drawerOpen} open={drawerOpen}
onClose={() => setDrawerOpen(false)} onClose={() => setDrawerOpen(false)}
onMessages={() => { setDrawerOpen(false); setPage('chat'); }} onMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('chat'); }}
onGroupMessages={() => { setDrawerOpen(false); setPage('groupmessages'); }} onGroupMessages={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groupmessages'); }}
onSchedule={() => { setDrawerOpen(false); setPage('schedule'); }} onSchedule={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onScheduleManager={() => { setDrawerOpen(false); setPage('schedule'); }} onScheduleManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('schedule'); }}
onGroupManager={() => { setDrawerOpen(false); setPage('groups'); }} onGroupManager={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('groups'); }}
onBranding={() => { setDrawerOpen(false); setModal('branding'); }} onBranding={() => { setDrawerOpen(false); setModal('branding'); }}
onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onSettings={() => { setDrawerOpen(false); setModal('settings'); }}
onUsers={() => { setDrawerOpen(false); setPage('users'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }}
onHostPanel={() => { setDrawerOpen(false); setPage('hostpanel'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }}
features={features} features={features}
currentPage={page} currentPage={page}
isMobile={isMobile} /> isMobile={isMobile} />