v0.12.49 family rules update

This commit is contained in:
2026-04-01 16:26:58 -04:00
parent 7031979571
commit 3910063ed3
6 changed files with 258 additions and 26 deletions

View File

@@ -1,17 +1,34 @@
import { useState, useEffect } from 'react';
import { useToast } from '../contexts/ToastContext.jsx';
import { useAuth } from '../contexts/AuthContext.jsx';
import { api } from '../utils/api.js';
export default function AddChildAliasModal({ onClose }) {
const toast = useToast();
const [aliases, setAliases] = useState([]);
const [editingAlias, setEditingAlias] = useState(null); // null = new entry
const [form, setForm] = useState({ firstName: '', lastName: '', dob: '', phone: '', email: '' });
const [avatarFile, setAvatarFile] = useState(null);
const [saving, setSaving] = useState(false);
const { user: currentUser } = useAuth();
const [aliases, setAliases] = useState([]);
const [editingAlias, setEditingAlias] = useState(null); // null = new entry
const [form, setForm] = useState({ firstName: '', lastName: '', dob: '', phone: '', email: '' });
const [avatarFile, setAvatarFile] = useState(null);
const [saving, setSaving] = useState(false);
// Partner state
const [partner, setPartner] = useState(null);
const [selectedPartnerId, setSelectedPartnerId] = useState('');
const [allUsers, setAllUsers] = useState([]);
const [savingPartner, setSavingPartner] = useState(false);
useEffect(() => {
api.getAliases().then(({ aliases }) => setAliases(aliases || [])).catch(() => {});
Promise.all([
api.getAliases(),
api.getPartner(),
api.searchUsers(''),
]).then(([aliasRes, partnerRes, usersRes]) => {
setAliases(aliasRes.aliases || []);
setPartner(partnerRes.partner || null);
setSelectedPartnerId(partnerRes.partner?.id?.toString() || '');
setAllUsers((usersRes.users || []).filter(u => u.id !== currentUser?.id));
}).catch(() => {});
}, []);
const set = k => e => setForm(p => ({ ...p, [k]: e.target.value }));
@@ -35,6 +52,27 @@ export default function AddChildAliasModal({ onClose }) {
setAvatarFile(null);
};
const handleSavePartner = async () => {
setSavingPartner(true);
try {
if (!selectedPartnerId) {
await api.removePartner();
setPartner(null);
toast('Spouse/Partner removed', 'success');
} else {
const { partner: p } = await api.setPartner(parseInt(selectedPartnerId));
setPartner(p);
const { aliases: fresh } = await api.getAliases();
setAliases(fresh || []);
toast('Spouse/Partner saved', 'success');
}
} catch (e) {
toast(e.message, 'error');
} finally {
setSavingPartner(false);
}
};
const handleSave = async () => {
if (!form.firstName.trim() || !form.lastName.trim())
return toast('First and last name required', 'error');
@@ -93,7 +131,7 @@ export default function AddChildAliasModal({ onClose }) {
{/* Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
<h2 className="modal-title" style={{ margin: 0 }}>Add Child Alias</h2>
<h2 className="modal-title" style={{ margin: 0 }}>Family Manager</h2>
<button className="btn-icon" onClick={onClose} aria-label="Close">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
@@ -101,6 +139,37 @@ export default function AddChildAliasModal({ onClose }) {
</button>
</div>
{/* Spouse/Partner section */}
<div style={{ marginBottom: 16 }}>
{lbl('Spouse/Partner')}
<div style={{ display: 'flex', gap: 8 }}>
<select
className="input"
style={{ flex: 1 }}
value={selectedPartnerId}
onChange={e => setSelectedPartnerId(e.target.value)}
>
<option value=""> None </option>
{allUsers.map(u => (
<option key={u.id} value={u.id}>{u.display_name || u.name}</option>
))}
</select>
<button
className="btn btn-primary"
onClick={handleSavePartner}
disabled={savingPartner}
style={{ whiteSpace: 'nowrap' }}
>
{savingPartner ? 'Saving…' : 'Save'}
</button>
</div>
{partner && (
<div className="text-sm" style={{ color: 'var(--text-secondary)', marginTop: 4 }}>
Linked with {partner.display_name || partner.name}
</div>
)}
</div>
{/* Existing aliases list */}
{aliases.length > 0 && (
<div style={{ marginBottom: 16 }}>