// Entry point: sign-in gate, app shell, routing, overlays, mobile tab bar.
import { html, render, useState, useEffect, useRef } from '../vendor/preact-htm.mjs';
import { api, connectEvents } from './api.js';
import { state, update, useStore, navigate, loadBootstrap, applyTheme } from './store.js';
import { Icon, PopoverHost, ModalHost, Spinner, useMobile } from './ui.js';
import { Sidebar } from './sidebar.js';
import { BoardPage } from './board.js';
import { HomePage, MyWorkPage, InboxPage, SearchPage, PeoplePage, AdminPage, WorkspacePage } from './pages.js';
applyTheme();
function App() {
const s = useStore();
const [phase, setPhase] = useState('loading'); // loading | signin | ready
useEffect(() => {
loadBootstrap().then(() => { setPhase('ready'); connectEvents(); }).catch(() => setPhase('signin'));
const out = () => setPhase('signin');
window.addEventListener('wb:signed-out', out);
return () => window.removeEventListener('wb:signed-out', out);
}, []);
// Keyboard: "/" or Cmd/Ctrl+K opens search
useEffect(() => {
const fn = (e) => {
if (/INPUT|TEXTAREA|SELECT/.test(document.activeElement?.tagName) || document.activeElement?.isContentEditable) return;
if ((e.key === 'k' && (e.metaKey || e.ctrlKey)) || e.key === '/') { e.preventDefault(); navigate('/search'); }
};
document.addEventListener('keydown', fn);
return () => document.removeEventListener('keydown', fn);
}, []);
if (phase === 'loading') return html`
`;
if (phase === 'signin') return html`<${SignIn} onDone=${async () => { await loadBootstrap(); connectEvents(); setPhase('ready'); }} />`;
return html``;
}
function Route() {
const r = useStore().route;
useEffect(() => {
const b = r.boardId && state.boards.find((x) => x.id === r.boardId);
document.title = b ? `${b.name} · Waters Boards` : 'Waters Boards';
}, [r.name, r.boardId, state.boards.length]);
switch (r.name) {
case 'board': return html`<${BoardPage} key=${r.boardId} boardId=${r.boardId} viewId=${r.viewId} itemId=${r.itemId} />`;
case 'my-work': return html`<${MyWorkPage} key=${r.q?.user || 'me'} />`;
case 'inbox': return html`<${InboxPage} />`;
case 'search': return html`<${SearchPage} />`;
case 'people': return html`<${PeoplePage} />`;
case 'admin': return html`<${AdminPage} />`;
case 'workspace': return html`<${WorkspacePage} workspaceId=${r.workspaceId} />`;
default: return html`<${HomePage} />`;
}
}
function MobileTabs() {
const s = useStore();
const mobile = useMobile();
if (!mobile || s.route.itemId) return null;
const r = s.route.name;
const Tab = ({ icon, label, to, active, badge, onClick }) => html``;
return html``;
}
function Toasts() {
const s = useStore();
return html`${s.toasts.map((t) => html`
${t.text}
${t.action ? html`` : null}
`)}
`;
}
// ─── Sign in ─────────────────────────────────────────────
function SignIn({ onDone }) {
const [cfg, setCfg] = useState(null);
const [err, setErr] = useState('');
const [mode, setMode] = useState('google');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const btn = useRef();
useEffect(() => { api.get('/api/auth/config').then(setCfg).catch(() => setCfg({})); }, []);
useEffect(() => {
if (!cfg?.googleClientId || mode !== 'google') return;
let tries = 0;
const init = () => {
if (!window.google?.accounts?.id) { if (tries++ < 50) setTimeout(init, 100); else setErr("Couldn't load Google sign-in. Check your connection."); return; }
window.google.accounts.id.initialize({
client_id: cfg.googleClientId,
callback: async (resp) => {
setErr('');
try { await api.post('/api/auth/google', { credential: resp.credential }); onDone(); }
catch (e) { setErr(e.message); }
},
hd: cfg.allowedDomains?.length === 1 ? cfg.allowedDomains[0] : undefined,
ux_mode: 'popup',
});
window.google.accounts.id.renderButton(btn.current, { theme: 'outline', size: 'large', text: 'continue_with', shape: 'pill', width: 280 });
};
init();
}, [cfg, mode]);
const submitPassword = async (e) => {
e.preventDefault(); setErr('');
try { await api.post(mode === 'dev' ? '/api/auth/dev' : '/api/auth/password', { email, password, name: email.split('@')[0] }); onDone(); }
catch (e2) { setErr(e2.message); }
};
if (!cfg) return html`<${Spinner} />
`;
return html`
Waters Boards
Sign in with your ${cfg.allowedDomains?.map((d) => '@' + d).join(' or ')} account
${mode === 'google' ? html`
${cfg.googleClientId ? html`
` : html`
Google sign-in isn't set up on this server yet (GOOGLE_CLIENT_ID is empty). An admin can still sign in with a password.
`}
${cfg.passwordLogin ? html`
` : null}
${cfg.devLogin ? html`
` : null}
` : html`
`}
${err ? html`
${err}
` : null}
`;
}
render(html`<${App} />`, document.getElementById('app'));
if ('serviceWorker' in navigator && location.protocol === 'https:') navigator.serviceWorker.register('/sw.js').catch(() => {});