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; } 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 }) => { 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. */} {!import.meta.env.VITE_DESKTOP && ( )}
{/* Right: language + share + auth + mobile hamburger */}
{/* 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. */} {!import.meta.env.VITE_DESKTOP && ( )}
{showShareModal && setShowShareModal(false)} />}
); };