import { useState, useEffect, useRef } from "react"; import { Document, AuthUser, SyncLog, TeamMember } from "../types"; import { INITIAL_DOCUMENTS, INITIAL_TEAM_MEMBERS } from "../data"; import { DEFAULT_VALUES } from "../config"; export function useWorkspaceState() { // Core Document State const [documents, setDocuments] = useState(() => { const cached = localStorage.getItem("workspace_documents"); return cached ? JSON.parse(cached) : INITIAL_DOCUMENTS; }); const [activeDocId, setActiveDocId] = useState(""); const activeDoc = documents.find((d) => d.id === activeDocId) || documents[0] || INITIAL_DOCUMENTS[0]; const [editorText, setEditorText] = useState(activeDoc.content); // Sync state & Offline detection const [isOnline, setIsOnline] = useState(navigator.onLine); const [syncStatus, setSyncStatus] = useState<"synced" | "syncing" | "offline" | "error">("synced"); const [syncLogs, setSyncLogs] = useState([]); // Auth — no hardcoded credentials const [user, setUser] = useState(() => { const cached = localStorage.getItem("workspace_user"); return cached ? JSON.parse(cached) : { email: null, displayName: null, isLoggedIn: false, tier: DEFAULT_VALUES.TIER, privateKeySeed: "", }; }); // Team const [teamMembers, setTeamMembers] = useState(INITIAL_TEAM_MEMBERS); const [teamChangesEnabled, setTeamChangesEnabled] = useState(true); const [activeCollaborators, setActiveCollaborators] = useState(1); // Toast const [toast, setToast] = useState<{ show: boolean; message: string; timestamp: string; }>({ show: false, message: "", timestamp: "" }); // Form states const [showDocModal, setShowDocModal] = useState(false); const [newDocTitle, setNewDocTitle] = useState(""); const [documentSearchTerm, setDocumentSearchTerm] = useState(""); const [uploadFile, setUploadFile] = useState(null); const [uploadLoading, setUploadLoading] = useState(false); const [uploadError, setUploadError] = useState(null); // Admin const [adminPassword, setAdminPassword] = useState(""); const [isAdminLoggedIn, setIsAdminLoggedIn] = useState(false); const [adminVerifyLoading] = useState(false); // Analytics const [tokensConsumed, setTokensConsumed] = useState(0); // Other UI state const [showVersionHistory, setShowVersionHistory] = useState(false); const [isReadingMode, setIsReadingMode] = useState(false); const [showExportPreview, setShowExportPreview] = useState(false); const [exportPreviewFormat, setExportPreviewFormat] = useState<"markdown" | "pdf" | "html">("pdf"); const [showPreferenceModal, setShowPreferenceModal] = useState(false); const [showShareModal, setShowShareModal] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [copiedDoc, setCopiedDoc] = useState(false); const [showStatsPopover, setShowStatsPopover] = useState(false); const [leftPanelMode, setLeftPanelMode] = useState<"edit" | "preview" | "folding">("edit"); const [foldedBlockIds, setFoldedBlockIds] = useState>({}); // Refs const saveTimeoutRef = useRef(null); const isFirstLocalStorageSave = useRef(true); // Load editor text when active doc changes useEffect(() => { setEditorText(activeDoc.content); }, [activeDoc.id]); // eslint-disable-line react-hooks/exhaustive-deps // Parse shared links on mount useEffect(() => { const urlStr = window.location.href; const match = urlStr.match(/\/share\/doc\/([^/?#]+)/); if (match) { const docId = match[1]; // Fetch the real document from the server import("../lib/apiClient").then(({ getDocument }) => { getDocument(docId) .then((serverDoc) => { const docExists = documents.some((d) => d.id === docId); if (!docExists) { const loadedDoc: Document = { id: serverDoc.id, title: serverDoc.title, content: serverDoc.content, version: serverDoc.version, lastSaved: serverDoc.lastSaved, isSynced: true }; setDocuments((prev) => [loadedDoc, ...prev]); } setActiveDocId(docId); setIsReadingMode(true); addLog(`Shared document loaded successfully.`, "success"); }) .catch(() => { addLog(`Shared document not found on server.`, "error"); }); }); } }, []); // eslint-disable-line react-hooks/exhaustive-deps // Load or generate device UUID, and perform guest authentication on mount useEffect(() => { async function initAuth() { // Check for OAuth callback const urlParams = new URLSearchParams(window.location.search); const oauthCode = urlParams.get("code"); if (oauthCode) { // Handle OAuth callback const provider = sessionStorage.getItem("oauth_provider") || "google"; const redirectUri = sessionStorage.getItem("oauth_redirect_uri") || `${window.location.origin}/auth/callback`; sessionStorage.removeItem("oauth_provider"); sessionStorage.removeItem("oauth_redirect_uri"); try { const { oauthCallback } = await import("../lib/apiClient"); const res = await oauthCallback(provider, oauthCode, redirectUri); localStorage.setItem("workspace_auth_token", res.token); setUser({ email: res.user.email, displayName: res.user.display_name, isLoggedIn: true, isGuest: false, tier: res.user.tier, privateKeySeed: "", hasPasskey: res.user.has_passkey, oauthProvider: res.user.oauth_provider, }); addLog(`Signed in via ${provider}: ${res.user.display_name}`, "success"); // Clean URL window.history.replaceState({}, "", window.location.pathname); } catch (err: any) { addLog(`OAuth callback failed: ${err.message}`, "error"); } return; } // Check for existing token const existingToken = localStorage.getItem("workspace_auth_token"); if (existingToken) { const cached = localStorage.getItem("workspace_user"); const parsed = cached ? JSON.parse(cached) : null; if (parsed && parsed.isLoggedIn) { addLog(`Restored session for ${parsed.displayName || parsed.email}`, "success"); return; } } // Fallback to guest auth let deviceUuid = localStorage.getItem("workspace_device_uuid"); if (!deviceUuid) { deviceUuid = (typeof crypto.randomUUID === "function") ? crypto.randomUUID() : `device-${Math.random().toString(36).substring(2, 15)}`; localStorage.setItem("workspace_device_uuid", deviceUuid); } try { const { authGuest } = await import("../lib/apiClient"); const res = await authGuest(deviceUuid); localStorage.setItem("workspace_auth_token", res.token); setUser({ email: res.user.email, displayName: res.user.display_name, isLoggedIn: true, isGuest: res.user.is_guest, tier: res.user.tier, privateKeySeed: "", }); addLog(`Guest session: ${res.user.display_name}`, "success"); // Load documents from server try { const { getDocuments } = await import("../lib/apiClient"); const docRes = await getDocuments(); if (docRes.documents && docRes.documents.length > 0) { const serverDocs: Document[] = docRes.documents.map((d: any) => ({ id: d.id, title: d.title, content: d.content, version: d.version, lastSaved: d.lastSaved, isSynced: true })); setDocuments(serverDocs); } } catch { // Keep local docs } } catch (err: any) { addLog(`Auth failed: ${err.message}`, "error"); } } initAuth(); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Online/Offline listeners useEffect(() => { const handleOnline = () => { setIsOnline(true); setSyncStatus("synced"); addLog("Network connection restored. Re-authenticating sync tunnel.", "success"); }; const handleOffline = () => { setIsOnline(false); setSyncStatus("offline"); addLog("Offline mode active. All edits are being buffered securely in local IndexDB.", "info"); }; window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline); return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; }, []); // Auto-save to localStorage const [autoSaveEnabled, setAutoSaveEnabled] = useState(() => { return localStorage.getItem("workspace_pref_autoSave") !== "false"; }); useEffect(() => { if (!autoSaveEnabled) return; if (isFirstLocalStorageSave.current) { isFirstLocalStorageSave.current = false; return; } const timer = setTimeout(() => { localStorage.setItem("workspace_documents", JSON.stringify(documents)); const now = new Date(); const formattedTime = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", }); setToast({ show: true, message: "All document progress autosaved locally", timestamp: formattedTime, }); }, 1500); return () => clearTimeout(timer); }, [documents, autoSaveEnabled]); // Toast dismiss useEffect(() => { if (toast.show) { const timer = setTimeout(() => { setToast((prev) => ({ ...prev, show: false })); }, 3000); return () => clearTimeout(timer); } }, [toast.show]); // Beforeunload save useEffect(() => { const handleBeforeUnload = () => { if (autoSaveEnabled) { localStorage.setItem("workspace_documents", JSON.stringify(documents)); } }; window.addEventListener("beforeunload", handleBeforeUnload); return () => window.removeEventListener("beforeunload", handleBeforeUnload); }, [documents, autoSaveEnabled]); // Save credentials useEffect(() => { localStorage.setItem("workspace_user", JSON.stringify(user)); }, [user]); // Helpers const addLog = (text: string, type: "success" | "info" | "sync" | "error") => { setSyncLogs((prev) => [ { id: `log-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, text, timestamp: new Date().toISOString(), type, }, ...prev.slice(0, 24), ]); }; const handleManualSave = () => { localStorage.setItem("workspace_documents", JSON.stringify(documents)); const now = new Date(); const formattedTime = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", }); setToast({ show: true, message: "Document progress manually saved locally", timestamp: formattedTime, }); addLog("Document progress manually saved locally.", "success"); }; return { documents, setDocuments, activeDocId, setActiveDocId, activeDoc, editorText, setEditorText, isOnline, syncStatus, setSyncStatus, syncLogs, user, setUser, teamMembers, setTeamMembers, teamChangesEnabled, setTeamChangesEnabled, activeCollaborators, setActiveCollaborators, toast, setToast, showDocModal, setShowDocModal, newDocTitle, setNewDocTitle, documentSearchTerm, setDocumentSearchTerm, uploadFile, setUploadFile, uploadLoading, setUploadLoading, uploadError, setUploadError, adminPassword, setAdminPassword, isAdminLoggedIn, setIsAdminLoggedIn, adminVerifyLoading, tokensConsumed, setTokensConsumed, showVersionHistory, setShowVersionHistory, isReadingMode, setIsReadingMode, showExportPreview, setShowExportPreview, exportPreviewFormat, setExportPreviewFormat, showPreferenceModal, setShowPreferenceModal, showShareModal, setShowShareModal, isSidebarOpen, setIsSidebarOpen, copiedDoc, setCopiedDoc, showStatsPopover, setShowStatsPopover, leftPanelMode, setLeftPanelMode, foldedBlockIds, setFoldedBlockIds, autoSaveEnabled, setAutoSaveEnabled, saveTimeoutRef, isFirstLocalStorageSave, addLog, handleManualSave, }; }