"use client"; import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; interface ThreadTarget { channelId: string; parentId: string; } export interface AiSeed { context?: string; prompt?: string; } export type ModalKind = "create-channel" | "new-message" | "invite" | "channel-details" | "channel-members" | "create-workspace" | "callback" | "set-status" | "edit-profile"; export interface Toast { id: number; message: string; tone: "default" | "success" | "danger"; } interface UiState { thread: ThreadTarget | null; openThread: (channelId: string, parentId: string) => void; closeThread: () => void; paletteOpen: boolean; setPaletteOpen: (v: boolean) => void; notifOpen: boolean; setNotifOpen: (v: boolean) => void; profileUserId: string | null; openProfile: (id: string) => void; closeProfile: () => void; themingOpen: boolean; setThemingOpen: (v: boolean) => void; huddleChannelId: string | null; startHuddle: (channelId: string) => void; endHuddle: () => void; aiPanel: AiSeed | null; openAi: (seed?: AiSeed) => void; closeAi: () => void; sidebarOpen: boolean; setSidebarOpen: (v: boolean) => void; // desktop sidebar collapse sidebarCollapsed: boolean; toggleSidebarCollapsed: () => void; // modals modal: ModalKind | null; modalArg?: string; openModal: (m: ModalKind, arg?: string) => void; closeModal: () => void; // toasts toasts: Toast[]; toast: (message: string, tone?: Toast["tone"]) => void; dismissToast: (id: number) => void; } const Ctx = createContext(null); export function UiStateProvider({ children }: { children: React.ReactNode }) { const [thread, setThread] = useState(null); const [paletteOpen, setPaletteOpen] = useState(false); const [notifOpen, setNotifOpen] = useState(false); const [profileUserId, setProfileUserId] = useState(null); const [themingOpen, setThemingOpen] = useState(false); const [huddleChannelId, setHuddleChannelId] = useState(null); const [aiPanel, setAiPanel] = useState(null); const [sidebarOpen, setSidebarOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [modal, setModal] = useState(null); const [modalArg, setModalArg] = useState(undefined); const [toasts, setToasts] = useState([]); const toastId = useRef(1); const openThread = useCallback((channelId: string, parentId: string) => setThread({ channelId, parentId }), []); const closeThread = useCallback(() => setThread(null), []); const openProfile = useCallback((id: string) => setProfileUserId(id), []); const closeProfile = useCallback(() => setProfileUserId(null), []); const startHuddle = useCallback((id: string) => setHuddleChannelId(id), []); const endHuddle = useCallback(() => setHuddleChannelId(null), []); const openAi = useCallback((seed?: AiSeed) => setAiPanel(seed ?? {}), []); const closeAi = useCallback(() => setAiPanel(null), []); const openModal = useCallback((m: ModalKind, arg?: string) => { setModal(m); setModalArg(arg); }, []); const closeModal = useCallback(() => { setModal(null); setModalArg(undefined); }, []); useEffect(() => { try { if (localStorage.getItem("lynkd.sidebarCollapsed") === "1") setSidebarCollapsed(true); } catch { /* ignore */ } }, []); const toggleSidebarCollapsed = useCallback(() => { setSidebarCollapsed((v) => { const next = !v; try { localStorage.setItem("lynkd.sidebarCollapsed", next ? "1" : "0"); } catch { /* ignore */ } return next; }); }, []); const dismissToast = useCallback((id: number) => setToasts((t) => t.filter((x) => x.id !== id)), []); const toast = useCallback((message: string, tone: Toast["tone"] = "default") => { const id = toastId.current++; setToasts((t) => [...t, { id, message, tone }]); setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3200); }, []); // ⌘/Ctrl+K opens palette; Escape closes the top-most open surface. useEffect(() => { const onKey = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setPaletteOpen((v) => !v); return; } if (e.key === "Escape") { // close the most transient surface first if (paletteOpen) return setPaletteOpen(false); if (modal) return closeModal(); if (notifOpen) return setNotifOpen(false); if (profileUserId) return setProfileUserId(null); if (themingOpen) return setThemingOpen(false); if (aiPanel) return setAiPanel(null); if (thread) return setThread(null); if (sidebarOpen) return setSidebarOpen(false); if (huddleChannelId) return setHuddleChannelId(null); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [paletteOpen, modal, notifOpen, profileUserId, themingOpen, aiPanel, thread, sidebarOpen, huddleChannelId, closeModal]); const value = useMemo( () => ({ thread, openThread, closeThread, paletteOpen, setPaletteOpen, notifOpen, setNotifOpen, profileUserId, openProfile, closeProfile, themingOpen, setThemingOpen, huddleChannelId, startHuddle, endHuddle, aiPanel, openAi, closeAi, sidebarOpen, setSidebarOpen, sidebarCollapsed, toggleSidebarCollapsed, modal, modalArg, openModal, closeModal, toasts, toast, dismissToast, }), [thread, openThread, closeThread, paletteOpen, notifOpen, profileUserId, openProfile, closeProfile, themingOpen, huddleChannelId, startHuddle, endHuddle, aiPanel, openAi, closeAi, sidebarOpen, sidebarCollapsed, toggleSidebarCollapsed, modal, modalArg, openModal, closeModal, toasts, toast, dismissToast], ); return {children}; } export function useUi(): UiState { const ctx = useContext(Ctx); if (!ctx) throw new Error("useUi must be used within "); return ctx; }