// App — async session init, API-backed auth const NAV = { verwaltung: [ {section:'ARBEITSBEREICH'}, {k:'dashboard',icon:'dashboard',l:'Dashboard'}, {k:'liegenschaften',icon:'building',l:'Liegenschaften'}, {k:'meldungen',icon:'alert',l:'Schadens­meldungen',count:'12',dot:true}, {k:'wartung',icon:'wrench',l:'Wartungs­aufträge',count:'4'}, {section:'KOMMUNIKATION'}, {k:'chat',icon:'chat',l:'Chat',count:'3'}, {k:'news',icon:'pin',l:'Schwarzes Brett'}, {k:'kalender',icon:'calendar',l:'Kalender'}, {section:'ADMINISTRATION'}, {k:'docs',icon:'doc',l:'Dokumente'}, {k:'mieter',icon:'users',l:'Mieter & Einladungen'}, {k:'einstellungen',icon:'settings',l:'Einstellungen'}, ], mieter: [ {section:'IHRE WOHNUNG'}, {k:'dashboard',icon:'home',l:'Übersicht'}, {k:'meldungen',icon:'alert',l:'Meine Meldungen',count:'2'}, {k:'docs',icon:'doc',l:'Meine Dokumente'}, {section:'LIEGENSCHAFT'}, {k:'chat',icon:'chat',l:'Liegenschafts-Chat',count:'11'}, {k:'news',icon:'pin',l:'Schwarzes Brett',dot:true}, {k:'kalender',icon:'calendar',l:'Termine'}, {k:'wasch',icon:'washer',l:'Waschküche'}, {section:'KONTO'}, {k:'einstellungen',icon:'settings',l:'Einstellungen'}, ], hauswart: [ {section:'ARBEITSBEREICH'}, {k:'dashboard',icon:'dashboard',l:'Heute'}, {k:'meldungen',icon:'alert',l:'Meldungen',count:'2'}, {k:'wartung',icon:'wrench',l:'Aufträge',count:'6'}, {k:'kalender',icon:'calendar',l:'Kalender'}, {section:'LIEGENSCHAFTEN'}, {k:'liegenschaften',icon:'building',l:'Meine Liegenschaften',count:'4'}, {k:'chat',icon:'chat',l:'Chats',count:'4'}, {section:'KONTO'}, {k:'einstellungen',icon:'settings',l:'Einstellungen'}, ], }; const TONE = {verwaltung:'red', mieter:'gold', hauswart:'green'}; const LoadingScreen = () => (
WIRD GELADEN…
); const App = () => { const [session, setSession] = React.useState(null); const [org, setOrg] = React.useState(null); const [loading, setLoading] = React.useState(true); const [authView, setAuthView] = React.useState('login'); const [page, setPage] = React.useState('dashboard'); const [propId, setPropId] = React.useState(null); const [dataTick, setDataTick] = React.useState(0); const inviteCode = React.useMemo(() => { const m = window.location.hash.match(/[#&]invite=([A-Z0-9]+)/i); return m ? m[1] : null; }, []); // Restore session from token on mount React.useEffect(() => { if (!Store.hasToken()) { setLoading(false); return; } Store.getMe().then(data => { if (data) { setSession(data.session); setOrg(data.org); } setLoading(false); }); }, []); const reload = () => setDataTick(t => t + 1); const ctx = React.useMemo(() => { if (!session) return null; return { session, org, reload, dataTick }; }, [session, org, dataTick]); const handleLogin = (sess) => { setSession(sess); setPage('dashboard'); window.location.hash = ''; Store.getMe().then(d => { if (d) setOrg(d.org); }); }; const handleLogout = async () => { await Store.logout(); setSession(null); setOrg(null); setPage('dashboard'); }; const navigate = (p, arg) => { setPage(p); if (p === 'liegenschaft' && arg) setPropId(arg); }; if (loading) return ; // Invite flow if (inviteCode && !session) return (
); // Auth flow if (!session) return (
{authView==='login' ? setAuthView('register')}/> : setAuthView('login')}/>}
); const role = session.role; const renderPage = () => { if (role==='verwaltung') { if (page==='dashboard') return ; if (page==='liegenschaften')return navigate('liegenschaft',id)} ctx={ctx}/>; if (page==='liegenschaft') return ; if (page==='meldungen') return {}} ctx={ctx}/>; if (page==='wartung') return <>
; if (page==='chat') return ; if (page==='news') return ; if (page==='kalender') return ; if (page==='docs') return ; if (page==='mieter') return ; if (page==='einstellungen') return ; } if (role==='mieter') { if (page==='dashboard') return ; if (page==='meldung-neu') return navigate(t||'dashboard')} ctx={ctx}/>; if (page==='meldungen') return ; if (page==='chat') return ; if (page==='news') return ; if (page==='kalender') return ; if (page==='wasch') return ; if (page==='docs') return ; if (page==='einstellungen') return ; } if (role==='hauswart') { if (page==='dashboard') return ; if (page==='meldungen') return {}} ctx={ctx}/>; if (page==='wartung') return <>
; if (page==='chat') return ; if (page==='kalender') return ; if (page==='liegenschaften')return navigate('liegenschaft',id)} ctx={ctx}/>; if (page==='einstellungen') return ; } return null; }; const nav = NAV[role] || []; return (
ImmoHubSchweiz
Interxion Zürich · All systems OK
{org &&
{org.name}
}
{session.name}
{role}
{renderPage()}
); }; // Mieter list with async invite system const MieterListView = ({ ctx }) => { const [users, setUsers] = React.useState([]); const [pending, setPending] = React.useState([]); const [props, setProps] = React.useState([]); const [showModal, setShowModal] = React.useState(false); const [form, setForm] = React.useState({name:'',email:'',role:'mieter',propId:'',unit:''}); const [genLink, setGenLink] = React.useState(''); const [copied, setCopied] = React.useState(false); const [loadingInvite, setLoadingInvite] = React.useState(false); const upd = (k,v) => setForm(f=>({...f,[k]:v})); const fetchData = () => { Store.getUsers().then(setUsers); Store.getPendingInvites().then(setPending); Store.getProperties().then(setProps); }; React.useEffect(fetchData, [ctx.dataTick]); const nonAdmin = users.filter(u => u.role !== 'verwaltung'); const sendInvite = async () => { if (!form.email||loadingInvite) return; setLoadingInvite(true); try { const inv = await Store.createInvite(form); const link = window.location.origin + window.location.pathname + '#invite=' + inv.code; setGenLink(link); fetchData(); } catch(e) { alert(e.message); } setLoadingInvite(false); }; const copyLink = (link) => { navigator.clipboard.writeText(link).then(()=>{ setCopied(true); setTimeout(()=>setCopied(false),2000); }); }; const closeModal = () => { setShowModal(false); setForm({name:'',email:'',role:'mieter',propId:'',unit:''}); setGenLink(''); }; return ( <> setShowModal(true)}>Einladen}/>
{nonAdmin.length > 0 && ( {nonAdmin.map(u=>( ))}
NameE-MailRolleEinheitSeit
{u.name}
{u.email} {u.unit||'—'} {u.created_at ? new Date(u.created_at).toLocaleDateString('de-CH') : '—'}
)} {nonAdmin.length===0 && pending.length===0 && (
Noch keine Mieter oder Hauswarte eingeladen.
Klicken Sie auf «Einladen» um Einladungslinks zu erstellen.
)} {pending.length > 0 && ( {pending.map(inv=>{ const link = window.location.origin+window.location.pathname+'#invite='+inv.code; return ( ); })}
NameE-MailRolleEinheitErstelltLink
{inv.name||'—'} {inv.email} {inv.unit||'—'} {inv.created_at ? new Date(inv.created_at).toLocaleDateString('de-CH') : '—'}
)}
{showModal && (
e.target===e.currentTarget&&closeModal()}>

Einladung erstellen

{!genLink ? (
upd('name',e.target.value)} placeholder="Vorname Nachname"/>
upd('email',e.target.value)} placeholder="name@email.ch"/>
{props.length>0 && (
upd('unit',e.target.value)} placeholder="z.B. 2.OG links"/>
)}
) : (
EINLADUNGSLINK
{genLink}
Senden Sie diesen Link an {form.email}.
)}
)} ); }; const MieterMeldungen = ({onNav}) => ( <> onNav('meldung-neu')}>Neue Meldung}/>
TicketTitelKategorieStatusGemeldet
SM-0377Kühlschrank-DichtungGeräte17.04.2026
SM-0373Wohnungstür-ScharnierFenster/Türen08.04.2026
); const MieterDocs = () => ( <>
); const EinstellungenView = ({ ctx }) => { const s = ctx?.session; const o = ctx?.org; const [team, setTeam] = React.useState([]); React.useEffect(() => { if (s?.role === 'verwaltung') Store.getUsers().then(setTeam); }, [s?.role]); return ( <>
{[['Name',s?.name],['E-Mail',s?.email],['Rolle',s?.role]].map(([k,v])=>(
{k} {v||'—'}
))}
{team.length > 0 && ( {team.map(u=>( ))}
NameE-MailRolleSeit
{u.name}
{u.email} {u.created_at ? new Date(u.created_at).toLocaleDateString('de-CH') : '—'}
)}
{o && {[['Firmenname',o.name],['Plan',o.plan||'starter'],['Erstellt',o.created_at?new Date(o.created_at).toLocaleDateString('de-CH'):'—']].map(([k,v])=>(
{k} {v||'—'}
))}
}
); }; ReactDOM.createRoot(document.getElementById('root')).render();