import React, { useCallback, useMemo, useState, useEffect } from 'react'; import { Modal, Button, Text, ActionIcon } from '@mantine/core'; import { useMediaQuery } from '@mantine/hooks'; import { useAuth } from '@app/auth/UseSession'; import { isUserAnonymous } from '@app/auth/supabase'; import { useTranslation } from 'react-i18next'; import LocalIcon from '@app/components/shared/LocalIcon'; import Overview from '@app/components/shared/config/configSections/Overview'; import { createSaasConfigNavSections } from '@app/components/shared/config/saasConfigNavSections'; import { NavKey } from '@app/components/shared/config/types'; import { withBasePath } from '@app/constants/app'; import '@app/components/shared/AppConfigModal.css'; import { Z_INDEX_OVER_FULLSCREEN_SURFACE, Z_INDEX_OVER_SETTINGS_MODAL } from '@app/styles/zIndex'; interface AppConfigModalProps { opened: boolean; onClose: () => void; } const AppConfigModal: React.FC = ({ opened, onClose }) => { const isMobile = useMediaQuery("(max-width: 1024px)"); const { signOut, user, creditBalance, refreshCredits } = useAuth(); const { t } = useTranslation(); const [confirmOpen, setConfirmOpen] = useState(false); const [active, setActive] = useState('overview'); const [notice, setNotice] = useState(null); // Check if user can access billing features (non-anonymous users only) const isAnonymous = user ? isUserAnonymous(user) : false; useEffect(() => { const handler = (ev: Event) => { const detail = (ev as CustomEvent).detail as { key?: NavKey } | undefined; if (detail?.key) { setActive(detail.key); } }; window.addEventListener('appConfig:navigate', handler as EventListener); return () => window.removeEventListener('appConfig:navigate', handler as EventListener); }, []); // Listen for notice updates (e.g., "Not enough credits..." next to Plan title) useEffect(() => { const handler = (ev: Event) => { const detail = (ev as CustomEvent).detail as { key?: NavKey; notice?: string } | undefined; if (detail?.notice && (detail?.key ? detail.key === 'plan' : true)) { setNotice(detail.notice); } }; window.addEventListener('appConfig:notice', handler as EventListener); return () => window.removeEventListener('appConfig:notice', handler as EventListener); }, []); // When the modal opens to Plan, proactively refresh credits and log values useEffect(() => { if (!opened) return; if (active !== 'plan') return; console.log('[AppConfigModal] Opening Plan section. Current creditBalance:', creditBalance); (async () => { try { await refreshCredits(); } catch (e) { console.warn('[AppConfigModal] Failed to refresh credits on Plan open:', e); } })(); }, [opened, active]); useEffect(() => { if (!opened) return; if (active !== 'plan') return; console.log('[AppConfigModal] Credit balance updated while viewing Plan:', creditBalance); }, [opened, active, creditBalance]); const colors = useMemo(() => ({ navBg: 'var(--modal-nav-bg)', sectionTitle: 'var(--modal-nav-section-title)', navItem: 'var(--modal-nav-item)', navItemActive: 'var(--modal-nav-item-active)', navItemActiveBg: 'var(--modal-nav-item-active-bg)', contentBg: 'var(--modal-content-bg)', headerBorder: 'var(--modal-header-border)', }), []); const isDev = process.env.NODE_ENV === 'development'; const openLogoutConfirm = useCallback(() => setConfirmOpen(true), []); // Left navigation structure and icons const configNavSections = useMemo( () => createSaasConfigNavSections(Overview, openLogoutConfirm, { isDev, isAnonymous, t, }), [openLogoutConfirm, isDev, isAnonymous, t], ); const activeLabel = useMemo(() => { for (const section of configNavSections) { const found = section.items.find(i => i.key === active); if (found) return found.label; } return ''; }, [configNavSections, active]); const activeComponent = useMemo(() => { for (const section of configNavSections) { const found = section.items.find(i => i.key === active); if (found) return found.component; } return null; }, [configNavSections, active]); return ( <>
{/* Left navigation */}
{configNavSections.map(section => (
{!isMobile && ( {section.title} )}
{section.items.map(item => { const isActive = active === item.key; const color = isActive ? colors.navItemActive : colors.navItem; const iconSize = isMobile ? 28 : 18; return (
setActive(item.key)} className={`modal-nav-item ${isMobile ? 'mobile' : ''}`} style={{ background: isActive ? colors.navItemActiveBg : 'transparent', }} > {!isMobile && ( {item.label} )}
); })}
))}
{/* Right content */}
{/* Sticky header with section title and small close button */}
{activeLabel} {active === 'plan' && notice ? ( – {notice} ) : null}
{activeComponent}
{/* Confirm logout modal */} setConfirmOpen(false)} title="Sign out" centered zIndex={Z_INDEX_OVER_SETTINGS_MODAL} >
Are you sure you want to sign out?
); }; export default AppConfigModal;