From 9c263e7e8df8b0da7d68a5b0aa3ba4aea416c742 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Tue, 31 Mar 2026 13:51:47 -0400 Subject: [PATCH] v0.12.47 Add Child alias update --- backend/package.json | 2 +- build.sh | 2 +- frontend/package.json | 2 +- .../src/components/AddChildAliasModal.jsx | 195 +++++++++++++++++ frontend/src/components/NavDrawer.jsx | 14 +- frontend/src/components/ProfileModal.jsx | 200 +----------------- frontend/src/pages/Chat.jsx | 101 ++++++--- 7 files changed, 288 insertions(+), 228 deletions(-) create mode 100644 frontend/src/components/AddChildAliasModal.jsx diff --git a/backend/package.json b/backend/package.json index cb0135e..a0fa40d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "rosterchirp-backend", - "version": "0.12.46", + "version": "0.12.47", "description": "RosterChirp backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index b705232..c756507 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.12.46}" +VERSION="${1:-0.12.47}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="rosterchirp" diff --git a/frontend/package.json b/frontend/package.json index 9779498..bc2f70c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "rosterchirp-frontend", - "version": "0.12.46", + "version": "0.12.47", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/AddChildAliasModal.jsx b/frontend/src/components/AddChildAliasModal.jsx new file mode 100644 index 0000000..543e595 --- /dev/null +++ b/frontend/src/components/AddChildAliasModal.jsx @@ -0,0 +1,195 @@ +import { useState, useEffect } from 'react'; +import { useToast } from '../contexts/ToastContext.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); + + useEffect(() => { + api.getAliases().then(({ aliases }) => setAliases(aliases || [])).catch(() => {}); + }, []); + + const set = k => e => setForm(p => ({ ...p, [k]: e.target.value })); + + const resetForm = () => { + setEditingAlias(null); + setForm({ firstName: '', lastName: '', dob: '', phone: '', email: '' }); + setAvatarFile(null); + }; + + const handleSelectAlias = (a) => { + if (editingAlias?.id === a.id) { resetForm(); return; } + setEditingAlias(a); + setForm({ + firstName: a.first_name || '', + lastName: a.last_name || '', + dob: a.date_of_birth ? a.date_of_birth.slice(0, 10) : '', + phone: a.phone || '', + email: a.email || '', + }); + setAvatarFile(null); + }; + + const handleSave = async () => { + if (!form.firstName.trim() || !form.lastName.trim()) + return toast('First and last name required', 'error'); + setSaving(true); + try { + if (editingAlias) { + await api.updateAlias(editingAlias.id, { + firstName: form.firstName.trim(), + lastName: form.lastName.trim(), + dateOfBirth: form.dob || null, + phone: form.phone || null, + email: form.email || null, + }); + if (avatarFile) await api.uploadAliasAvatar(editingAlias.id, avatarFile); + toast('Child alias updated', 'success'); + } else { + const { alias } = await api.createAlias({ + firstName: form.firstName.trim(), + lastName: form.lastName.trim(), + dateOfBirth: form.dob || null, + phone: form.phone || null, + email: form.email || null, + }); + if (avatarFile) await api.uploadAliasAvatar(alias.id, avatarFile); + toast('Child alias added', 'success'); + } + const { aliases: fresh } = await api.getAliases(); + setAliases(fresh || []); + resetForm(); + } catch (e) { + toast(e.message, 'error'); + } finally { + setSaving(false); + } + }; + + const handleDelete = async (e, aliasId) => { + e.stopPropagation(); + try { + await api.deleteAlias(aliasId); + setAliases(prev => prev.filter(a => a.id !== aliasId)); + if (editingAlias?.id === aliasId) resetForm(); + toast('Child alias removed', 'success'); + } catch (err) { toast(err.message, 'error'); } + }; + + const lbl = (text, required) => ( + + ); + + return ( +
e.target === e.currentTarget && onClose()}> +
+ + {/* Header */} +
+

Add Child Alias

