v0.12.48 Login Type bug fixes
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rosterchirp-backend",
|
"name": "rosterchirp-backend",
|
||||||
"version": "0.12.47",
|
"version": "0.12.48",
|
||||||
"description": "RosterChirp backend server",
|
"description": "RosterChirp backend server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -211,7 +211,16 @@ router.get('/:id', authMiddleware, teamManagerMiddleware, async (req, res) => {
|
|||||||
FROM user_group_members ugm JOIN users u ON u.id=ugm.user_id
|
FROM user_group_members ugm JOIN users u ON u.id=ugm.user_id
|
||||||
WHERE ugm.user_group_id=$1 ORDER BY u.name ASC
|
WHERE ugm.user_group_id=$1 ORDER BY u.name ASC
|
||||||
`, [req.params.id]);
|
`, [req.params.id]);
|
||||||
res.json({ group, members });
|
const aliasMembers = await query(req.schema, `
|
||||||
|
SELECT ga.id, ga.first_name, ga.last_name,
|
||||||
|
ga.first_name || ' ' || ga.last_name AS name,
|
||||||
|
ga.guardian_id, ga.avatar, ga.date_of_birth
|
||||||
|
FROM alias_group_members agm
|
||||||
|
JOIN guardian_aliases ga ON ga.id = agm.alias_id
|
||||||
|
WHERE agm.user_group_id=$1
|
||||||
|
ORDER BY ga.first_name, ga.last_name ASC
|
||||||
|
`, [req.params.id]);
|
||||||
|
res.json({ group, members, aliasMembers });
|
||||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -13,7 +13,7 @@
|
|||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="${1:-0.12.47}"
|
VERSION="${1:-0.12.48}"
|
||||||
ACTION="${2:-}"
|
ACTION="${2:-}"
|
||||||
REGISTRY="${REGISTRY:-}"
|
REGISTRY="${REGISTRY:-}"
|
||||||
IMAGE_NAME="rosterchirp"
|
IMAGE_NAME="rosterchirp"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rosterchirp-frontend",
|
"name": "rosterchirp-frontend",
|
||||||
"version": "0.12.47",
|
"version": "0.12.48",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ function AllGroupsTab({ allUsers, onRefresh, isMobile = false, onIF, onIB }) {
|
|||||||
const [savedMembers, setSavedMembers] = useState(new Set());
|
const [savedMembers, setSavedMembers] = useState(new Set());
|
||||||
const [members, setMembers] = useState(new Set());
|
const [members, setMembers] = useState(new Set());
|
||||||
const [fullMembers, setFullMembers] = useState([]); // full member objects including deleted
|
const [fullMembers, setFullMembers] = useState([]); // full member objects including deleted
|
||||||
|
const [aliasMembers, setAliasMembers] = useState([]); // child aliases in this group
|
||||||
const [editName, setEditName] = useState('');
|
const [editName, setEditName] = useState('');
|
||||||
const [noDm, setNoDm] = useState(false);
|
const [noDm, setNoDm] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@@ -81,16 +82,17 @@ function AllGroupsTab({ allUsers, onRefresh, isMobile = false, onIF, onIB }) {
|
|||||||
const selectGroup = async (g) => {
|
const selectGroup = async (g) => {
|
||||||
setShowDelete(false);
|
setShowDelete(false);
|
||||||
setAccordionOpen(false);
|
setAccordionOpen(false);
|
||||||
const { members: mems } = await api.getUserGroup(g.id);
|
const { members: mems, aliasMembers: aliases } = await api.getUserGroup(g.id);
|
||||||
const ids = new Set(mems.map(m => m.id));
|
const ids = new Set(mems.map(m => m.id));
|
||||||
setSelected(g); setEditName(g.name); setMembers(ids); setSavedMembers(ids);
|
setSelected(g); setEditName(g.name); setMembers(ids); setSavedMembers(ids);
|
||||||
setFullMembers(mems);
|
setFullMembers(mems);
|
||||||
|
setAliasMembers(aliases || []);
|
||||||
// No DM → checkbox enabled+checked; has DM → checkbox disabled+unchecked
|
// No DM → checkbox enabled+checked; has DM → checkbox disabled+unchecked
|
||||||
setNoDm(!g.dm_group_id);
|
setNoDm(!g.dm_group_id);
|
||||||
};
|
};
|
||||||
const clearSelection = () => {
|
const clearSelection = () => {
|
||||||
setSelected(null); setEditName(''); setMembers(new Set()); setSavedMembers(new Set());
|
setSelected(null); setEditName(''); setMembers(new Set()); setSavedMembers(new Set());
|
||||||
setShowDelete(false); setFullMembers([]); setNoDm(false);
|
setShowDelete(false); setFullMembers([]); setAliasMembers([]); setNoDm(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
@@ -102,9 +104,9 @@ function AllGroupsTab({ allUsers, onRefresh, isMobile = false, onIF, onIB }) {
|
|||||||
const createDm = !selected.dm_group_id && !noDm;
|
const createDm = !selected.dm_group_id && !noDm;
|
||||||
const { group: updated } = await api.updateUserGroup(selected.id, { name: editName.trim(), memberIds: [...members], createDm });
|
const { group: updated } = await api.updateUserGroup(selected.id, { name: editName.trim(), memberIds: [...members], createDm });
|
||||||
toast('Group updated', 'success');
|
toast('Group updated', 'success');
|
||||||
const { members: fresh } = await api.getUserGroup(selected.id);
|
const { members: fresh, aliasMembers: freshAliases } = await api.getUserGroup(selected.id);
|
||||||
const freshIds = new Set(fresh.map(m => m.id));
|
const freshIds = new Set(fresh.map(m => m.id));
|
||||||
setSavedMembers(freshIds); setMembers(freshIds); setFullMembers(fresh);
|
setSavedMembers(freshIds); setMembers(freshIds); setFullMembers(fresh); setAliasMembers(freshAliases || []);
|
||||||
// Reflect new dm_group_id if a DM was just created
|
// Reflect new dm_group_id if a DM was just created
|
||||||
setSelected(prev => ({ ...prev, name: editName.trim(), dm_group_id: updated?.dm_group_id ?? prev.dm_group_id }));
|
setSelected(prev => ({ ...prev, name: editName.trim(), dm_group_id: updated?.dm_group_id ?? prev.dm_group_id }));
|
||||||
if (createDm) setNoDm(false);
|
if (createDm) setNoDm(false);
|
||||||
@@ -220,6 +222,19 @@ function AllGroupsTab({ allUsers, onRefresh, isMobile = false, onIF, onIB }) {
|
|||||||
<div style={{ marginTop:6 }}><UserCheckList allUsers={allUsers} selectedIds={members} onChange={setMembers} onIF={onIF} onIB={onIB} /></div>
|
<div style={{ marginTop:6 }}><UserCheckList allUsers={allUsers} selectedIds={members} onChange={setMembers} onIF={onIF} onIB={onIB} /></div>
|
||||||
<p style={{ fontSize:12, color:'var(--text-tertiary)', marginTop:5 }}>{members.size} selected</p>
|
<p style={{ fontSize:12, color:'var(--text-tertiary)', marginTop:5 }}>{members.size} selected</p>
|
||||||
</div>
|
</div>
|
||||||
|
{aliasMembers.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<label className="settings-section-label">Child Aliases</label>
|
||||||
|
<div style={{ marginTop:6, border:'1px solid var(--border)', borderRadius:'var(--radius)', overflow:'hidden' }}>
|
||||||
|
{aliasMembers.map((a, i) => (
|
||||||
|
<div key={a.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 12px', borderBottom: i < aliasMembers.length - 1 ? '1px solid var(--border)' : 'none' }}>
|
||||||
|
<span style={{ flex:1, fontSize:13 }}>{a.name}</span>
|
||||||
|
{a.date_of_birth && <span style={{ fontSize:11, color:'var(--text-tertiary)' }}>{a.date_of_birth.slice(0,10)}</span>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{deletedMembers.length > 0 && (
|
{deletedMembers.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<label className="settings-section-label" style={{ color:'var(--error)' }}>
|
<label className="settings-section-label" style={{ color:'var(--error)' }}>
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ function UserForm({ user, userPass, allUserGroups, nonMinorUsers, loginType, onD
|
|||||||
const [email, setEmail] = useState(user?.email || '');
|
const [email, setEmail] = useState(user?.email || '');
|
||||||
const [phone, setPhone] = useState(user?.phone || '');
|
const [phone, setPhone] = useState(user?.phone || '');
|
||||||
const [role, setRole] = useState(user?.role || 'member');
|
const [role, setRole] = useState(user?.role || 'member');
|
||||||
const [dob, setDob] = useState(user?.date_of_birth || '');
|
const [dob, setDob] = useState(user?.date_of_birth?.slice(0, 10) || '');
|
||||||
const [guardianId, setGuardianId] = useState(user?.guardian_user_id || '');
|
const [guardianId, setGuardianId] = useState(user?.guardian_user_id || '');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [pwEnabled, setPwEnabled] = useState(!isEdit);
|
const [pwEnabled, setPwEnabled] = useState(!isEdit);
|
||||||
@@ -566,6 +566,7 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o
|
|||||||
const [userPass, setUserPass] = useState('user@1234');
|
const [userPass, setUserPass] = useState('user@1234');
|
||||||
const [allUserGroups, setAllUserGroups] = useState([]);
|
const [allUserGroups, setAllUserGroups] = useState([]);
|
||||||
const [loginType, setLoginType] = useState('all_ages');
|
const [loginType, setLoginType] = useState('all_ages');
|
||||||
|
const [guardiansGroupUserIds, setGuardiansGroupUserIds] = useState(null); // null = not loaded yet
|
||||||
const [inputFocused, setInputFocused] = useState(false);
|
const [inputFocused, setInputFocused] = useState(false);
|
||||||
const onIF = () => setInputFocused(true);
|
const onIF = () => setInputFocused(true);
|
||||||
const onIB = () => setInputFocused(false);
|
const onIB = () => setInputFocused(false);
|
||||||
@@ -582,6 +583,12 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o
|
|||||||
api.getSettings().then(({ settings }) => {
|
api.getSettings().then(({ settings }) => {
|
||||||
if (settings.user_pass) setUserPass(settings.user_pass);
|
if (settings.user_pass) setUserPass(settings.user_pass);
|
||||||
setLoginType(settings.feature_login_type || 'all_ages');
|
setLoginType(settings.feature_login_type || 'all_ages');
|
||||||
|
const guardiansGroupId = settings.feature_guardians_group_id ? parseInt(settings.feature_guardians_group_id) : null;
|
||||||
|
if (guardiansGroupId) {
|
||||||
|
api.getUserGroup(guardiansGroupId)
|
||||||
|
.then(({ members }) => setGuardiansGroupUserIds(new Set((members || []).map(m => m.id))))
|
||||||
|
.catch(() => setGuardiansGroupUserIds(null));
|
||||||
|
}
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
api.getUserGroups().then(({ groups }) => setAllUserGroups([...(groups||[])].sort((a,b) => a.name.localeCompare(b.name)))).catch(() => {});
|
api.getUserGroups().then(({ groups }) => setAllUserGroups([...(groups||[])].sort((a,b) => a.name.localeCompare(b.name)))).catch(() => {});
|
||||||
}, [load]);
|
}, [load]);
|
||||||
@@ -690,7 +697,7 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o
|
|||||||
user={view === 'edit' ? editUser : null}
|
user={view === 'edit' ? editUser : null}
|
||||||
userPass={userPass}
|
userPass={userPass}
|
||||||
allUserGroups={allUserGroups}
|
allUserGroups={allUserGroups}
|
||||||
nonMinorUsers={users.filter(u => !u.is_minor && u.status === 'active')}
|
nonMinorUsers={users.filter(u => !u.is_minor && u.status === 'active' && (guardiansGroupUserIds === null || guardiansGroupUserIds.has(u.id)))}
|
||||||
loginType={loginType}
|
loginType={loginType}
|
||||||
onDone={() => { load(); goList(); }}
|
onDone={() => { load(); goList(); }}
|
||||||
onCancel={goList}
|
onCancel={goList}
|
||||||
|
|||||||
Reference in New Issue
Block a user