diff --git a/backend/package.json b/backend/package.json index 41935d2..9acefd6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.10.7", + "version": "0.10.8", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index 8d530ed..8c1abeb 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.10.7}" +VERSION="${1:-0.10.8}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index 54754e2..a69452d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.10.7", + "version": "0.10.8", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/pages/GroupManagerPage.jsx b/frontend/src/pages/GroupManagerPage.jsx index bca0bc1..50ccd79 100644 --- a/frontend/src/pages/GroupManagerPage.jsx +++ b/frontend/src/pages/GroupManagerPage.jsx @@ -495,6 +495,8 @@ function U2URestrictionsTab({ allUserGroups, isMobile = false }) { } // ── Main page ───────────────────────────────────────────────────────────────── +const SIDEBAR_W = 260; + export default function GroupManagerPage({ isMobile = false, onProfile, onHelp, onAbout }) { const [tab, setTab] = useState('all'); const [allUsers, setAllUsers] = useState([]); @@ -508,31 +510,47 @@ export default function GroupManagerPage({ isMobile = false, onProfile, onHelp, }, [refreshKey]); return ( -
- {/* Page header */} -
-
-
- - Group Manager -
-
- - - +
+ + {/* ── Left panel (desktop only) ── */} + {!isMobile && ( +
+
+ +
+ )} + + {/* ── Right panel ── */} +
+ + {/* Header */} +
+
+
+ + Group Manager +
+
+ + + +
-
- {/* Content */} -
- {tab==='all' && } - {tab==='dm' && } - {tab==='u2u' && } -
- {/* User footer */} -
- + {/* Content */} +
+ {tab==='all' && } + {tab==='dm' && } + {tab==='u2u' && } +
+ + {/* Mobile footer — fixed at bottom, always visible */} + {isMobile && ( +
+ +
+ )}
); diff --git a/frontend/src/pages/UserManagerPage.jsx b/frontend/src/pages/UserManagerPage.jsx index e8f2c80..9ecef3a 100644 --- a/frontend/src/pages/UserManagerPage.jsx +++ b/frontend/src/pages/UserManagerPage.jsx @@ -4,6 +4,8 @@ import { api } from '../utils/api.js'; import Avatar from '../components/Avatar.jsx'; import UserFooter from '../components/UserFooter.jsx'; +const SIDEBAR_W = 260; + function isValidEmail(e) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e); } function parseCSV(text) { @@ -118,7 +120,6 @@ function UserRow({ u, onUpdated }) { Edit Name )} -
{roleWarning && Role Required}
- {showReset ? (
)} -
{u.status==='active' ? ( @@ -162,7 +161,7 @@ function UserRow({ u, onUpdated }) { } // ── Create user form ────────────────────────────────────────────────────────── -function CreateUserForm({ userPass, onCreated, isMobile = false }) { +function CreateUserForm({ userPass, onCreated, isMobile }) { const toast = useToast(); const [form, setForm] = useState({ name:'', email:'', password:'', role:'member' }); const [saving, setSaving] = useState(false); @@ -183,7 +182,7 @@ function CreateUserForm({ userPass, onCreated, isMobile = false }) { }; return ( -
+
@@ -256,11 +255,7 @@ function BulkImportForm({ userPass, onCreated }) { {csvFile && {csvFile.name}{csvRows.length > 0 && ({csvRows.length} valid)}} - {csvRows.length > 0 && ( - - )} + {csvRows.length > 0 && }
{csvInvalid.length > 0 && (
@@ -300,6 +295,7 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o const [search, setSearch] = useState(''); const [tab, setTab] = useState('users'); const [userPass, setUserPass] = useState('user@1234'); + const [inputFocused, setInputFocused] = useState(false); const load = async () => { setLoadError(''); setLoading(true); @@ -321,57 +317,72 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o u.email?.toLowerCase().includes(search.toLowerCase()) ); - const pad = isMobile ? 16 : 24; return ( -
- {/* Page header */} -
-
-
- - User Manager - {!loading && {users.length} user{users.length!==1?'s':''}} -
-
- - - {!isMobile && } +
+ + {/* ── Left panel (desktop only) — blank, reserved for future use ── */} + {!isMobile && ( +
+
+ +
+ )} + + {/* ── Right panel ── */} +
+ + {/* Header */} +
+
+
+ + User Manager + {!loading && {users.length} user{users.length!==1?'s':''}} +
+
+ + + {!isMobile && } +
-
- {/* Content */} -
- {tab === 'users' && ( - <> - setSearch(e.target.value)} - autoComplete="new-password" autoCorrect="off" spellCheck={false} - style={{ marginBottom:16, width:'100%', maxWidth: isMobile ? '100%' : 400 }} /> -
- {loading ? ( -
- ) : loadError ? ( -
-
⚠ {loadError}
- -
- ) : filtered.length === 0 ? ( -
- {search ? 'No users match your search.' : 'No users yet.'} -
- ) : ( - filtered.map(u => ) - )} -
- + {/* Content */} +
+ {tab === 'users' && ( + <> + setSearch(e.target.value)} + onFocus={() => setInputFocused(true)} onBlur={() => setInputFocused(false)} + autoComplete="new-password" autoCorrect="off" spellCheck={false} + style={{ marginBottom:16, width:'100%', maxWidth: isMobile ? '100%' : 400 }} /> +
+ {loading ? ( +
+ ) : loadError ? ( +
+
⚠ {loadError}
+ +
+ ) : filtered.length === 0 ? ( +
+ {search ? 'No users match your search.' : 'No users yet.'} +
+ ) : ( + filtered.map(u => ) + )} +
+ + )} + {tab === 'create' && { load(); setTab('users'); }} isMobile={isMobile} />} + {tab === 'bulk' && } +
+ + {/* Mobile footer — fixed at bottom, hidden when keyboard open */} + {isMobile && !inputFocused && ( +
+ +
)} - {tab === 'create' && { load(); setTab('users'); }} isMobile={isMobile} />} - {tab === 'bulk' && } -
- - {/* User footer */} -
-
);