+ +
+ + {/* Existing aliases list */} + {aliases.length > 0 && ( +
+
+ Your Children — click to edit +
+
+ {aliases.map((a, i) => ( +
handleSelectAlias(a)} + style={{ + display: 'flex', alignItems: 'center', gap: 10, + padding: '9px 12px', cursor: 'pointer', + borderBottom: i < aliases.length - 1 ? '1px solid var(--border)' : 'none', + background: editingAlias?.id === a.id ? 'var(--primary-light)' : 'transparent', + }} + > + + {a.first_name} {a.last_name} + + {a.date_of_birth && ( + + {a.date_of_birth.slice(0, 10)} + + )} + +
+ ))} +
+
+ )} + + {/* Form section label */} +
+ {editingAlias + ? `Editing: ${editingAlias.first_name} ${editingAlias.last_name}` + : 'New Child Alias'} +
+ + {/* Form */} +
+
+
+ {lbl('First Name', true)} + +
+
+ {lbl('Last Name', true)} + +
+
+ {lbl('Date of Birth')} + +
+
+ {lbl('Phone')} + +
+
+
+ {lbl('Email (optional)')} + +
+
+ {lbl('Avatar (optional)')} + setAvatarFile(e.target.files?.[0] || null)} /> +
+
+ {editingAlias && ( + + )} + +
+
+ +
+
+ ); +} diff --git a/frontend/src/components/NavDrawer.jsx b/frontend/src/components/NavDrawer.jsx index 4fc6f4b..856ca2b 100644 --- a/frontend/src/components/NavDrawer.jsx +++ b/frontend/src/components/NavDrawer.jsx @@ -13,13 +13,14 @@ const NAV_ICON = { settings: , }; -export default function NavDrawer({ open, onClose, onMessages, onGroupMessages, onSchedule, onScheduleManager, onBranding, onSettings, onUsers, onGroupManager, onHostPanel, features = {}, currentPage = 'chat', isMobile = false, unreadMessages = false, unreadGroupMessages = false }) { +export default function NavDrawer({ open, onClose, onMessages, onGroupMessages, onSchedule, onScheduleManager, onBranding, onSettings, onUsers, onGroupManager, onHostPanel, onAddChild, features = {}, currentPage = 'chat', isMobile = false, unreadMessages = false, unreadGroupMessages = false }) { const { user } = useAuth(); const drawerRef = useRef(null); const isAdmin = user?.role === 'admin'; const userGroupIds = features.userGroupMemberships || []; const canAccessTools = isAdmin || user?.role === 'manager' || (features.teamToolManagers || []).some(gid => userGroupIds.includes(gid)); const hasUserGroups = userGroupIds.length > 0; + const showAddChild = features.loginType === 'guardian_only' && features.inGuardiansGroup; useEffect(() => { if (!open) return; @@ -80,11 +81,16 @@ export default function NavDrawer({ open, onClose, onMessages, onGroupMessages, )} {/* Tools section */} - {canAccessTools && ( + {(canAccessTools || showAddChild) && ( <>
Tools
- {item(NAV_ICON.users, 'User Manager', onUsers, { active: currentPage === 'users' })} - {features.groupManager && item(NAV_ICON.groups, 'Group Manager', onGroupManager, { active: currentPage === 'groups' })} + {canAccessTools && item(NAV_ICON.users, 'User Manager', onUsers, { active: currentPage === 'users' })} + {canAccessTools && features.groupManager && item(NAV_ICON.groups, 'Group Manager', onGroupManager, { active: currentPage === 'groups' })} + {showAddChild && item( + , + 'Add Child Aliase', + onAddChild + )} )} diff --git a/frontend/src/components/ProfileModal.jsx b/frontend/src/components/ProfileModal.jsx index d775c8b..8d83dd6 100644 --- a/frontend/src/components/ProfileModal.jsx +++ b/frontend/src/components/ProfileModal.jsx @@ -23,7 +23,7 @@ export default function ProfileModal({ onClose }) { const [newPw, setNewPw] = useState(''); const [confirmPw, setConfirmPw] = useState(''); const [loading, setLoading] = useState(false); - const [tab, setTab] = useState('profile'); // 'profile' | 'password' | 'notifications' | 'appearance' | 'add-child' + const [tab, setTab] = useState('profile'); // 'profile' | 'password' | 'notifications' | 'appearance' const [pushTesting, setPushTesting] = useState(false); const [pushResult, setPushResult] = useState(null); const [notifPermission, setNotifPermission] = useState( @@ -36,20 +36,8 @@ export default function ProfileModal({ onClose }) { const [hideAdminTag, setHideAdminTag] = useState(!!user?.hide_admin_tag); const [allowDm, setAllowDm] = useState(user?.allow_dm !== 0); - // Minor age protection state - const [loginType, setLoginType] = useState('all_ages'); - const [guardiansGroupId,setGuardiansGroupId] = useState(null); - const [showAddChild, setShowAddChild] = useState(false); - const [aliases, setAliases] = useState([]); - // Add Child form state - const [childList, setChildList] = useState([]); // pending aliases to add - const [childForm, setChildForm] = useState({ firstName:'', lastName:'', email:'', dob:'', phone:'' }); - const [childFormAvatar, setChildFormAvatar] = useState(null); - const [childSaving, setChildSaving] = useState(false); - // Mixed Age: minor user search - const [minorSearch, setMinorSearch] = useState(''); - const [minorResults, setMinorResults] = useState([]); - const [selectedMinor, setSelectedMinor] = useState(null); + // Minor age protection — DOB/phone display only + const [loginType, setLoginType] = useState('all_ages'); const savedScale = parseFloat(localStorage.getItem(LS_FONT_KEY)); const [fontScale, setFontScale] = useState( @@ -62,32 +50,13 @@ export default function ProfileModal({ onClose }) { return () => window.removeEventListener('resize', onResize); }, []); - // Load login type + check if user is in guardians group + // Load login type for DOB/phone field visibility useEffect(() => { - Promise.all([api.getSettings(), api.getMyUserGroups()]).then(([{ settings: s }, { userGroups }]) => { - const lt = s.feature_login_type || 'all_ages'; - const gid = parseInt(s.feature_guardians_group_id); - setLoginType(lt); - setGuardiansGroupId(gid || null); - if (lt === 'guardian_only') { - // In guardian_only mode all authenticated users are guardians — always show Add Child - setShowAddChild(true); - } else if (lt === 'mixed_age' && gid) { - const inGroup = (userGroups || []).some(g => g.id === gid); - setShowAddChild(inGroup); - } + api.getSettings().then(({ settings: s }) => { + setLoginType(s.feature_login_type || 'all_ages'); }).catch(() => {}); - api.getAliases().then(({ aliases }) => setAliases(aliases || [])).catch(() => {}); }, []); - useEffect(() => { - if (loginType === 'mixed_age' && minorSearch.length >= 1) { - api.searchMinorUsers(minorSearch).then(({ users }) => setMinorResults(users || [])).catch(() => {}); - } else { - setMinorResults([]); - } - }, [minorSearch, loginType]); - const applyFontScale = (val) => { setFontScale(val); document.documentElement.style.setProperty('--font-scale', val); @@ -109,46 +78,6 @@ export default function ProfileModal({ onClose }) { } }; - const handleSaveChildren = async () => { - if (childList.length === 0) return; - setChildSaving(true); - try { - if (loginType === 'mixed_age') { - // Link each selected minor - for (const minor of childList) { - await api.linkMinor(minor.id); - } - toast('Guardian link request sent — awaiting manager approval', 'success'); - } else { - // Create aliases - for (const child of childList) { - const { alias } = await api.createAlias({ firstName: child.firstName, lastName: child.lastName, email: child.email, dateOfBirth: child.dob, phone: child.phone }); - if (child.avatarFile) { - await api.uploadAliasAvatar(alias.id, child.avatarFile); - } - } - toast('Children saved', 'success'); - const { aliases: fresh } = await api.getAliases(); - setAliases(fresh || []); - } - setChildList([]); - setChildForm({ firstName:'', lastName:'', email:'', dob:'', phone:'' }); - setSelectedMinor(null); - } catch (e) { - toast(e.message, 'error'); - } finally { - setChildSaving(false); - } - }; - - const handleRemoveAlias = async (aliasId) => { - try { - await api.deleteAlias(aliasId); - setAliases(prev => prev.filter(a => a.id !== aliasId)); - toast('Child removed', 'success'); - } catch (e) { toast(e.message, 'error'); } - }; - const handleAvatarUpload = async (e) => { const file = e.target.files?.[0]; if (!file) return; @@ -219,7 +148,6 @@ export default function ProfileModal({ onClose }) { - {showAddChild && } @@ -447,122 +375,6 @@ export default function ProfileModal({ onClose }) { )} - {tab === 'add-child' && ( -
- {/* Existing saved aliases */} - {aliases.length > 0 && ( -
-
Saved Children
-
- {aliases.map((a, i) => ( -
- {a.first_name} {a.last_name} - {a.date_of_birth && {a.date_of_birth.slice(0,10)}} - -
- ))} -
-
- )} - - {loginType === 'guardian_only' ? ( - <> -
Add a Child
-
-
- - setChildForm(p=>({...p,firstName:e.target.value}))} autoComplete="off" autoCapitalize="words" /> -
-
- - setChildForm(p=>({...p,lastName:e.target.value}))} autoComplete="off" autoCapitalize="words" /> -
-
- - setChildForm(p=>({...p,dob:e.target.value}))} autoComplete="off" /> -
-
- - setChildForm(p=>({...p,phone:e.target.value}))} autoComplete="off" /> -
-
- - setChildForm(p=>({...p,email:e.target.value}))} autoComplete="off" /> -
-
- - setChildForm(p=>({...p,avatarFile:e.target.files?.[0]||null}))} /> -
-
- - {childList.length > 0 && ( -
- {childList.map((c, i) => ( -
- {c.firstName} {c.lastName} - Pending save - -
- ))} -
- )} - - ) : loginType === 'mixed_age' ? ( - <> -
Link a Child Account
-

Search for a minor user account to link to your guardian profile. The link requires manager approval.

- setMinorSearch(e.target.value)} autoComplete="off" /> - {minorResults.length > 0 && ( -
- {minorResults.map(u => ( -
{ setSelectedMinor(u); setMinorSearch(''); setMinorResults([]); }}> - {u.first_name} {u.last_name} - {u.date_of_birth && {u.date_of_birth.slice(0,10)}} -
- ))} -
- )} - {selectedMinor && ( -
-
{selectedMinor.first_name} {selectedMinor.last_name}
- {selectedMinor.date_of_birth &&
{selectedMinor.date_of_birth.slice(0,10)}
} -
- - -
-
- )} - {childList.length > 0 && ( -
- {childList.map((c, i) => ( -
- {c.first_name || c.firstName} {c.last_name || c.lastName} - Pending approval - -
- ))} -
- )} - - ) : null} - - -
- )} - {tab === 'appearance' && (
diff --git a/frontend/src/pages/Chat.jsx b/frontend/src/pages/Chat.jsx index b0adb27..176faf2 100644 --- a/frontend/src/pages/Chat.jsx +++ b/frontend/src/pages/Chat.jsx @@ -16,6 +16,7 @@ import GlobalBar from '../components/GlobalBar.jsx'; import AboutModal from '../components/AboutModal.jsx'; import HelpModal from '../components/HelpModal.jsx'; import NavDrawer from '../components/NavDrawer.jsx'; +import AddChildAliasModal from '../components/AddChildAliasModal.jsx'; import SchedulePage from '../components/SchedulePage.jsx'; import MobileGroupManager from '../components/MobileGroupManager.jsx'; import './Chat.css'; @@ -48,6 +49,9 @@ export default function Chat() { const [drawerOpen, setDrawerOpen] = useState(false); const [features, setFeatures] = useState({ branding: false, groupManager: false, scheduleManager: false, appType: 'RosterChirp-Chat', teamToolManagers: [], isHostDomain: false, msgPublic: true, msgGroup: true, msgPrivateGroup: true, msgU2U: true }); const [helpDismissed, setHelpDismissed] = useState(true); // true until status loaded + const [addChildPending, setAddChildPending] = useState(false); // defer add-child popup until help closes + const addChildCheckedRef = useRef(false); // only auto-check aliases once per session + const modalRef = useRef(null); // always reflects current modal value in async callbacks const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const [showSidebar, setShowSidebar] = useState(true); @@ -80,28 +84,31 @@ export default function Chat() { // Keep groupsRef in sync so visibility/reconnect handlers can read current groups useEffect(() => { groupsRef.current = groups; }, [groups]); - // Load feature flags + current user's group memberships on mount + // Load feature flags + current user's group memberships on mount (combined for consistent inGuardiansGroup) const loadFeatures = useCallback(() => { - api.getSettings().then(({ settings }) => { - setFeatures(prev => ({ - ...prev, - branding: settings.feature_branding === 'true', - groupManager: settings.feature_group_manager === 'true', - scheduleManager: settings.feature_schedule_manager === 'true', - appType: settings.app_type || 'RosterChirp-Chat', - teamToolManagers: JSON.parse(settings.team_tool_managers || settings.team_group_managers || '[]'), - isHostDomain: settings.is_host_domain === 'true', - msgPublic: settings.feature_msg_public !== 'false', - msgGroup: settings.feature_msg_group !== 'false', - msgPrivateGroup: settings.feature_msg_private_group !== 'false', - msgU2U: settings.feature_msg_u2u !== 'false', - loginType: settings.feature_login_type || 'all_ages', - playersGroupId: settings.feature_players_group_id ? parseInt(settings.feature_players_group_id) : null, - })); - }).catch(() => {}); - api.getMyUserGroups().then(({ userGroups }) => { - setFeatures(prev => ({ ...prev, userGroupMemberships: (userGroups || []).map(g => g.id) })); - }).catch(() => {}); + Promise.all([api.getSettings(), api.getMyUserGroups()]) + .then(([{ settings: s }, { userGroups }]) => { + const memberships = (userGroups || []).map(g => g.id); + const guardiansGroupId = s.feature_guardians_group_id ? parseInt(s.feature_guardians_group_id) : null; + setFeatures(prev => ({ + ...prev, + branding: s.feature_branding === 'true', + groupManager: s.feature_group_manager === 'true', + scheduleManager: s.feature_schedule_manager === 'true', + appType: s.app_type || 'RosterChirp-Chat', + teamToolManagers: JSON.parse(s.team_tool_managers || s.team_group_managers || '[]'), + isHostDomain: s.is_host_domain === 'true', + msgPublic: s.feature_msg_public !== 'false', + msgGroup: s.feature_msg_group !== 'false', + msgPrivateGroup: s.feature_msg_private_group !== 'false', + msgU2U: s.feature_msg_u2u !== 'false', + loginType: s.feature_login_type || 'all_ages', + playersGroupId: s.feature_players_group_id ? parseInt(s.feature_players_group_id) : null, + guardiansGroupId, + userGroupMemberships: memberships, + inGuardiansGroup: guardiansGroupId ? memberships.includes(guardiansGroupId) : false, + })); + }).catch(() => {}); }, []); useEffect(() => { @@ -110,6 +117,35 @@ export default function Chat() { return () => window.removeEventListener('rosterchirp:settings-changed', loadFeatures); }, [loadFeatures]); + // Keep modalRef in sync so async callbacks can read current modal without stale closure + useEffect(() => { modalRef.current = modal; }, [modal]); + + // Auto-popup Add Child Alias modal when guardian_only user has no aliases yet + useEffect(() => { + if (addChildCheckedRef.current) return; + if (features.loginType !== 'guardian_only' || !features.inGuardiansGroup) return; + addChildCheckedRef.current = true; + api.getAliases().then(({ aliases }) => { + if (!(aliases || []).length) { + if (modalRef.current === 'help') { + setAddChildPending(true); // defer until help closes + } else if (!modalRef.current) { + setModal('addchild'); + } + } + }).catch(() => {}); + }, [features.loginType, features.inGuardiansGroup]); + + // Close help — open deferred add-child popup if pending + const handleHelpClose = useCallback(() => { + if (addChildPending) { + setAddChildPending(false); + setModal('addchild'); + } else { + setModal(null); + } + }, [addChildPending]); + // Register / refresh push subscription — FCM for Android/Chrome, Web Push for iOS useEffect(() => { if (!('serviceWorker' in navigator)) return; @@ -601,12 +637,14 @@ export default function Chat() { onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} + onAddChild={() => { setDrawerOpen(false); setModal('addchild'); }} features={features} currentPage={page} isMobile={isMobile} unreadMessages={hasUnreadChat} unreadGroupMessages={hasUnreadGroupMessages} /> {modal === 'profile' && setModal(null)} />} {modal === 'settings' && setModal(null)} onFeaturesChanged={setFeatures} />} {modal === 'branding' && setModal(null)} />} - {modal === 'help' && setModal(null)} dismissed={helpDismissed} />} + {modal === 'help' && } + {modal === 'addchild' && setModal(null)} />} {modal === 'about' && setModal(null)} />}
@@ -630,12 +668,14 @@ export default function Chat() { onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} + onAddChild={() => { setDrawerOpen(false); setModal('addchild'); }} features={features} currentPage={page} isMobile={isMobile} unreadMessages={hasUnreadChat} unreadGroupMessages={hasUnreadGroupMessages} /> {modal === 'profile' && setModal(null)} />} {modal === 'settings' && setModal(null)} onFeaturesChanged={setFeatures} />} {modal === 'branding' && setModal(null)} />} - {modal === 'help' && setModal(null)} dismissed={helpDismissed} />} + {modal === 'help' && } + {modal === 'addchild' && setModal(null)} />} {modal === 'about' && setModal(null)} />}
@@ -689,12 +729,14 @@ export default function Chat() { onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} + onAddChild={() => { setDrawerOpen(false); setModal('addchild'); }} features={features} currentPage={page} isMobile={isMobile} unreadMessages={hasUnreadChat} unreadGroupMessages={hasUnreadGroupMessages} /> {modal === 'profile' && setModal(null)} />} {modal === 'settings' && setModal(null)} onFeaturesChanged={setFeatures} />} {modal === 'branding' && setModal(null)} />} - {modal === 'help' && setModal(null)} dismissed={helpDismissed} />} + {modal === 'help' && } + {modal === 'addchild' && setModal(null)} />} {modal === 'about' && setModal(null)} />} {modal === 'newchat' && setModal(null)} onCreated={(g) => { loadGroups(); setModal(null); setActiveGroupId(g.id); setPage('chat'); }} />} @@ -721,6 +763,7 @@ export default function Chat() { onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} + onAddChild={() => { setDrawerOpen(false); setModal('addchild'); }} features={features} currentPage={page} isMobile={isMobile} @@ -728,7 +771,8 @@ export default function Chat() { {modal === 'profile' && setModal(null)} />} {modal === 'settings' && setModal(null)} onFeaturesChanged={setFeatures} />} {modal === 'branding' && setModal(null)} />} - {modal === 'help' && setModal(null)} dismissed={helpDismissed} />} + {modal === 'help' && } + {modal === 'addchild' && setModal(null)} />} {modal === 'about' && setModal(null)} />} @@ -760,6 +804,7 @@ export default function Chat() { onSettings={() => { setDrawerOpen(false); setModal('settings'); }} onUsers={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('users'); }} onHostPanel={() => { setDrawerOpen(false); setActiveGroupId(null); setChatHasText(false); setPage('hostpanel'); }} + onAddChild={() => { setDrawerOpen(false); setModal('addchild'); }} features={features} currentPage={page} isMobile={isMobile} @@ -774,7 +819,8 @@ export default function Chat() { )} {modal === 'about' && setModal(null)} />} - {modal === 'help' && setModal(null)} dismissed={helpDismissed} />} + {modal === 'help' && } + {modal === 'addchild' && setModal(null)} />} ); @@ -842,7 +888,8 @@ export default function Chat() { {modal === 'newchat' && setModal(null)} onCreated={(g) => { loadGroups(); setModal(null); setActiveGroupId(g.id); }} />} {modal === 'about' && setModal(null)} />} - {modal === 'help' && setModal(null)} dismissed={helpDismissed} />} + {modal === 'help' && } + {modal === 'addchild' && setModal(null)} />} ); }