import { useState, useEffect, lazy, Suspense } from "react"; import { openUrl } from "@tauri-apps/plugin-opener"; import { Sidebar } from "./components/sidebar/Sidebar"; import { Feed } from "./components/feed/Feed"; import { OnboardingFlow } from "./components/onboarding/OnboardingFlow"; import { PodcastPlayerBar } from "./components/podcast/PodcastPlayerBar"; import { ToastContainer } from "./components/shared/ToastContainer"; // Lazy-loaded views — only fetched when navigated to const SearchView = lazy(() => import("./components/search/SearchView").then(m => ({ default: m.SearchView }))); const RelaysView = lazy(() => import("./components/shared/RelaysView").then(m => ({ default: m.RelaysView }))); const SettingsView = lazy(() => import("./components/shared/SettingsView").then(m => ({ default: m.SettingsView }))); const ProfileView = lazy(() => import("./components/profile/ProfileView").then(m => ({ default: m.ProfileView }))); const ThreadView = lazy(() => import("./components/thread/ThreadView").then(m => ({ default: m.ThreadView }))); const ArticleEditor = lazy(() => import("./components/article/ArticleEditor").then(m => ({ default: m.ArticleEditor }))); const ArticleView = lazy(() => import("./components/article/ArticleView").then(m => ({ default: m.ArticleView }))); const ArticleFeed = lazy(() => import("./components/article/ArticleFeed").then(m => ({ default: m.ArticleFeed }))); const MediaFeed = lazy(() => import("./components/media/MediaFeed").then(m => ({ default: m.MediaFeed }))); const AboutView = lazy(() => import("./components/shared/AboutView").then(m => ({ default: m.AboutView }))); const ZapHistoryView = lazy(() => import("./components/zap/ZapHistoryView").then(m => ({ default: m.ZapHistoryView }))); const DMView = lazy(() => import("./components/dm/DMView").then(m => ({ default: m.DMView }))); const NotificationsView = lazy(() => import("./components/notifications/NotificationsView").then(m => ({ default: m.NotificationsView }))); const BookmarkView = lazy(() => import("./components/bookmark/BookmarkView").then(m => ({ default: m.BookmarkView }))); const HashtagFeed = lazy(() => import("./components/feed/HashtagFeed").then(m => ({ default: m.HashtagFeed }))); const PodcastsView = lazy(() => import("./components/podcast/PodcastsView").then(m => ({ default: m.PodcastsView }))); const FollowsView = lazy(() => import("./components/follows/FollowsView").then(m => ({ default: m.FollowsView }))); const V4VView = lazy(() => import("./components/v4v/V4VView").then(m => ({ default: m.V4VView }))); const DebugPanel = lazy(() => import("./components/shared/DebugPanel").then(m => ({ default: m.DebugPanel }))); const HelpModal = lazy(() => import("./components/shared/HelpModal").then(m => ({ default: m.HelpModal }))); import { useUIStore } from "./stores/ui"; import { useUserStore } from "./stores/user"; import { getTheme, applyTheme } from "./lib/themes"; import { useUpdater } from "./hooks/useUpdater"; import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts"; function UpdateBanner() { const { available, version, installing, error, install, dismiss } = useUpdater(); if (!available) return null; return (
Vega {version} is available.{" "} {error && {error}}
); } function ReadOnlyBanner() { const loggedIn = useUserStore((s) => s.loggedIn); if (loggedIn) return null; return (
Read-only mode — sign in to post, react, and zap
); } function App() { const currentView = useUIStore((s) => s.currentView); const showHelp = useUIStore((s) => s.showHelp); const toggleHelp = useUIStore((s) => s.toggleHelp); const showDebugPanel = useUIStore((s) => s.showDebugPanel); const toggleDebugPanel = useUIStore((s) => s.toggleDebugPanel); const fontSize = useUIStore((s) => s.fontSize); const themeId = useUIStore((s) => s.themeId); const [onboardingDone, setOnboardingDone] = useState( () => !!localStorage.getItem("wrystr_pubkey") ); useKeyboardShortcuts(); // Apply zoom to main content area only (sidebar stays fixed size) useEffect(() => { // Clear any old root zoom document.documentElement.style.zoom = ""; // Set CSS custom property for components that need to know the scale document.documentElement.style.setProperty("--ui-zoom", `${fontSize / 14}`); }, [fontSize]); // Apply color theme useEffect(() => { const theme = getTheme(themeId); if (theme) applyTheme(theme); }, [themeId]); // Intercept external link clicks and open in system browser via Tauri opener useEffect(() => { const handler = (e: MouseEvent) => { const anchor = (e.target as HTMLElement).closest("a[href]") as HTMLAnchorElement | null; if (!anchor) return; const href = anchor.getAttribute("href"); if (!href) return; // Only intercept external http(s) links if (href.startsWith("http://") || href.startsWith("https://")) { e.preventDefault(); e.stopPropagation(); openUrl(href).catch(() => {}); } }; document.addEventListener("click", handler, true); return () => document.removeEventListener("click", handler, true); }, []); if (!onboardingDone) { return setOnboardingDone(true)} />; } return (
{currentView === "search" && } {currentView === "relays" && } {currentView === "settings" && } {currentView === "profile" && } {currentView === "thread" && } {currentView === "articles" && } {currentView === "media" && } {currentView === "article-editor" && } {currentView === "article" && } {currentView === "about" && } {currentView === "zaps" && } {currentView === "dm" && } {currentView === "notifications" && } {currentView === "bookmarks" && } {currentView === "hashtag" && } {currentView === "podcasts" && } {currentView === "follows" && } {currentView === "v4v" && }
{showDebugPanel && } {showHelp && }
); } export default App;