"use client"; import { useState, useEffect } from "react"; import PropTypes from "prop-types"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { cn } from "@/shared/utils/cn"; import { APP_CONFIG } from "@/shared/constants/config"; import OmniRouteLogo from "./OmniRouteLogo"; import Button from "./Button"; import { ConfirmModal } from "./Modal"; import CloudSyncStatus from "./CloudSyncStatus"; import { useTranslations } from "next-intl"; import { HIDDEN_SIDEBAR_ITEMS_SETTING_KEY, SIDEBAR_SETTINGS_UPDATED_EVENT, SIDEBAR_SECTIONS, normalizeHiddenSidebarItems, } from "@/shared/constants/sidebarVisibility"; const isE2EMode = process.env.NEXT_PUBLIC_OMNIROUTE_E2E_MODE === "1"; export default function Sidebar({ onClose, collapsed = false, onToggleCollapse, isMacElectron = false, }: { onClose?: any; collapsed?: boolean; onToggleCollapse?: any; isMacElectron?: boolean; }) { const pathname = usePathname(); const t = useTranslations("sidebar"); const tc = useTranslations("common"); const [showShutdownModal, setShowShutdownModal] = useState(false); const [showRestartModal, setShowRestartModal] = useState(false); const [isShuttingDown, setIsShuttingDown] = useState(false); const [isRestarting, setIsRestarting] = useState(false); const [isDisconnected, setIsDisconnected] = useState(false); const [showDebug, setShowDebug] = useState(false); const [hiddenSidebarItems, setHiddenSidebarItems] = useState([]); const [customAppName, setCustomAppName] = useState(null); const [customLogo, setCustomLogo] = useState(null); useEffect(() => { const applySettings = (data) => { setShowDebug(data?.debugMode === true); setHiddenSidebarItems(normalizeHiddenSidebarItems(data?.[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY])); setCustomAppName(data?.instanceName || null); setCustomLogo(data?.customLogoBase64 || data?.customLogoUrl || null); }; fetch("/api/settings") .then((res) => res.json()) .then((data) => applySettings(data)) .catch(() => {}); const handleSettingsUpdated = (event: Event) => { const detail = (event as CustomEvent>).detail || {}; if ("debugMode" in detail) { setShowDebug(detail.debugMode === true); } if (HIDDEN_SIDEBAR_ITEMS_SETTING_KEY in detail) { setHiddenSidebarItems( normalizeHiddenSidebarItems(detail[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]) ); } if ("instanceName" in detail) { setCustomAppName((detail.instanceName as string) || null); } if ("customLogoBase64" in detail) { setCustomLogo((detail.customLogoBase64 as string) || null); } else if ("customLogoUrl" in detail) { setCustomLogo((detail.customLogoUrl as string) || null); } }; window.addEventListener(SIDEBAR_SETTINGS_UPDATED_EVENT, handleSettingsUpdated as EventListener); return () => { window.removeEventListener( SIDEBAR_SETTINGS_UPDATED_EVENT, handleSettingsUpdated as EventListener ); }; }, []); const isActive = (href, exact) => { if (exact) { return pathname === href; } return pathname.startsWith(href); }; const handleShutdown = async () => { setIsShuttingDown(true); try { await fetch("/api/shutdown", { method: "POST" }); } catch (e) { // Expected to fail as server shuts down; ignore error } setIsShuttingDown(false); setShowShutdownModal(false); setIsDisconnected(true); }; const handleRestart = async () => { setIsRestarting(true); try { await fetch("/api/restart", { method: "POST" }); } catch (e) { // Expected to fail as server restarts } setIsRestarting(false); setShowRestartModal(false); setIsDisconnected(true); setTimeout(() => { globalThis.location.reload(); }, 3000); }; const getSidebarLabel = (key: string, fallback: string) => typeof t.has === "function" && t.has(key) ? t(key) : fallback; const hiddenSidebarSet = new Set(hiddenSidebarItems); const visibleSections = SIDEBAR_SECTIONS.filter( (section) => section.visibility !== "debug" || showDebug ) .map((section) => ({ ...section, title: getSidebarLabel(section.titleKey, section.titleFallback), items: section.items .map((item) => ({ ...item, label: t(item.i18nKey) })) .filter((item) => !hiddenSidebarSet.has(item.id)), })) .filter((section) => section.items.length > 0); const renderNavLink = (item) => { const active = !item.external && isActive(item.href, item.exact); const className = cn( "flex items-center gap-3 rounded-lg transition-all group", collapsed ? "justify-center px-2 py-2.5" : "px-4 py-2", active ? "bg-primary/10 text-primary" : "text-text-muted hover:bg-surface/50 hover:text-text-main" ); const iconClassName = cn( "material-symbols-outlined text-[18px]", active ? "fill-1" : "group-hover:text-primary transition-colors" ); const content = ( <> {item.icon} {!collapsed && {item.label}} ); if (item.external) { return ( {content} ); } return ( {content} ); }; return ( <> setShowShutdownModal(false)} onConfirm={handleShutdown} title={t("shutdown")} message={t("shutdownConfirm")} confirmText={t("shutdown")} cancelText={tc("cancel")} variant="danger" loading={isShuttingDown} /> setShowRestartModal(false)} onConfirm={handleRestart} title={t("restart")} message={t("restartConfirm")} confirmText={t("restart")} cancelText={tc("cancel")} variant="warning" loading={isRestarting} /> {isDisconnected && (
power_off

Server Disconnected

The proxy server has been stopped or is restarting.

)} ); } Sidebar.propTypes = { onClose: PropTypes.func, collapsed: PropTypes.bool, onToggleCollapse: PropTypes.func, isMacElectron: PropTypes.bool, };