import { useEffect, type ReactElement } from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate, useLocation } from 'react-router-dom'; import { LandingPage } from './pages/LandingPage'; import { EditorPage } from './pages/EditorPage'; import { ExamplesPage } from './pages/ExamplesPage'; import { DocsPage } from './pages/DocsPage'; // Login, Register, ForgotPassword, ResetPassword, Admin, UserProfile, // Project, ProjectById — moved to the pro overlay in Phase 3 of the // OSS split. They register themselves via registerProRoutes() inside // mountPro() and appear under /login, /admin, /:username etc. only when // the overlay is loaded. import { ExampleDetailPage } from './pages/ExampleDetailPage'; import { ExampleEditorPage } from './pages/ExampleEditorPage'; import { ArduinoSimulatorPage } from './pages/ArduinoSimulatorPage'; import { ArduinoEmulatorPage } from './pages/ArduinoEmulatorPage'; import { AtmegaSimulatorPage } from './pages/AtmegaSimulatorPage'; import { ArduinoMegaSimulatorPage } from './pages/ArduinoMegaSimulatorPage'; import { Attiny85SimulatorPage } from './pages/Attiny85SimulatorPage'; import { CircuitSimulatorPage } from './pages/CircuitSimulatorPage'; import { SpiceSimulatorPage } from './pages/SpiceSimulatorPage'; import { ElectronicsSimulatorPage } from './pages/ElectronicsSimulatorPage'; import { CustomChipSimulatorPage } from './pages/CustomChipSimulatorPage'; import { Esp32SimulatorPage } from './pages/Esp32SimulatorPage'; import { Esp32S3SimulatorPage } from './pages/Esp32S3SimulatorPage'; import { Esp32C3SimulatorPage } from './pages/Esp32C3SimulatorPage'; import { RaspberryPiPicoSimulatorPage } from './pages/RaspberryPiPicoSimulatorPage'; import { RaspberryPiSimulatorPage } from './pages/RaspberryPiSimulatorPage'; import { Velxio2Page } from './pages/Velxio2Page'; import { Velxio25Page } from './pages/Velxio25Page'; import { Velxio3Page } from './pages/Velxio3Page'; import { AboutPage } from './pages/AboutPage'; import { PricingPlaceholder } from './pages/PricingPlaceholder'; import { LocaleSync } from './i18n/LocaleSync'; import { NON_DEFAULT_LOCALES } from './i18n/config'; import { useProRoutes } from './lib/proRoutes'; import { triggerSessionCheck } from './lib/proSession'; import { MessageDialogHost } from './components/ui/MessageDialogHost'; import './App.css'; /** * Single source of truth for the route tree. Each entry is registered * twice in below: once at the root (default locale) and once * nested under each non-default locale prefix (e.g. `/es/editor`). * * Index entries (path === '') belong to the locale-prefixed parent's * `index` slot — they render at exactly `//`. */ // In Tauri desktop builds the marketing landing page is a disorienting // first screen — users opened the desktop app to land in the editor. // `/` redirects there. Web builds still see the LandingPage. const ROOT_ELEMENT: ReactElement = import.meta.env.VITE_DESKTOP ? ( ) : ( ); const ROUTES: { path: string; element: ReactElement; index?: boolean }[] = [ { path: '/', element: ROOT_ELEMENT, index: true }, { path: 'editor', element: }, { path: 'examples', element: }, // /examples/ = SEO landing (preview, badges, "Open in Simulator" CTA). // /example/ = live editor with the example pre-loaded; the URL // stays pinned so links are shareable + bookmarkable. // Singular vs plural is intentional — Google indexes the plural landings. { path: 'examples/:exampleId', element: }, { path: 'example/:exampleId', element: }, { path: 'docs', element: }, { path: 'docs/:section', element: }, // SEO landing pages — keyword-targeted { path: 'circuit-simulator', element: }, { path: 'spice-simulator', element: }, { path: 'electronics-simulator', element: }, { path: 'custom-chip-simulator', element: }, { path: 'attiny85-simulator', element: }, { path: 'arduino-simulator', element: }, { path: 'arduino-emulator', element: }, { path: 'atmega328p-simulator', element: }, { path: 'arduino-mega-simulator', element: }, { path: 'esp32-simulator', element: }, { path: 'esp32-s3-simulator', element: }, { path: 'esp32-c3-simulator', element: }, { path: 'raspberry-pi-pico-simulator', element: }, { path: 'raspberry-pi-simulator', element: }, { path: 'v2', element: }, { path: 'v2-5', element: }, { path: 'v3', element: }, { path: 'about', element: }, // Pricing — placeholder by default; private overlays portal-inject the real page { path: 'pricing', element: }, // project/:id, :username/:projectName, :username — also moved to the // pro overlay (project persistence + public profiles are pro features). ]; /** * The default locale (English) is served at the root with NO `/en` prefix, so * `/en/...` matches no route and renders blank. People reasonably guess `/en/` * by analogy with `/es/`, `/zh-cn/`, … — redirect them to the prefix-free path * (`/en/project/x` → `/project/x`, `/en` → `/`) instead of a blank page. This * keeps the canonical no-prefix English URLs (good for SEO) while handling the * guessed ones gracefully. */ function EnPrefixRedirect() { const { pathname, search, hash } = useLocation(); const stripped = pathname.replace(/^\/en(?=\/|$)/, ''); return ; } function App() { // Pro overlay registers extra routes (login, register, admin, profile, // project-by-slug, …) via registerProRoutes() inside mountPro(). The // subscription is sync external store, so any registration after the // initial render triggers a re-render — no Not-Found flash for routes // the overlay was about to add. const proRoutes = useProRoutes(); const allRoutes = [...ROUTES, ...proRoutes]; useEffect(() => { // Pro overlay's mountPro() registers a session-check callback that // resolves the JWT cookie into a user object. No-op in OSS without // the overlay. triggerSessionCheck(); // #root-seo is a static SEO fallback in index.html (position:absolute, // visibility:hidden). It still contributes to document scrollHeight, so // every page got a phantom scroll the size of the prerendered SEO body. document.getElementById('root-seo')?.remove(); }, []); return ( {/* Default locale (English) — no URL prefix. */} {allRoutes.map((r) => r.index ? ( ) : ( ) )} {/* Non-default locales — same routes nested under `//`. We register one branch per locale rather than a `:lang` param so React Router doesn't accidentally swallow real top-level paths like `/circuit-simulator` as a locale segment. */} {NON_DEFAULT_LOCALES.map((locale) => ( {allRoutes.map((r) => r.index ? ( ) : ( ) )} ))} {/* `/en/...` is the default locale spelled out — redirect to the canonical prefix-free path instead of rendering a blank page. */} } /> {/* Global alert() replacement — opened from anywhere (React or plain .ts) via showMessageDialog() in store/useMessageDialogStore. */} ); } export default App;