v0.12.47 Add Child alias update

This commit is contained in:
2026-03-31 13:51:47 -04:00
parent 350bb25ecd
commit 9c263e7e8d
7 changed files with 288 additions and 228 deletions

View File

@@ -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' && <ProfileModal onClose={() => setModal(null)} />}
{modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />}
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
{modal === 'help' && <HelpModal onClose={handleHelpClose} dismissed={helpDismissed} />}
{modal === 'addchild' && <AddChildAliasModal onClose={() => setModal(null)} />}
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
</div>
@@ -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' && <ProfileModal onClose={() => setModal(null)} />}
{modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />}
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
{modal === 'help' && <HelpModal onClose={handleHelpClose} dismissed={helpDismissed} />}
{modal === 'addchild' && <AddChildAliasModal onClose={() => setModal(null)} />}
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
</div>
@@ -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' && <ProfileModal onClose={() => setModal(null)} />}
{modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />}
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
{modal === 'help' && <HelpModal onClose={handleHelpClose} dismissed={helpDismissed} />}
{modal === 'addchild' && <AddChildAliasModal onClose={() => setModal(null)} />}
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
{modal === 'newchat' && <NewChatModal features={features} onClose={() => 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' && <ProfileModal onClose={() => setModal(null)} />}
{modal === 'settings' && <SettingsModal onClose={() => setModal(null)} onFeaturesChanged={setFeatures} />}
{modal === 'branding' && <BrandingModal onClose={() => setModal(null)} />}
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
{modal === 'help' && <HelpModal onClose={handleHelpClose} dismissed={helpDismissed} />}
{modal === 'addchild' && <AddChildAliasModal onClose={() => setModal(null)} />}
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
</div>
@@ -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() {
</div>
)}
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
{modal === 'help' && <HelpModal onClose={handleHelpClose} dismissed={helpDismissed} />}
{modal === 'addchild' && <AddChildAliasModal onClose={() => setModal(null)} />}
</div>
);
@@ -842,7 +888,8 @@ export default function Chat() {
{modal === 'newchat' && <NewChatModal features={features} onClose={() => setModal(null)} onCreated={(g) => { loadGroups(); setModal(null); setActiveGroupId(g.id); }} />}
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
{modal === 'help' && <HelpModal onClose={() => setModal(null)} dismissed={helpDismissed} />}
{modal === 'help' && <HelpModal onClose={handleHelpClose} dismissed={helpDismissed} />}
{modal === 'addchild' && <AddChildAliasModal onClose={() => setModal(null)} />}
</div>
);
}