import { useState, useEffect } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useProjectStore } from '../../store/useProjectStore'; import { ShareModal } from './ShareModal'; import { LanguageSwitcher } from './LanguageSwitcher'; import { useLocalizedHref, useCurrentLocale } from '../../i18n/useLocalizedNavigate'; import { blogUrlFor } from '../../i18n/path'; import { trackVisitGitHub, trackVisitDiscord } from '../../utils/analytics'; import type { AutoSaveState } from '../../hooks/useAutoSaveProject'; import './LanguageSwitcher.css'; const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio'; const DISCORD_URL = 'https://discord.gg/3mARjJrh4E'; interface AppHeaderProps { /** Optional auto-save state — when set, renders a save status indicator. */ autoSave?: AutoSaveState; /** Editor variant: a File/Edit menu bar rendered next to the logo. When * set, the marketing nav links (Home / Docs / Pricing / …) are hidden — * inside the editor they are noise that costs exactly the width the * toolbar is starved of on small screens; the logo still links home. * Same mechanism the Tauri desktop build uses (VITE_DESKTOP). */ editorMenu?: React.ReactNode; /** Editor variant: the unified toolbar strip rendered in the header's * middle — the space the marketing nav used to occupy. One row instead * of header + toolbar stacked; the strip wraps internally when narrow * and the header grows to fit (height: auto on the modifier class). */ editorToolbar?: React.ReactNode; } const SAVE_STATUS_COPY: Record = { idle: { label: 'Saved', color: '#7d8590' }, dirty: { label: 'Unsaved changes', color: '#f0883e' }, saving: { label: 'Saving…', color: '#3fb950' }, saved: { label: 'Saved', color: '#3fb950' }, error: { label: 'Save failed', color: '#f85149' }, }; const AutoSaveIndicator: React.FC<{ state: AutoSaveState }> = ({ state }) => { const meta = SAVE_STATUS_COPY[state.status]; const tip = state.status === 'error' && state.errorMessage ? `Auto-save failed: ${state.errorMessage}` : state.lastSavedAt ? `Last saved ${new Date(state.lastSavedAt).toLocaleTimeString()}` : 'Auto-save ready'; return (
{meta.label}
); }; export const AppHeader: React.FC = ({ autoSave, editorMenu, editorToolbar }) => { const location = useLocation(); const currentProject = useProjectStore((s) => s.currentProject); const [menuOpen, setMenuOpen] = useState(false); const [showShareModal, setShowShareModal] = useState(false); const { t } = useTranslation(); const localize = useLocalizedHref(); const currentLocale = useCurrentLocale(); // Close mobile menu on route change useEffect(() => { setMenuOpen(false); }, [location.pathname]); // Tauri desktop: skip the header entirely. The marketing nav was // already hidden, but the strip itself was still painting an empty // black bar over the editor. Brand/auto-save/share/auth-slot all // live elsewhere in desktop: title bar shows "Velxio Desktop", the // native menubar has File/Edit/View/Help, auto-save is a Pro cloud // feature (desktop saves to .vlx), share generates a velxio.dev URL // that doesn't apply to a desktop session, and the license flow // owns its own DesktopWelcomePage. if (import.meta.env.VITE_DESKTOP) { return null; } const isActive = (path: string) => location.pathname === localize(path) ? ' header-nav-link-active' : ''; return (
{/* Brand */}
Velxio
{/* Main nav links (web only). The Tauri desktop build hides this nav and surfaces the equivalent actions via the native menubar (see pro/desktop/src-tauri/src/menu.rs in velxio-prod). VITE_DESKTOP is the env flag the Tauri build sets — main.tsx already uses it to gate the @pro overlay, same pattern here. */} {editorMenu} {!import.meta.env.VITE_DESKTOP && !editorMenu && ( )}
{/* Editor toolbar strip — fills the middle the nav vacated. */} {editorToolbar && ( <> {autoSave && currentProject && }
{editorToolbar}
)} {/* Right: language + share + auth + mobile hamburger. In the desktop-editor variant this block does not render at all: the language switcher and the account button move to the corner box below (bottom-left), Share lives in File > Share/Embed, and the autosave dot rides next to the menus — every pixel of the row goes to the toolbar, which is what lets 1440px-with-chat keep the single-row layout. */} {!editorToolbar && (
{/* Auto-save status — only when a project is loaded and the editor page mounted the hook */} {autoSave && currentProject && } {/* Share button — visible when a project is loaded */} {currentProject && location.pathname === '/editor' && ( )} {/* Auth UI lives in the pro overlay — sign-in/sign-up buttons when anonymous, user dropdown when logged in. The overlay's mountPro() portals its HeaderAuth component into this slot via mountIntoSlot('header-auth'). In OSS without the overlay this slot stays empty, which is correct because the OSS image has no auth backend either. */}
{/* Mobile hamburger — useless in desktop where the nav it would expand is itself hidden, and in the editor variant, where there is no nav to expand at all. */} {!import.meta.env.VITE_DESKTOP && !editorMenu && ( )}
)}
{/* The account + language block lives in the file-explorer footer now (EditorPage renders it) — fused so a long file tree never scrolls underneath a floating box. */} {showShareModal && setShowShareModal(false)} />}
); };