DEVELOPER REFERENCE — LIBRARY LOOT
Library Loot
Developer Reference
← Index

Source: hooks/useTenantSettings.js

// src/hooks/useTenantSettings.js
//
// Live subscription to the active tenant's settings doc at /{tenantId}/_main.
// Returns the operator contact info (`support`), the optional legal
// supplements (`legal`), and a merged catch-all `settings` object. Falls
// back to siteContent.* defaults when a field is missing — so the public
// About / Privacy / Terms pages render cleanly the moment the React app
// mounts, even before Firestore has responded.
//
// Firestore rules allow public read on `_main` so anonymous visitors get
// the live operator contact too (see firestore.rules — ITEM 2c + 2e.1).
//
// Created by Miguel Brown on 5/13/26.
// Copyright (c) 2026 Luckey Logic LLC. All rights reserved.

import { useEffect, useState }  from 'react'
import { onSnapshot }            from 'firebase/firestore'

import { tenantDoc }             from '../firebase/tenant.js'
import siteContent               from '../data/siteContent.js'

/**
 * useTenantSettings — subscribes to `/{tenantId}/_main` and exposes the
 * tenant's live settings with siteContent fallbacks for unset fields.
 *
 * @returns {{
 *   settings: Object | null,
 *   support : Object,
 *   legal   : Object,
 *   loading : boolean,
 *   error   : Error | null
 * }}
 */
export default function useTenantSettings() {

  const [settings, setSettings] = useState(null)
  const [loading,  setLoading]  = useState(true)
  const [error,    setError]    = useState(null)

  useEffect(() => {
    const ref   = tenantDoc()
    const unsub = onSnapshot(
      ref,
      (snap) => {
        setSettings(snap.exists() ? snap.data() : null)
        setLoading(false)
      },
      (err) => {
        setError(err)
        setLoading(false)
      }
    )
    return () => unsub()
  }, [])

  // Merge live tenant settings on top of siteContent defaults. Each field
  // resolves independently so a partially-populated _main still produces
  // a fully-populated `support` block in the consumer.
  const tenantSupport = (settings && settings.support) || {}
  const tenantLegal   = (settings && settings.legal)   || {}

  const support = {
    organizationName    : tenantSupport.organizationName    || siteContent.support.organizationName,
    programContactEmail : tenantSupport.programContactEmail || siteContent.support.programContactEmail,
    coppaContactEmail   : tenantSupport.coppaContactEmail   || siteContent.support.coppaContactEmail,
    privacyContactEmail : tenantSupport.privacyContactEmail || siteContent.support.programContactEmail,
    operatorAddress     : tenantSupport.operatorAddress     || null,
    contactBlurb        : tenantSupport.contactBlurb        || siteContent.support.contactBlurb
  }

  const legal = {
    privacyPolicySupplement          : tenantLegal.privacyPolicySupplement          || null,
    privacyPolicySupplementUpdatedAt : tenantLegal.privacyPolicySupplementUpdatedAt || null,
    termsSupplement                  : tenantLegal.termsSupplement                  || null,
    termsSupplementUpdatedAt         : tenantLegal.termsSupplementUpdatedAt         || null
  }

  return { settings, support, legal, loading, error }
}