{"version":3,"sources":["../../src/hooks/useWallToken.ts"],"names":[],"mappings":";;;AA6DO,SAAS,aAAa,IAAA,EAAgD;AAC3E,EAAA,MAAM,aAAA,GAAgB,MAAM,aAAA,IAAiB,6BAAA;AAC7C,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,IAAW,KAAA;AACjC,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,IAAY,GAAA;AACnC,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,IAAU,OAAA;AAC/B,EAAA,MAAM,iBAAiB,IAAA,EAAM,OAAA;AAI7B,EAAA,MAAM,KAAA,GAAQ,cAAA,KAAmB,KAAA,IAAS,MAAA,KAAW,KAAA;AAErD,EAAA,MAAM,QAAA,GAAW,OAAsB,IAAI,CAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,OAA0B,IAAI,CAAA;AAClD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAEhD,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,SAAS,EAAE,CAAA;AACnD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,EAAE,CAAA;AACrC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAC1C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAC1C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,EAAE,CAAA;AAS3C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,MAAA,CAAO,SAAS,MAAM,CAAA;AACzD,IAAA,MAAM,CAAA,GAAI,MAAA,CAAO,GAAA,CAAI,QAAQ,CAAA;AAC7B,IAAA,IAAI,CAAA,EAAG;AACL,MAAA,QAAA,CAAS,OAAA,GAAU,EAAE,IAAA,EAAK;AAC1B,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,MAAA,CAAO,OAAO,QAAQ,CAAA;AACtB,MAAA,MAAM,EAAA,GAAK,OAAO,QAAA,EAAS;AAC3B,MAAA,MAAM,MAAA,GACJ,OAAO,QAAA,CAAS,QAAA,IACf,KAAK,GAAA,GAAM,EAAA,GAAK,EAAA,CAAA,GACjB,MAAA,CAAO,QAAA,CAAS,IAAA;AAClB,MAAA,MAAA,CAAO,OAAA,CAAQ,YAAA,CAAa,IAAA,EAAM,EAAA,EAAI,MAAM,CAAA;AAAA,IAC9C;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,KAAK,CAAC,CAAA;AAGpB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,IAAW,OAAO,MAAA,KAAW,WAAA,EAAa;AAC/C,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,KAAA,CAAM,aAAA,EAAe,EAAE,WAAA,EAAa,SAAA,EAAW,CAAA,CAC5C,IAAA,CAAK,SAAQ,GAAA,CAAI,EAAA,GAAK,IAAI,IAAA,EAAK,GAAI,EAAE,KAAA,EAAO,EAAA,EAAK,CAAA,CACjD,IAAA,CAAK,CAAC,IAAA,KAA6B;AAClC,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,MAAM,CAAA,GAAK,KAAK,KAAA,IAAoB,EAAA;AACpC,MAAA,eAAA,CAAgB,CAAC,CAAA;AACjB,MAAA,QAAA,CAAS,CAAA,IAAK,aAAA,CAAc,CAAC,CAAC,CAAA;AAC9B,MAAA,WAAA,CAAY,MAAA,CAAO,QAAA,CAAS,MAAA,GAAS,MAAA,CAAO,SAAS,QAAQ,CAAA;AAAA,IAC/D,CAAC,CAAA,CACA,KAAA,CAAM,MAAM,MAAS,CAAA,CACrB,QAAQ,MAAM;AACb,MAAA,IAAI,CAAC,SAAA,EAAW,SAAA,CAAU,IAAI,CAAA;AAAA,IAChC,CAAC,CAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,EAAS,aAAa,CAAC,CAAA;AAE3B,EAAA,MAAM,YAAA,GAAe,WAAA,CAAY,CAAC,CAAA,KAAc;AAC9C,IAAA,QAAA,CAAS,kBAAA,CAAmB,CAAC,CAAC,CAAA;AAAA,EAChC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,CAAC,GAAA,GAAM,CAAA,KAAM;AACxC,IAAA,QAAA,CAAS,aAAA,CAAc,GAAG,CAAC,CAAA;AAAA,EAC7B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,IAAA,GAAO,YAAY,YAAY;AACnC,IAAA,SAAA,CAAU,IAAI,CAAA;AACd,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,aAAA,EAAe;AAAA,QACrC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,QAC9C,WAAA,EAAa,SAAA;AAAA,QACb,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,OAAO;AAAA,OACtC,CAAA;AACD,MAAA,IAAI,IAAI,EAAA,EAAI;AACV,QAAA,MAAM,IAAA,GAA2B,MAAM,GAAA,CAAI,IAAA,EAAK;AAChD,QAAA,eAAA,CAAgB,IAAA,CAAK,SAAS,EAAE,CAAA;AAAA,MAClC;AAAA,IACF,CAAA,SAAE;AACA,MAAA,SAAA,CAAU,KAAK,CAAA;AAAA,IACjB;AAAA,EACF,CAAA,EAAG,CAAC,aAAA,EAAe,KAAK,CAAC,CAAA;AAEzB,EAAA,MAAM,UAAA,GAAa,YAAY,MAAM;AACnC,IAAA,MAAM,SAAS,WAAA,CAAY,OAAA;AAC3B,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,YAAA,EAAc;AAC9B,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,SAAA,CAAU,WAAW,CAAA;AACxC,IAAA,MAAM,CAAA,GAAI,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACpC,IAAA,CAAA,CAAE,IAAA,GAAO,GAAA;AACT,IAAA,CAAA,CAAE,QAAA,GAAW,WAAW,YAAY,CAAA,IAAA,CAAA;AACpC,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,CAAC,CAAA;AAC3B,IAAA,CAAA,CAAE,KAAA,EAAM;AACR,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,CAAC,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC9B,IAAA,QAAA,CAAS,EAAE,CAAA;AAAA,EACb,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA,EAAO;AAAA,MACL,YAAA;AAAA,MACA,KAAA;AAAA,MACA,QAAA,EAAU,YAAA;AAAA,MACV,QAAA;AAAA,MACA,IAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,QAAA;AAAA,MACA,WAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA;AACF,GACF;AACF","file":"useWallToken.mjs","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport type { RefObject } from 'react';\nimport { generateToken, sanitizeTokenInput } from '../lib/token';\n\nexport interface UseWallTokenOptions {\n  /** Endpoint for GET/POST owner-token operations. Defaults to '/api/admin/wall-scene-token'. */\n  adminEndpoint?: string;\n  /** Set true after admin role is confirmed; gates the GET fetch. */\n  isAdmin?: boolean;\n  /** URL search-param key carrying the token. Defaults to 't'. */\n  paramKey?: string;\n  /**\n   * Site-level AI photobooth switch. Defaults to `'token'` (legacy behaviour:\n   * `aiEnabled` flips true when the URL carries `?t=<token>`). Pass `'off'` for\n   * sites that don't want the AI scene flow at all — URL token capture is\n   * skipped and `aiEnabled` stays false even if a token sneaks into the URL.\n   * The admin token panel still renders for `isAdmin` users in either mode so\n   * an owner can pre-configure a token before flipping AI on later.\n   */\n  aiMode?: 'token' | 'off';\n  /**\n   * Runtime override for the AI photobooth switch — typically wired to\n   * `useWallSettings().wallAiEnabled`. When `false`, behaves exactly like\n   * `aiMode: 'off'` (URL token capture skipped, `aiEnabled` forced false).\n   * When `true` or omitted, the static `aiMode` controls behaviour. Lets a\n   * site flip AI on/off at runtime via the admin checkbox without a redeploy.\n   */\n  enabled?: boolean;\n}\n\nexport interface WallTokenAdmin {\n  currentToken: string;\n  input: string;\n  setInput: (v: string) => void;\n  generate: (len?: number) => void;\n  save: () => Promise<void>;\n  saving: boolean;\n  loaded: boolean;\n  shareUrl: string;\n  qrCanvasRef: RefObject<HTMLCanvasElement | null>;\n  downloadQR: () => void;\n  clear: () => void;\n}\n\nexport interface UseWallTokenResult {\n  /** True iff the URL carried a token at mount; AI scene generation should be enabled. */\n  aiEnabled: boolean;\n  /** Live token value, in-memory only, captured once at mount. */\n  tokenRef: RefObject<string | null>;\n  /** Owner-only admin panel state. Always returned, but `loaded` stays false until an admin GET completes. */\n  admin: WallTokenAdmin;\n}\n\n/**\n * Captures a one-shot wall-access token from the URL (`?t=<token>`) on mount and\n * strips it from the address bar via `history.replaceState`. Held in-memory only —\n * never persisted in localStorage / sessionStorage.\n *\n * When `isAdmin` is true, also fetches and exposes the admin token panel state\n * (current token, editable input, generate/save handlers, QR canvas ref + download).\n */\nexport function useWallToken(opts?: UseWallTokenOptions): UseWallTokenResult {\n  const adminEndpoint = opts?.adminEndpoint ?? '/api/admin/wall-scene-token';\n  const isAdmin = opts?.isAdmin ?? false;\n  const paramKey = opts?.paramKey ?? 't';\n  const aiMode = opts?.aiMode ?? 'token';\n  const runtimeEnabled = opts?.enabled;\n  // The runtime flag short-circuits the static one. Either knob can force\n  // AI off: `enabled === false` (runtime, e.g. owner unchecked the box) or\n  // `aiMode === 'off'` (static, hardcoded by the site author).\n  const aiOff = runtimeEnabled === false || aiMode === 'off';\n\n  const tokenRef = useRef<string | null>(null);\n  const qrCanvasRef = useRef<HTMLCanvasElement>(null);\n  const [aiEnabled, setAiEnabled] = useState(false);\n\n  const [currentToken, setCurrentToken] = useState('');\n  const [input, setInput] = useState('');\n  const [loaded, setLoaded] = useState(false);\n  const [saving, setSaving] = useState(false);\n  const [shareUrl, setShareUrl] = useState('');\n\n  // Capture URL token once when AI is on and strip it from the address bar.\n  // Skipped when aiOff — the site has opted out of the AI photobooth flow, so\n  // a stray ?t= param in the URL must not flip aiEnabled or be retained.\n  // Note: if the runtime flag flips on AFTER mount (owner ticks the checkbox\n  // post-load) the URL token has already been replaceState'd away on initial\n  // capture, so guests need a refresh to get a fresh photobooth session —\n  // intentional, since the toggle is a rare admin action.\n  useEffect(() => {\n    if (typeof window === 'undefined') return;\n    if (aiOff) {\n      tokenRef.current = null;\n      setAiEnabled(false);\n      return;\n    }\n    const params = new URLSearchParams(window.location.search);\n    const t = params.get(paramKey);\n    if (t) {\n      tokenRef.current = t.trim();\n      setAiEnabled(true);\n      params.delete(paramKey);\n      const qs = params.toString();\n      const newUrl =\n        window.location.pathname +\n        (qs ? '?' + qs : '') +\n        window.location.hash;\n      window.history.replaceState(null, '', newUrl);\n    }\n  }, [paramKey, aiOff]);\n\n  // Fetch the persisted token for the admin panel, once we know the user is admin.\n  useEffect(() => {\n    if (!isAdmin || typeof window === 'undefined') return;\n    let cancelled = false;\n    fetch(adminEndpoint, { credentials: 'include' })\n      .then(res => (res.ok ? res.json() : { token: '' }))\n      .then((data: { token?: string }) => {\n        if (cancelled) return;\n        const t = (data.token as string) || '';\n        setCurrentToken(t);\n        setInput(t || generateToken(6));\n        setShareUrl(window.location.origin + window.location.pathname);\n      })\n      .catch(() => undefined)\n      .finally(() => {\n        if (!cancelled) setLoaded(true);\n      });\n    return () => {\n      cancelled = true;\n    };\n  }, [isAdmin, adminEndpoint]);\n\n  const setInputSafe = useCallback((v: string) => {\n    setInput(sanitizeTokenInput(v));\n  }, []);\n\n  const generate = useCallback((len = 6) => {\n    setInput(generateToken(len));\n  }, []);\n\n  const save = useCallback(async () => {\n    setSaving(true);\n    try {\n      const res = await fetch(adminEndpoint, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        credentials: 'include',\n        body: JSON.stringify({ token: input }),\n      });\n      if (res.ok) {\n        const data: { token?: string } = await res.json();\n        setCurrentToken(data.token || '');\n      }\n    } finally {\n      setSaving(false);\n    }\n  }, [adminEndpoint, input]);\n\n  const downloadQR = useCallback(() => {\n    const canvas = qrCanvasRef.current;\n    if (!canvas || !currentToken) return;\n    const url = canvas.toDataURL('image/png');\n    const a = document.createElement('a');\n    a.href = url;\n    a.download = `wall-qr-${currentToken}.png`;\n    document.body.appendChild(a);\n    a.click();\n    document.body.removeChild(a);\n  }, [currentToken]);\n\n  const clear = useCallback(() => {\n    setInput('');\n  }, []);\n\n  return {\n    aiEnabled,\n    tokenRef,\n    admin: {\n      currentToken,\n      input,\n      setInput: setInputSafe,\n      generate,\n      save,\n      saving,\n      loaded,\n      shareUrl,\n      qrCanvasRef,\n      downloadQR,\n      clear,\n    },\n  };\n}\n"]}