From 83b2105a9af7604d628bf924027d82b873bbd64a Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Fri, 13 Mar 2026 11:47:33 -0400 Subject: [PATCH] v0.8.7 pin bug fixes --- .env.example | 2 +- backend/package.json | 2 +- backend/src/routes/push.js | 11 +++-- build.sh | 2 +- frontend/package.json | 2 +- frontend/src/components/ChatWindow.jsx | 65 ++++++++++++++------------ 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/.env.example b/.env.example index 0c1b0b2..28631ee 100644 --- a/.env.example +++ b/.env.example @@ -7,7 +7,7 @@ TZ=UTC # Copy this file to .env and customize # Image version to run (set by build.sh, or use 'latest') -JAMA_VERSION=0.8.6 +JAMA_VERSION=0.8.7 # Default admin credentials (used on FIRST RUN only) ADMIN_NAME=Admin User diff --git a/backend/package.json b/backend/package.json index abd806d..c7e0a8b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.8.6", + "version": "0.8.7", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/backend/src/routes/push.js b/backend/src/routes/push.js index 9835ddd..a4c5c5a 100644 --- a/backend/src/routes/push.js +++ b/backend/src/routes/push.js @@ -70,11 +70,12 @@ router.post('/subscribe', authMiddleware, (req, res) => { return res.status(400).json({ error: 'Invalid subscription' }); } const db = getDb(); - db.prepare(` - INSERT INTO push_subscriptions (user_id, endpoint, p256dh, auth) - VALUES (?, ?, ?, ?) - ON CONFLICT(endpoint) DO UPDATE SET user_id = ?, p256dh = ?, auth = ? - `).run(req.user.id, endpoint, keys.p256dh, keys.auth, req.user.id, keys.p256dh, keys.auth); + // Use DELETE+INSERT to avoid relying on any specific UNIQUE constraint + // (existing DBs may have different schemas for this table) + const delStmt = db.prepare('DELETE FROM push_subscriptions WHERE endpoint = ?'); + const insStmt = db.prepare('INSERT INTO push_subscriptions (user_id, endpoint, p256dh, auth) VALUES (?, ?, ?, ?)'); + delStmt.run(endpoint); + insStmt.run(req.user.id, endpoint, keys.p256dh, keys.auth); res.json({ success: true }); }); diff --git a/build.sh b/build.sh index 13bf150..e383258 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.8.6}" +VERSION="${1:-0.8.7}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index c5be7de..81fca89 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.8.6", + "version": "0.8.7", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/ChatWindow.jsx b/frontend/src/components/ChatWindow.jsx index d776d70..c7fffbe 100644 --- a/frontend/src/components/ChatWindow.jsx +++ b/frontend/src/components/ChatWindow.jsx @@ -2,39 +2,40 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import Message from './Message.jsx'; import MessageInput from './MessageInput.jsx'; import { api } from '../utils/api.js'; +import { useAuth } from '../contexts/AuthContext.jsx'; import { useToast } from '../contexts/ToastContext.jsx'; import { useSocket } from '../contexts/SocketContext.jsx'; import './ChatWindow.css'; -function formatTime(ts) { - if (!ts) return ''; - const d = new Date(ts); - const now = new Date(); - const diff = now - d; - if (diff < 86400000 && d.getDate() === now.getDate()) { - return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - } - if (diff < 604800000) { - return d.toLocaleDateString([], { weekday: 'short' }); - } - return d.toLocaleDateString([], { month: 'short', day: 'numeric' }); -} +export default function ChatWindow({ group, onBack, onGroupUpdated, onDirectMessage, onlineUserIds = new Set() }) { + const { user: currentUser } = useAuth(); + const { socket } = useSocket(); + const { toast } = useToast(); -export default function ChatWindow({ group, currentUser, onBack, isMobile, onlineUserIds = new Set() }) { const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(false); const [typing, setTyping] = useState([]); const [iconGroupInfo, setIconGroupInfo] = useState(''); + const [isMobile, setIsMobile] = useState(window.innerWidth < 768); + const messagesEndRef = useRef(null); const messagesContainerRef = useRef(null); const typingTimers = useRef({}); - const { toast } = useToast(); - const { socket } = useSocket(); useEffect(() => { - api.getSettings().then(({ settings }) => setIconGroupInfo(settings.icon_groupinfo || '')).catch(() => {}); - const handler = () => api.getSettings().then(({ settings }) => setIconGroupInfo(settings.icon_groupinfo || '')).catch(() => {}); + const onResize = () => setIsMobile(window.innerWidth < 768); + window.addEventListener('resize', onResize); + return () => window.removeEventListener('resize', onResize); + }, []); + + useEffect(() => { + api.getSettings() + .then(({ settings }) => setIconGroupInfo(settings.icon_groupinfo || '')) + .catch(() => {}); + const handler = () => api.getSettings() + .then(({ settings }) => setIconGroupInfo(settings.icon_groupinfo || '')) + .catch(() => {}); window.addEventListener('jama:settings-updated', handler); return () => window.removeEventListener('jama:settings-updated', handler); }, []); @@ -84,7 +85,10 @@ export default function ChatWindow({ group, currentUser, onBack, isMobile, onlin }; const handleTypingStart = ({ userId: tid, user: tu }) => { - setTyping(prev => prev.find(t => t.userId === tid) ? prev : [...prev, { userId: tid, name: tu?.display_name || tu?.name || 'Someone' }]); + if (tid === currentUser?.id) return; + setTyping(prev => prev.find(t => t.userId === tid) + ? prev + : [...prev, { userId: tid, name: tu?.display_name || tu?.name || 'Someone' }]); if (typingTimers.current[tid]) clearTimeout(typingTimers.current[tid]); typingTimers.current[tid] = setTimeout(() => { setTyping(prev => prev.filter(t => t.userId !== tid)); @@ -96,11 +100,16 @@ export default function ChatWindow({ group, currentUser, onBack, isMobile, onlin setTyping(prev => prev.filter(t => t.userId !== tid)); }; + const handleGroupUpdated = (updatedGroup) => { + if (updatedGroup.id === group.id) onGroupUpdated?.(); + }; + socket.on('message:new', handleNew); socket.on('message:deleted', handleDeleted); socket.on('reaction:updated', handleReaction); socket.on('typing:start', handleTypingStart); socket.on('typing:stop', handleTypingStop); + socket.on('group:updated', handleGroupUpdated); return () => { socket.off('message:new', handleNew); @@ -108,8 +117,9 @@ export default function ChatWindow({ group, currentUser, onBack, isMobile, onlin socket.off('reaction:updated', handleReaction); socket.off('typing:start', handleTypingStart); socket.off('typing:stop', handleTypingStop); + socket.off('group:updated', handleGroupUpdated); }; - }, [socket, group?.id]); + }, [socket, group?.id, currentUser?.id]); const handleLoadMore = async () => { if (!hasMore || loading || messages.length === 0) return; @@ -160,13 +170,8 @@ export default function ChatWindow({ group, currentUser, onBack, isMobile, onlin window.dispatchEvent(new CustomEvent('jama:reply', { detail: msg })); }; - const handleDirectMessage = async (userId) => { - try { - const { group: dmGroup } = await api.createGroup({ type: 'direct', userId }); - window.dispatchEvent(new CustomEvent('jama:open-group', { detail: dmGroup.id })); - } catch (e) { - toast(e.message || 'Could not open DM', 'error'); - } + const handleDirectMessage = (dmGroup) => { + onDirectMessage?.(dmGroup); }; if (!group) { @@ -195,7 +200,7 @@ export default function ChatWindow({ group, currentUser, onBack, isMobile, onlin
{/* Header */}
- {isMobile && ( + {isMobile && onBack && (