From b79d8811d87f0d47999a8675ce3a6798cfbd9726 Mon Sep 17 00:00:00 2001 From: Gokce Yalcin Date: Fri, 13 Jun 2025 02:44:26 -0700 Subject: [PATCH 01/17] WIP(devx-ui): initial docs page --- devex-ui/src/App.tsx | 97 ++++++++++--- devex-ui/src/components/DocsSidebar.tsx | 166 ++++++++++++++++++++++ devex-ui/src/components/Sidebar.tsx | 17 ++- devex-ui/src/pages/Index.tsx | 7 +- devex-ui/src/pages/WelcomePage.tsx | 181 ++++++++++++++++++++++++ 5 files changed, 444 insertions(+), 24 deletions(-) create mode 100644 devex-ui/src/components/DocsSidebar.tsx create mode 100644 devex-ui/src/pages/WelcomePage.tsx diff --git a/devex-ui/src/App.tsx b/devex-ui/src/App.tsx index 3d110114..7d23650f 100644 --- a/devex-ui/src/App.tsx +++ b/devex-ui/src/App.tsx @@ -3,8 +3,10 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { TooltipProvider } from '@/components/ui/tooltip'; import ErrorBoundary from '@/components/ErrorBoundary'; import { AppProvider } from '@/app/AppProvider'; +import { useEffect, useState } from 'react'; import Index from './pages/Index'; +import WelcomePage from './pages/WelcomePage'; import NotFound from './pages/NotFound'; /** all sidebar slugs except the default “dashboard” */ @@ -20,27 +22,78 @@ const VIEWS = [ 'settings', ] as const; -const App = () => ( - - - - - - {/* dashboard */} - } /> - - {/* other sidebar views → same Index page for now */} - {VIEWS.map(view => ( - } /> - ))} - - {/* fallback */} - } /> - - - - - -); +const App = () => { + // Check if docs mode is enabled via environment variable or localStorage + const [isDocsMode, setIsDocsMode] = useState(() => { + // First check localStorage (for switching between modes) + const storedMode = localStorage.getItem('docs_mode'); + if (storedMode !== null) { + return storedMode === 'true'; + } + // Then check environment variable + return import.meta.env.VITE_DOCS_MODE === 'true'; + }); + + // Listen for storage changes (for cross-tab sync) + useEffect(() => { + const handleStorageChange = (e: StorageEvent) => { + if (e.key === 'docs_mode' && e.newValue !== null) { + setIsDocsMode(e.newValue === 'true'); + } + }; + + window.addEventListener('storage', handleStorageChange); + return () => window.removeEventListener('storage', handleStorageChange); + }, []); + + const handleSwitchToDocs = () => { + localStorage.setItem('docs_mode', 'true'); + setIsDocsMode(true); + }; + + const handleSwitchToConsole = () => { + localStorage.setItem('docs_mode', 'false'); + setIsDocsMode(false); + }; + + return ( + + + + + {isDocsMode ? ( + + {/* Documentation routes */} + } /> + } /> + } /> + + {/* fallback */} + } /> + + ) : ( + + {/* dashboard */} + } /> + + {/* other sidebar views → same Index page for now */} + {VIEWS.map(view => ( + } + /> + ))} + + {/* fallback */} + } /> + + )} + + + + + ); +}; export default App; diff --git a/devex-ui/src/components/DocsSidebar.tsx b/devex-ui/src/components/DocsSidebar.tsx new file mode 100644 index 00000000..89d203b9 --- /dev/null +++ b/devex-ui/src/components/DocsSidebar.tsx @@ -0,0 +1,166 @@ +// devex-ui/src/components/DocsSidebar.tsx +import { useState } from 'react'; +import { + Book, + FileText, + Code, + BookOpen, + ExternalLink, + ChevronLeft, + ChevronRight, + ChevronDown, + ChevronUp, + Home, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; + +/* ─────────────────────── Types / constants ────────────────────── */ + +type View = 'welcome' | 'guidelines' | 'architecture' | 'examples' | 'references'; + +interface DocsSidebarProps { + onViewChange?: (view: View) => void; + activeView?: View; + onSwitchToConsole?: () => void; +} + +interface NavItem { + id: View; + label: string; + icon: React.ElementType; + children?: { id: string; label: string }[]; +} + +const NAV_ITEMS: NavItem[] = [ + { id: 'welcome', label: 'Welcome', icon: Home }, + { + id: 'guidelines', + label: 'Guidelines', + icon: FileText, + children: [ + { id: 'simplicity', label: 'Simplicity of Event Sourcing' }, + { id: 'initial-setup', label: 'Initial Setup' }, + ] + }, + { id: 'architecture', label: 'Architecture', icon: Book }, + { id: 'examples', label: 'Examples', icon: Code }, + { id: 'references', label: 'References', icon: BookOpen }, +]; + +/* ───────────────────────── component ───────────────────────────── */ + +export const DocsSidebar = ({ onViewChange, activeView, onSwitchToConsole }: DocsSidebarProps) => { + const [isCollapsed, setIsCollapsed] = useState(false); + const [expandedItems, setExpandedItems] = useState>({ + guidelines: true, // Start with guidelines expanded + }); + + /* source of truth: prefer explicit prop, else derive from URL */ + const current = activeView || 'welcome'; + + const toggleExpand = (id: string) => { + setExpandedItems(prev => ({ + ...prev, + [id]: !prev[id] + })); + }; + + return ( + + ); +}; \ No newline at end of file diff --git a/devex-ui/src/components/Sidebar.tsx b/devex-ui/src/components/Sidebar.tsx index 6a4da97e..a5981cf2 100644 --- a/devex-ui/src/components/Sidebar.tsx +++ b/devex-ui/src/components/Sidebar.tsx @@ -14,9 +14,11 @@ import { ChevronLeft, ChevronRight, LayoutDashboard, + BookOpen, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useFeatures } from '@/hooks/useFeatures'; +import { Button } from '@/components/ui/button'; /* ─────────────────────── Types / constants ────────────────────── */ @@ -35,6 +37,7 @@ type View = interface SidebarProps { onViewChange?: (view: View) => void; activeView?: View; // optional – parent can still control + onSwitchToDocs?: () => void; } const NAV_ITEMS = [ @@ -61,7 +64,7 @@ const viewFromPath = (path: string): View => { /* ───────────────────────── component ───────────────────────────── */ -export const Sidebar = ({ onViewChange, activeView }: SidebarProps) => { +export const Sidebar = ({ onViewChange, activeView, onSwitchToDocs }: SidebarProps) => { const [isCollapsed, setIsCollapsed] = useState(false); const { enabled } = useFeatures(); const { pathname } = useLocation(); @@ -134,6 +137,18 @@ export const Sidebar = ({ onViewChange, activeView }: SidebarProps) => { ); })} + + {/* Switch to Docs button at the bottom */} +
+ +
); diff --git a/devex-ui/src/pages/Index.tsx b/devex-ui/src/pages/Index.tsx index 557f659a..8088ea22 100644 --- a/devex-ui/src/pages/Index.tsx +++ b/devex-ui/src/pages/Index.tsx @@ -19,7 +19,11 @@ import { useAppCtx } from '@/app/AppProvider'; type ActiveView = 'dashboard' | 'commands' | 'events' | 'projections' | 'traces' | 'aggregates' | 'status' | 'rewind' | 'ai' | 'settings'; -const Index = () => { +interface IndexProps { + onSwitchToDocs?: () => void; +} + +const Index = ({ onSwitchToDocs }: IndexProps) => { const initialView = window.location.pathname.replace(/^\//, '') as ActiveView || 'dashboard'; const [activeView, setActiveView] = useState(initialView); const [isAICompanionOpen, setIsAICompanionOpen] = useState(false); @@ -89,6 +93,7 @@ const Index = () => {
diff --git a/devex-ui/src/pages/WelcomePage.tsx b/devex-ui/src/pages/WelcomePage.tsx new file mode 100644 index 00000000..c5c5233f --- /dev/null +++ b/devex-ui/src/pages/WelcomePage.tsx @@ -0,0 +1,181 @@ +import { useState } from "react"; +import { Header } from "@/components/Header"; +import { DocsSidebar } from "@/components/DocsSidebar"; +import { Card, CardContent } from "@/components/ui/card"; +import { useNavigate } from "react-router-dom"; +import { Button } from "@/components/ui/button"; + +type ActiveView = 'welcome' | 'guidelines' | 'architecture' | 'examples' | 'references'; + +interface WelcomePageProps { + onSwitchToConsole?: () => void; +} + +const WelcomePage = ({ onSwitchToConsole }: WelcomePageProps) => { + const initialView = window.location.pathname.replace(/^\/docs\//, '') as ActiveView || 'welcome'; + const [activeView, setActiveView] = useState(initialView || 'welcome'); + const navigate = useNavigate(); + + const renderActiveView = () => { + switch (activeView) { + case 'welcome': + return ( +
+

Welcome to Intent

+

+ Intent turns event-sourcing theory into a platform you can demo in five minutes. + It's a pragmatic, ports-first reference for multi-tenant, event-sourced CQRS back-ends + powered by TypeScript and uses Temporal for durable workflow execution. +

+ +

Highlights

+
+ + +

Lossless backend processing

+

+ Event-sourced core guarantees no data loss, even under retries, crashes, or partial failures. + Structure follows DDD. Every command, event, and projection is persisted and replayable. +

+
+
+ + +

Ports-first hexagon

+

+ Technology-agnostic core logic. Adapters for PostgreSQL (event store + RLS) and + Temporal (workflows) plug in via explicit, testable ports. +

+
+
+ + +

Tenant isolation by default

+

+ Tenant IDs propagate edge → core → infra. Row isolation in DB and namespaced + workflows prevent accidental cross-tenant access or leaks. +

+
+
+ + +

Production-grade observability

+

+ Unified structured logging with context-aware LoggerPort, customizable log levels, + and error serialization. OpenTelemetry spans wrap all key flows. +

+
+
+
+
+ ); + case 'guidelines': + return ( +
+

Guidelines

+ + +

Simplicity of Event Sourcing

+

+ Content about simplicity of event sourcing will be added here. +

+
+
+ + +

Initial Setup

+

+ Content about initial setup will be added here. +

+
+
+
+ ); + case 'architecture': + return ( +
+

Architecture

+ + +

High-level Architecture

+

+ Content about high-level architecture will be added here. +

+
+
+
+ ); + case 'examples': + return ( +
+

Examples

+ + +

Example Applications

+

+ Content about example applications will be added here. +

+
+
+
+ ); + case 'references': + return ( +
+

References

+ + +

API References

+

+ Content about API references will be added here. +

+
+
+
+ ); + default: + return ( +
+

Welcome to Intent

+

Default content

+
+ ); + } + }; + + const handleViewChange = (view: string) => { + setActiveView(view as ActiveView); + navigate(`/docs/${view === 'welcome' ? '' : view}`); + }; + + // Use the prop if provided, otherwise fallback to local implementation + const handleSwitchToConsole = () => { + if (onSwitchToConsole) { + onSwitchToConsole(); + } else { + // Fallback implementation + localStorage.setItem('docs_mode', 'false'); + window.location.href = '/'; + } + }; + + return ( +
+
+ +
+ + +
+ {renderActiveView()} +
+
+
+ ); +}; + +export default WelcomePage; From 30bac60fb5b37cd5a2a93c6d993025060fce7c4c Mon Sep 17 00:00:00 2001 From: Gokce Yalcin Date: Fri, 13 Jun 2025 02:52:04 -0700 Subject: [PATCH 02/17] WIP(devx-ui): make docs minimalistic --- devex-ui/src/components/DocsHeader.tsx | 16 +++++++ devex-ui/src/components/DocsSidebar.tsx | 62 +++++++------------------ devex-ui/src/pages/WelcomePage.tsx | 4 +- 3 files changed, 34 insertions(+), 48 deletions(-) create mode 100644 devex-ui/src/components/DocsHeader.tsx diff --git a/devex-ui/src/components/DocsHeader.tsx b/devex-ui/src/components/DocsHeader.tsx new file mode 100644 index 00000000..56e258fe --- /dev/null +++ b/devex-ui/src/components/DocsHeader.tsx @@ -0,0 +1,16 @@ +import { Logo } from './Logo'; + +export const DocsHeader = () => { + return ( +
+ {/* Logo */} +
+ + Intent Documentation +
+ + {/* Spacer */} +
+
+ ); +}; \ No newline at end of file diff --git a/devex-ui/src/components/DocsSidebar.tsx b/devex-ui/src/components/DocsSidebar.tsx index 89d203b9..094ce877 100644 --- a/devex-ui/src/components/DocsSidebar.tsx +++ b/devex-ui/src/components/DocsSidebar.tsx @@ -51,7 +51,6 @@ const NAV_ITEMS: NavItem[] = [ /* ───────────────────────── component ───────────────────────────── */ export const DocsSidebar = ({ onViewChange, activeView, onSwitchToConsole }: DocsSidebarProps) => { - const [isCollapsed, setIsCollapsed] = useState(false); const [expandedItems, setExpandedItems] = useState>({ guidelines: true, // Start with guidelines expanded }); @@ -67,24 +66,7 @@ export const DocsSidebar = ({ onViewChange, activeView, onSwitchToConsole }: Doc }; return ( -