@akson/cortex-landing-hooks
Version:
React hooks for landing pages - device detection, API calls, form submission, analytics, and performance
771 lines (764 loc) • 24.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/data/index.ts
var data_exports = {};
__export(data_exports, {
createMockDashboardData: () => createMockDashboardData,
useApiCall: () => useApiCall,
useAuthenticatedApiCall: () => useAuthenticatedApiCall,
useDashboardData: () => useDashboardData,
useDocumentUpload: () => useDocumentUpload,
useLeadData: () => useLeadData,
useLocalStorageSync: () => useLocalStorageSync,
useRealtimeData: () => useRealtimeData,
useRestApi: () => useRestApi
});
module.exports = __toCommonJS(data_exports);
// src/data/useApiCall.ts
var import_react = require("react");
function useApiCall(options) {
const {
url,
method = "GET",
headers = {},
retries = 3,
retryDelay = 1e3,
onSuccess,
onError,
transformResponse = (data2) => data2,
skipRetryForStatus = [400, 401, 403, 404, 422]
} = options;
const [data, setData] = (0, import_react.useState)(null);
const [error, setError] = (0, import_react.useState)(null);
const [loading, setLoading] = (0, import_react.useState)(false);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const execute = (0, import_react.useCallback)(
async (body) => {
setLoading(true);
setError(null);
let lastError = null;
for (let attempt = 0; attempt < retries; attempt++) {
try {
const requestOptions = {
method,
headers: {
"Content-Type": "application/json",
...headers
}
};
if (body && method !== "GET") {
requestOptions.body = JSON.stringify(body);
}
const response = await fetch(url, requestOptions);
if (!response.ok) {
const errorMessage = `HTTP ${response.status}: ${response.statusText}`;
const httpError = new Error(errorMessage);
httpError.status = response.status;
throw httpError;
}
let responseData;
try {
responseData = await response.json();
} catch {
responseData = await response.text();
}
const transformedData = transformResponse(responseData);
setData(transformedData);
setLoading(false);
if (onSuccess) {
onSuccess(transformedData);
}
return transformedData;
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
const statusCode = lastError.status;
if (statusCode && skipRetryForStatus.includes(statusCode)) {
break;
}
if (attempt < retries - 1) {
await sleep(retryDelay * 2 ** attempt);
}
}
}
setError(lastError);
setLoading(false);
if (onError && lastError) {
onError(lastError);
}
return null;
},
[url, method, headers, retries, retryDelay, onSuccess, onError, transformResponse, skipRetryForStatus]
);
const reset = (0, import_react.useCallback)(() => {
setData(null);
setError(null);
setLoading(false);
}, []);
return {
data,
error,
loading,
execute,
reset
};
}
function useAuthenticatedApiCall(options) {
const {
getToken,
tokenKey = "auth_token",
tokenHeader = "Authorization",
tokenPrefix = "Bearer ",
headers = {},
...apiCallOptions
} = options;
const getAuthToken = (0, import_react.useCallback)(() => {
if (getToken) {
return getToken();
}
if (typeof window !== "undefined") {
return localStorage.getItem(tokenKey);
}
return null;
}, [getToken, tokenKey]);
const authHeaders = (0, import_react.useCallback)(() => {
const token = getAuthToken();
return token ? { [tokenHeader]: `${tokenPrefix}${token}` } : {};
}, [getAuthToken, tokenHeader, tokenPrefix]);
return useApiCall({
...apiCallOptions,
headers: {
...headers,
...authHeaders()
}
});
}
function useRestApi(endpoint, options) {
const { baseUrl, apiVersion, ...apiCallOptions } = options;
const url = apiVersion ? `${baseUrl}/${apiVersion}/${endpoint}` : `${baseUrl}/${endpoint}`;
return useApiCall({
...apiCallOptions,
url
});
}
// src/data/useLeadData.ts
var import_react2 = require("react");
var SimpleLeadManager = class {
constructor() {
this.data = /* @__PURE__ */ new Map();
this.listeners = [];
if (typeof window !== "undefined") {
try {
const stored = localStorage.getItem("akson_lead_data");
if (stored) {
const parsedData = JSON.parse(stored);
Object.entries(parsedData).forEach(([phone, data]) => {
this.data.set(phone, data);
});
}
} catch (error) {
console.warn("Failed to load lead data from localStorage:", error);
}
}
}
initializeLead(phoneNumber) {
if (!this.data.has(phoneNumber)) {
const initialData = {
phoneNumber,
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
};
this.data.set(phoneNumber, initialData);
this.persistToStorage();
}
return this.data.get(phoneNumber);
}
updateLead(phoneNumber, updates, source = "user") {
const existing = this.data.get(phoneNumber) || {};
const newData = {
...existing,
...updates,
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
lastUpdateSource: source
};
this.data.set(phoneNumber, newData);
this.persistToStorage();
this.listeners.forEach((listener) => {
try {
listener(phoneNumber, newData);
} catch (error) {
console.warn("Lead data listener error:", error);
}
});
}
getLead(phoneNumber) {
return this.data.get(phoneNumber) || null;
}
clearLead(phoneNumber) {
this.data.delete(phoneNumber);
this.persistToStorage();
this.listeners.forEach((listener) => {
try {
listener(phoneNumber, {});
} catch (error) {
console.warn("Lead data listener error:", error);
}
});
}
subscribe(listener) {
this.listeners.push(listener);
return () => {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
}
persistToStorage() {
if (typeof window !== "undefined") {
try {
const dataObj = Object.fromEntries(this.data.entries());
localStorage.setItem("akson_lead_data", JSON.stringify(dataObj));
} catch (error) {
console.warn("Failed to persist lead data to localStorage:", error);
}
}
}
// Simulate API sync - in real implementation this would call an API
async forceSyncToApi(phoneNumber) {
const data = this.getLead(phoneNumber);
if (!data) return false;
try {
console.log("[Lead Manager] Simulated API sync for:", phoneNumber, data);
this.updateLead(phoneNumber, {
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
syncStatus: "synced"
}, "api");
return true;
} catch (error) {
console.error("Failed to sync lead data to API:", error);
this.updateLead(phoneNumber, {
syncStatus: "failed",
lastSyncError: error instanceof Error ? error.message : "Unknown error"
}, "api");
return false;
}
}
};
var leadManager = null;
function getLeadManager() {
if (!leadManager && typeof window !== "undefined") {
leadManager = new SimpleLeadManager();
}
if (!leadManager) {
leadManager = new SimpleLeadManager();
}
return leadManager;
}
function useLeadData(phoneNumber) {
const [leadData, setLeadData] = (0, import_react2.useState)(null);
const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
(0, import_react2.useEffect)(() => {
if (!phoneNumber) {
setLeadData(null);
setIsLoading(false);
return;
}
setIsLoading(true);
const manager = getLeadManager();
const initialData = manager.initializeLead(phoneNumber);
setLeadData(initialData);
setIsLoading(false);
const unsubscribe = manager.subscribe((phone, data) => {
if (phone === phoneNumber) {
setLeadData(data);
}
});
return unsubscribe;
}, [phoneNumber]);
const updateField = (0, import_react2.useCallback)((fieldName, fieldValue, formData = {}) => {
if (!phoneNumber) return;
const updates = {
...formData,
[fieldName]: fieldValue
};
getLeadManager().updateLead(phoneNumber, updates, "user");
}, [phoneNumber]);
const updateImmediate = (0, import_react2.useCallback)(async (formData) => {
if (!phoneNumber) return false;
const manager = getLeadManager();
manager.updateLead(phoneNumber, formData, "user");
return manager.forceSyncToApi(phoneNumber);
}, [phoneNumber]);
const clearData = (0, import_react2.useCallback)(() => {
if (!phoneNumber) return;
getLeadManager().clearLead(phoneNumber);
}, [phoneNumber]);
const hasData = Boolean(leadData && Object.keys(leadData).length > 3);
return {
leadData,
updateField,
updateImmediate,
isLoading,
clearData,
hasData
};
}
// src/data/useDocumentUpload.ts
var import_react3 = require("react");
function useDocumentUpload(options = {}) {
const {
uploadEndpoint = "/api/documents/upload",
progressUpdateInterval = 200,
onUploadStart,
onUploadSuccess,
onUploadError
} = options;
const [uploadProgress, setUploadProgress] = (0, import_react3.useState)({
isUploading: false,
progress: 0
});
const uploadDocument = (0, import_react3.useCallback)(async (file, leadId, additionalData) => {
setUploadProgress({
isUploading: true,
progress: 0,
error: void 0
});
onUploadStart?.(file);
try {
const formData = new FormData();
formData.append("file", file);
if (leadId) {
formData.append("leadId", leadId);
}
if (additionalData) {
Object.entries(additionalData).forEach(([key, value]) => {
if (value !== void 0 && value !== null) {
formData.append(key, typeof value === "string" ? value : JSON.stringify(value));
}
});
}
const progressInterval = setInterval(() => {
setUploadProgress((prev) => ({
...prev,
progress: Math.min(prev.progress + 10, 90)
}));
}, progressUpdateInterval);
const response = await fetch(uploadEndpoint, {
method: "POST",
body: formData
});
clearInterval(progressInterval);
if (!response.ok) {
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
}
const result = await response.json();
if (!result.success && result.error) {
throw new Error(result.error);
}
setUploadProgress({
isUploading: false,
progress: 100
});
const uploadResult = {
success: true,
...result.data,
fileName: file.name,
fileSize: file.size,
fileType: file.type
};
onUploadSuccess?.(uploadResult);
return uploadResult;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Upload failed";
setUploadProgress({
isUploading: false,
progress: 0,
error: errorMessage
});
const uploadResult = {
success: false,
error: errorMessage
};
onUploadError?.(errorMessage);
return uploadResult;
}
}, [uploadEndpoint, progressUpdateInterval, onUploadStart, onUploadSuccess, onUploadError]);
const resetUpload = (0, import_react3.useCallback)(() => {
setUploadProgress({
isUploading: false,
progress: 0,
error: void 0
});
}, []);
return {
uploadProgress,
uploadDocument,
resetUpload
};
}
// src/data/useDashboardData.ts
var import_react4 = require("react");
function useDashboardData(options = {}) {
const {
endpoint = "/api/v1/analytics/dashboard",
timeRange = "24h",
refreshInterval = 5 * 60 * 1e3,
// 5 minutes
authToken,
headers = {},
onError,
onSuccess,
transformData
} = options;
const [data, setData] = (0, import_react4.useState)(null);
const [loading, setLoading] = (0, import_react4.useState)(true);
const [error, setError] = (0, import_react4.useState)(null);
const fetchDashboardData = (0, import_react4.useCallback)(async () => {
try {
setLoading(true);
setError(null);
const url = new URL(endpoint, window.location.origin);
url.searchParams.set("timeRange", timeRange);
const requestHeaders = {
"Content-Type": "application/json",
...headers
};
if (authToken) {
requestHeaders.Authorization = `Bearer ${authToken}`;
} else if (typeof window !== "undefined") {
const storedToken = localStorage.getItem("auth_token");
if (storedToken) {
requestHeaders.Authorization = `Bearer ${storedToken}`;
}
}
const response = await fetch(url.toString(), {
headers: requestHeaders
});
if (!response.ok) {
throw new Error(`Failed to fetch dashboard data: ${response.status} ${response.statusText}`);
}
const responseData = await response.json();
const rawData = responseData.data || responseData;
const dashboardData = transformData ? transformData(rawData) : rawData;
setData(dashboardData);
onSuccess?.(dashboardData);
} catch (err) {
const errorObj = err;
setError(errorObj);
onError?.(errorObj);
} finally {
setLoading(false);
}
}, [endpoint, timeRange, authToken, headers, onError, onSuccess, transformData]);
(0, import_react4.useEffect)(() => {
fetchDashboardData();
if (refreshInterval > 0) {
const interval = setInterval(fetchDashboardData, refreshInterval);
return () => clearInterval(interval);
}
}, [fetchDashboardData, refreshInterval]);
return {
data,
loading,
error,
refetch: fetchDashboardData
};
}
function createMockDashboardData() {
return {
traffic: {
total: 12540,
trend: 8.2,
hourly: Array.from({ length: 24 }, (_, i) => ({
hour: i,
value: Math.floor(Math.random() * 100) + 50
})),
sources: [
{ source: "Direct", visits: 4521, percentage: 36.1 },
{ source: "Google", visits: 3762, percentage: 30 },
{ source: "Social", visits: 2384, percentage: 19 },
{ source: "Referral", visits: 1873, percentage: 14.9 }
]
},
conversion: {
rate: 3.24,
trend: 12.5
},
funnel: {
visitors: 12540,
formStarted: 2856,
formCompleted: 1947,
quoteRequested: 1432,
converted: 406
},
aov: {
value: 189.5,
trend: -2.1
},
whatsapp: {
clickRate: 14.2,
trend: 18.7,
todayClicks: 89
},
devices: [
{ deviceType: "Mobile", sessions: 7524, conversionRate: 2.8 },
{ deviceType: "Desktop", sessions: 4320, conversionRate: 4.1 },
{ deviceType: "Tablet", sessions: 696, conversionRate: 2.2 }
],
channels: [
{ channel: "Google Ads", visits: 3200, conversions: 128, cost: 850, revenue: 24320, roi: 2762 },
{ channel: "Facebook", visits: 2100, conversions: 67, cost: 420, revenue: 12683, roi: 2921 },
{ channel: "Email", visits: 890, conversions: 45, cost: 120, revenue: 8535, roi: 7013 }
]
};
}
// src/data/useRealtimeData.ts
var import_react5 = require("react");
function useRealtimeData(options) {
const {
table,
filter,
eventType = "*",
onData,
onError,
onConnected,
onDisconnected,
enabled = true,
reconnectInterval = 5e3
} = options;
const [isConnected, setIsConnected] = (0, import_react5.useState)(false);
const [lastUpdate, setLastUpdate] = (0, import_react5.useState)(null);
const [error, setError] = (0, import_react5.useState)(null);
const wsRef = (0, import_react5.useRef)(null);
const reconnectTimeoutRef = (0, import_react5.useRef)(null);
const connect = (0, import_react5.useCallback)(() => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
return;
}
try {
const wsUrl = `ws://localhost:3001/realtime?table=${table}&filter=${filter || ""}&events=${eventType}`;
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log(`\u{1F4E1} Connected to realtime updates for table: ${table}`);
setIsConnected(true);
setError(null);
onConnected?.();
};
ws.onmessage = (event) => {
try {
const payload = JSON.parse(event.data);
setLastUpdate(/* @__PURE__ */ new Date());
onData?.(payload);
} catch (parseError) {
console.error("Failed to parse realtime message:", parseError);
onError?.(parseError);
}
};
ws.onclose = (event) => {
console.log(`\u{1F50C} Disconnected from realtime updates for table: ${table}`, event.reason);
setIsConnected(false);
onDisconnected?.();
if (enabled && event.code !== 1e3 && reconnectInterval > 0) {
reconnectTimeoutRef.current = setTimeout(() => {
console.log(`\u{1F504} Attempting to reconnect to ${table}...`);
connect();
}, reconnectInterval);
}
};
ws.onerror = (event) => {
console.error(`\u274C WebSocket error for table ${table}:`, event);
const wsError = new Error(`WebSocket connection failed for table: ${table}`);
setError(wsError);
onError?.(wsError);
};
wsRef.current = ws;
} catch (connectionError) {
console.error(`Failed to create WebSocket connection for ${table}:`, connectionError);
const error2 = connectionError;
setError(error2);
onError?.(error2);
}
}, [table, filter, eventType, onData, onError, onConnected, onDisconnected, enabled, reconnectInterval]);
const disconnect = (0, import_react5.useCallback)(() => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
if (wsRef.current) {
wsRef.current.close(1e3, "Manual disconnect");
wsRef.current = null;
}
setIsConnected(false);
setError(null);
}, []);
const sendMessage = (0, import_react5.useCallback)((message) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify(message));
} else {
console.warn("Cannot send message: WebSocket is not connected");
}
}, []);
(0, import_react5.useEffect)(() => {
if (enabled) {
connect();
} else {
disconnect();
}
return () => {
disconnect();
};
}, [enabled, connect, disconnect]);
return {
isConnected,
lastUpdate,
error,
connect,
disconnect,
sendMessage
};
}
function useLocalStorageSync(options) {
const {
key,
syncInterval = 3e4,
// 30 seconds
syncEndpoint = "/api/sync",
onSyncSuccess,
onSyncError,
transformBeforeSync
} = options;
const [lastSyncTime, setLastSyncTime] = (0, import_react5.useState)(null);
const [isSyncing, setIsSyncing] = (0, import_react5.useState)(false);
const intervalRef = (0, import_react5.useRef)(null);
const lastSyncDataRef = (0, import_react5.useRef)("");
const saveToLocal = (0, import_react5.useCallback)((data) => {
try {
const dataWithTimestamp = {
...data,
lastModified: (/* @__PURE__ */ new Date()).toISOString(),
clientId: typeof window !== "undefined" ? window.crypto?.randomUUID?.() || Math.random().toString(36) : Math.random().toString(36)
};
localStorage.setItem(key, JSON.stringify(dataWithTimestamp));
} catch (error) {
console.error("Failed to save to localStorage:", error);
}
}, [key]);
const getFromLocal = (0, import_react5.useCallback)(() => {
try {
if (typeof window === "undefined") return null;
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
} catch (error) {
console.error("Failed to read from localStorage:", error);
return null;
}
}, [key]);
const syncToRemote = (0, import_react5.useCallback)(async () => {
if (isSyncing) return false;
const localData = getFromLocal();
if (!localData) return false;
const transformedData = transformBeforeSync ? transformBeforeSync(localData) : localData;
const currentData = JSON.stringify(transformedData);
if (currentData === lastSyncDataRef.current) return true;
setIsSyncing(true);
try {
const response = await fetch(syncEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: currentData
});
if (response.ok) {
lastSyncDataRef.current = currentData;
const syncTime = /* @__PURE__ */ new Date();
setLastSyncTime(syncTime);
onSyncSuccess?.(localData);
console.log("\u2705 Successfully synced to remote");
return true;
} else {
const error = new Error(`Sync failed: ${response.status} ${response.statusText}`);
onSyncError?.(error);
console.error("\u274C Failed to sync to remote:", error.message);
return false;
}
} catch (error) {
const syncError = error;
onSyncError?.(syncError);
console.error("\u274C Sync error:", syncError.message);
return false;
} finally {
setIsSyncing(false);
}
}, [key, syncEndpoint, getFromLocal, transformBeforeSync, onSyncSuccess, onSyncError, isSyncing]);
(0, import_react5.useEffect)(() => {
if (syncInterval > 0) {
intervalRef.current = setInterval(() => {
syncToRemote();
}, syncInterval);
}
const handleVisibilityChange = () => {
if (document.visibilityState === "visible") {
syncToRemote();
}
};
const handleBeforeUnload = () => {
const localData = getFromLocal();
if (localData && navigator.sendBeacon) {
const transformedData = transformBeforeSync ? transformBeforeSync(localData) : localData;
navigator.sendBeacon(
syncEndpoint,
JSON.stringify(transformedData)
);
}
};
if (typeof document !== "undefined") {
document.addEventListener("visibilitychange", handleVisibilityChange);
window.addEventListener("beforeunload", handleBeforeUnload);
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
if (typeof document !== "undefined") {
document.removeEventListener("visibilitychange", handleVisibilityChange);
window.removeEventListener("beforeunload", handleBeforeUnload);
}
};
}, [syncInterval, syncToRemote, getFromLocal, syncEndpoint, transformBeforeSync]);
return {
saveToLocal,
getFromLocal,
syncToRemote,
lastSyncTime,
isSyncing
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createMockDashboardData,
useApiCall,
useAuthenticatedApiCall,
useDashboardData,
useDocumentUpload,
useLeadData,
useLocalStorageSync,
useRealtimeData,
useRestApi
});
;