v0.12.24 minor user manager buf fixes

This commit is contained in:
2026-03-24 16:10:27 -04:00
parent e77176841c
commit 7276228a98
5 changed files with 32 additions and 25 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "rosterchirp-backend",
"version": "0.12.23",
"version": "0.12.24",
"description": "RosterChirp backend server",
"main": "src/index.js",
"scripts": {

View File

@@ -0,0 +1,3 @@
-- Migration 010: Date of birth and guardian fields
ALTER TABLE users ADD COLUMN IF NOT EXISTS date_of_birth DATE;
ALTER TABLE users ADD COLUMN IF NOT EXISTS guardian_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL;

View File

@@ -13,7 +13,7 @@
# ─────────────────────────────────────────────────────────────
set -euo pipefail
VERSION="${1:-0.12.23}"
VERSION="${1:-0.12.24}"
ACTION="${2:-}"
REGISTRY="${REGISTRY:-}"
IMAGE_NAME="rosterchirp"

View File

@@ -1,6 +1,6 @@
{
"name": "rosterchirp-frontend",
"version": "0.12.23",
"version": "0.12.24",
"private": true,
"scripts": {
"dev": "vite",

View File

@@ -119,9 +119,10 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
const [lastName, setLastName] = useState(user?.last_name || '');
const [email, setEmail] = useState(user?.email || '');
const [phone, setPhone] = useState(user?.phone || '');
const [role, setRole] = useState(user?.role || 'member');
const [isMinor, setIsMinor] = useState(!!user?.is_minor);
const [password, setPassword] = useState('');
const [role, setRole] = useState(user?.role || 'member');
const [dob, setDob] = useState(user?.date_of_birth || '');
const [guardianId, setGuardianId] = useState(user?.guardian_user_id || '');
const [password, setPassword] = useState('');
const [pwEnabled, setPwEnabled] = useState(!isEdit);
const [saving, setSaving] = useState(false);
const [selectedGroupIds, setSelectedGroupIds] = useState(new Set());
@@ -155,8 +156,6 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
if (!lastName.trim()) return toast('Last name is required', 'error');
if (!isValidPhone(phone)) return toast('Invalid phone number', 'error');
if (!['member', 'admin', 'manager'].includes(role)) return toast('Role is required', 'error');
if (!isEdit && (!password || password.length < 6))
return toast('Password must be at least 6 characters', 'error');
if (isEdit && pwEnabled && (!password || password.length < 6))
return toast('New password must be at least 6 characters', 'error');
@@ -167,7 +166,6 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
firstName: firstName.trim(),
lastName: lastName.trim(),
phone: phone.trim(),
isMinor,
role,
...(pwEnabled && password ? { password } : {}),
});
@@ -185,9 +183,8 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
lastName: lastName.trim(),
email: email.trim(),
phone: phone.trim(),
isMinor,
role,
password,
...(password ? { password } : {}),
});
// Add to selected groups
for (const gId of selectedGroupIds) {
@@ -273,13 +270,23 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
</div>
</div>
{/* Row 4: Is minor */}
<div style={{ marginBottom:12 }}>
<label style={{ display:'flex', alignItems:'center', gap:8, cursor:'pointer', fontSize:14, color:'var(--text-primary)', userSelect:'none' }}>
<input type="checkbox" checked={isMinor} onChange={e => setIsMinor(e.target.checked)}
style={{ accentColor:'var(--primary)', width:15, height:15 }} />
User is a minor
</label>
{/* Row 4: DOB + Guardian */}
<div style={{ display:'grid', gridTemplateColumns:colGrid, gap:12, marginBottom:12 }}>
<div>
{lbl('Date of Birth', false, '(optional)')}
<input className="input" type="text" placeholder="YYYY-MM-DD"
value={dob} onChange={e => setDob(e.target.value)}
disabled
style={{ opacity:0.5, cursor:'not-allowed' }} />
</div>
<div>
{lbl('Guardian', false, '(optional)')}
<select className="input" value={guardianId} onChange={e => setGuardianId(e.target.value)}
disabled
style={{ opacity:0.5, cursor:'not-allowed' }}>
<option value=""> Select guardian </option>
</select>
</div>
</div>
{/* Row 4b: User Groups */}
@@ -303,9 +310,9 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
{/* Row 5: Password */}
<div style={{ marginBottom:16 }}>
{lbl('Password',
(!isEdit) || (isEdit && pwEnabled),
isEdit && pwEnabled,
isEdit && !pwEnabled ? '(not changing — click Reset Password to set a new one)' :
!isEdit ? `(blank = ${userPass})` : null
!isEdit ? `(optional — blank uses system default)` : null
)}
<div style={{ opacity: pwEnabled ? 1 : 0.55 }}>
<PasswordInput
@@ -335,9 +342,6 @@ function UserForm({ user, userPass, allUserGroups, onDone, onCancel, isMobile, o
Cancel Reset
</button>
)}
<button className="btn btn-secondary" onClick={onCancel} style={{ marginLeft:'auto' }}>
Cancel
</button>
</div>
{/* Row 7 (edit only): Last login + must change password */}
@@ -393,7 +397,7 @@ function BulkImportForm({ userPass, allUserGroups, onCreated }) {
finally { setLoading(false); }
};
const codeStyle = { fontSize:12, color:'var(--text-secondary)', display:'block', background:'var(--surface)', padding:'6px 8px', borderRadius:4, border:'1px solid var(--border)', whiteSpace:'pre', fontFamily:'monospace', marginBottom:4 };
const codeStyle = { fontSize:12, color:'var(--text-secondary)', display:'block', background:'var(--surface)', padding:'6px 8px', borderRadius:4, border:'1px solid var(--border)', whiteSpace:'pre-wrap', overflowWrap:'anywhere', fontFamily:'monospace', marginBottom:4 };
return (
<div style={{ maxWidth:580, display:'flex', flexDirection:'column', gap:16 }}>
@@ -444,7 +448,7 @@ function BulkImportForm({ userPass, allUserGroups, onCreated }) {
</ul>
</div>
<p style={{ color:'var(--text-tertiary)', marginTop:2 }}>
Optional field defaults: password = <strong>{userPass}</strong>, role = member, usergroup = (none), minor = (none)
Optional field defaults: password = <strong>{userPass}</strong>, role = member, usergroup = (none)
</p>
</div>
)}