/* TMD Portal — Supabase session, sign-in, and the Inquiries page. Inquiries come from the public site (themarketingdesk.net) into public.tmd_inquiries. Only emails listed in public.tmd_staff can read or update them (enforced by row security). */ const { useState: uSi, useEffect: uEi, useMemo: uMi, useCallback: uCi } = React; const SB = window.supabase.createClient(window.TMD_CONFIG.supabaseUrl, window.TMD_CONFIG.supabaseKey, { auth: { persistSession: true, autoRefreshToken: true, detectSessionInUrl: true } }); const INQ_STATUS = [ { v: 'new', l: 'New', sem: 'todo' }, { v: 'scoped', l: 'Scoped', sem: 'doing' }, { v: 'queued', l: 'Queued', sem: 'waiting' }, { v: 'delivered', l: 'Delivered', sem: 'done' }, { v: 'declined', l: 'Declined', sem: 'blocked' } ]; const inqStatus = v => INQ_STATUS.find(s => s.v === v) || INQ_STATUS[0]; const OPEN = ['new', 'scoped', 'queued']; /* ---------- session: Supabase auth + staff row ---------- */ function useSession() { const [state, setState] = uSi({ loading: true, session: null, staff: null, staffList: [] }); // Arriving from an invite or password-reset email: the URL hash says so, and Supabase fires PASSWORD_RECOVERY. const [needsPassword, setNeedsPassword] = uSi(() => /type=(invite|recovery|magiclink)/.test(location.hash)); const loadStaff = uCi(async session => { if (!session) { setState({ loading: false, session: null, staff: null, staffList: [] }); return; } const email = (session.user.email || '').toLowerCase(); const { data, error } = await SB.from('tmd_staff').select('email,name,role,person_id,active').order('name'); const list = error ? [] : (data || []); const me = list.find(s => (s.email || '').toLowerCase() === email && s.active) || null; setState({ loading: false, session, staff: me, staffList: list }); }, []); uEi(() => { let alive = true; SB.auth.getSession().then(({ data }) => { if (alive) loadStaff(data.session); }); // Defer: querying inside the auth callback can deadlock supabase-js. const { data: sub } = SB.auth.onAuthStateChange((evt, session) => { if (evt === 'PASSWORD_RECOVERY') setNeedsPassword(true); if (evt === 'SIGNED_OUT') setNeedsPassword(false); setTimeout(() => { if (alive) loadStaff(session); }, 0); }); return () => { alive = false; sub.subscription.unsubscribe(); }; }, [loadStaff]); return Object.assign({}, state, { needsPassword: needsPassword && !!state.session, passwordSet: () => { setNeedsPassword(false); history.replaceState(null, '', location.pathname); loadStaff(state.session); }, reload: () => loadStaff(state.session), signOut: () => SB.auth.signOut() }); } /* ---------- sign in / create account ---------- */ function SignIn() { const [mode, setMode] = uSi('in'); const [email, setEmail] = uSi(''); const [pw, setPw] = uSi(''); const [busy, setBusy] = uSi(false); const [err, setErr] = uSi(''); const [note, setNote] = uSi(''); const submit = async e => { if (e && e.preventDefault) e.preventDefault(); if (busy) return; setErr(''); setNote(''); if (!email.trim() || !pw) { setErr('Email and password are both needed.'); return; } setBusy(true); try { if (mode === 'in') { const { error } = await SB.auth.signInWithPassword({ email: email.trim(), password: pw }); if (error) throw error; } else if (mode === 'up') { if (pw.length < 8) throw new Error('Use at least 8 characters.'); const { data, error } = await SB.auth.signUp({ email: email.trim(), password: pw, options: { emailRedirectTo: location.origin + location.pathname } }); if (error) throw error; if (data.session) return; // confirmations off: signed in already setNote('Account created. Check your inbox for the confirmation link, then sign in.'); setMode('in'); } else { const { error } = await SB.auth.resetPasswordForEmail(email.trim(), { redirectTo: location.origin + location.pathname }); if (error) throw error; setNote('If that address is on the staff list, a reset link is on its way.'); setMode('in'); } } catch (ex) { setErr(ex.message || 'Sign-in failed.'); } finally { setBusy(false); } }; const title = mode === 'in' ? 'Sign in' : mode === 'up' ? 'Create account' : 'Reset password'; return (
You are signed in as {email}. The Desk only opens for addresses Abidh has added. Nothing here is hidden by accident.
{err}
Loading
: null} {list.length ? (| Received | From | Type | Needs | Budget | Status | Owner |
|---|---|---|---|---|---|---|
| {fmtStamp(r.created_at)}{ago(r.created_at)} | {r.name}{r.company ? · {r.company} : null} | {r.kind === 'website' ? (r.site_type || '—') : (r.services || '—')} | {r.budget || '—'} | {r.owner || —} |
Received {fmtStamp(inq.created_at)} · {ago(inq.created_at)}
{inq.brief}
Last change {fmtStamp(inq.updated_at)}.