import { useState, useCallback, useEffect } from 'react'; declare const resicomaData: { restUrl: string; nonce: string; apiUrl: string; apiKey: string; plan: string; siteUrl: string; pluginUrl: string; version: string; adminUrl: string; debug?: boolean; }; export const PLAN_OVERRIDE_KEY = 'resicoma_plan_override'; const PLAN_OVERRIDE_EVENT = 'resicoma-plan-override-changed'; export const isDevMode = (): boolean => { if (typeof resicomaData === 'undefined') return true; return resicomaData.debug === true; }; export const dispatchPlanChange = () => { window.dispatchEvent(new Event(PLAN_OVERRIDE_EVENT)); }; // Safe access to resicomaData (may not exist in dev) const getConfig = () => { const base = typeof resicomaData !== 'undefined' ? resicomaData : { restUrl: '/wp-json/resicoma/v1/', nonce: '', apiUrl: 'https://api.resiliencewp.com', apiKey: '', plan: 'free', siteUrl: 'http://localhost', pluginUrl: '', version: '0.1.0', adminUrl: '/wp-admin/', }; if (isDevMode()) { const override = localStorage.getItem(PLAN_OVERRIDE_KEY); if (override && ['free', 'basic', 'pro'].includes(override)) { return { ...base, plan: override }; } } return base; }; export function useConfig() { const [, forceUpdate] = useState(0); useEffect(() => { const handler = () => forceUpdate((n) => n + 1); window.addEventListener(PLAN_OVERRIDE_EVENT, handler); return () => window.removeEventListener(PLAN_OVERRIDE_EVENT, handler); }, []); return getConfig(); } // WordPress REST API calls (local) export async function wpApiFetch( endpoint: string, options: RequestInit = {} ): Promise { const config = getConfig(); const response = await fetch(`${config.restUrl}${endpoint}`, { ...options, headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': config.nonce, ...options.headers, }, }); if (!response.ok) { // WordPress nonces expire after 12–24 hours. A 403 on a WP REST call usually means // the nonce has expired; guide the user to refresh rather than showing a cryptic error. if (response.status === 403) { throw new Error('Session expired. Please refresh the page and try again.'); } const error = await response.json().catch(() => ({ message: 'Request failed' })); throw new Error(error.message || error.error || 'Request failed'); } return response.json(); } // External API calls (CRA Compliance API) export async function externalApiFetch( endpoint: string, options: RequestInit = {} ): Promise { const config = getConfig(); const response = await fetch(`${config.apiUrl}${endpoint}`, { ...options, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.apiKey}`, ...options.headers, }, }); if (!response.ok) { const error = await response.json().catch(() => ({ message: 'Request failed' })); throw new Error(error.message || error.error || 'Request failed'); } return response.json(); } // Generic async state hook export function useAsync() { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const execute = useCallback(async (promise: Promise) => { setLoading(true); setError(null); try { const result = await promise; setData(result); return result; } catch (err) { const message = err instanceof Error ? err.message : 'An error occurred'; setError(message); throw err; } finally { setLoading(false); } }, []); return { data, loading, error, execute, setData }; }