diff --git a/telcoinwiki-react/src/App.tsx b/telcoinwiki-react/src/App.tsx
index 8618e07..3082e57 100644
--- a/telcoinwiki-react/src/App.tsx
+++ b/telcoinwiki-react/src/App.tsx
@@ -1,38 +1,40 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
-import styles from './App.module.css'
+import type { SidebarHeading } from './config/types'
+import { AppLayout } from './components/layout/AppLayout'
+import { NAV_ITEMS } from './config/navigation'
+import { PAGE_META } from './config/pageMeta'
+import { SEARCH_CONFIG } from './config/search'
-function App() {
- const [count, setCount] = useState(0)
+const demoHeadings: SidebarHeading[] = [
+ { id: 'welcome', text: 'Welcome' },
+ { id: 'next-steps', text: 'Next steps' },
+]
+function App() {
return (
- <>
-
- Vite + React
-
-
setCount((count) => count + 1)}>
- count is {count}
-
+
+
+ Community Q&A for Telcoin
+ React layout shell
+
+ This demo renders the site header, sidebar, and breadcrumb trail from the shared
+ configuration so content authors only touch JSON-like data files.
+
+
+
+
+ Next steps
- Edit src/App.tsx and save to test HMR
+ Replace the placeholder sections with real page content. Update navigation, breadcrumb, and
+ search behaviour exclusively through the configuration modules in src/config.
-
-
- Click on the Vite and React logos to learn more
-
- >
+
+
)
}
diff --git a/telcoinwiki-react/src/components/layout/AppLayout.tsx b/telcoinwiki-react/src/components/layout/AppLayout.tsx
new file mode 100644
index 0000000..9abd28d
--- /dev/null
+++ b/telcoinwiki-react/src/components/layout/AppLayout.tsx
@@ -0,0 +1,57 @@
+import { useMemo } from 'react'
+import type {
+ NavItem,
+ PageMetaMap,
+ SearchConfig,
+ SidebarHeading,
+} from '../../config/types'
+import type { ReactNode } from 'react'
+import { Breadcrumbs } from './Breadcrumbs'
+import { Header } from './Header'
+import { Sidebar } from './Sidebar'
+
+interface AppLayoutProps {
+ pageId: string
+ navItems: NavItem[]
+ pageMeta: PageMetaMap
+ searchConfig: SearchConfig
+ headings?: SidebarHeading[]
+ children: ReactNode
+}
+
+export function AppLayout({
+ pageId,
+ navItems,
+ pageMeta,
+ searchConfig,
+ headings = [],
+ children,
+}: AppLayoutProps) {
+ const currentMeta = pageMeta[pageId] ?? pageMeta.home
+ const activeNavId = currentMeta?.navId ?? pageId
+
+ const sidebarItems = useMemo(
+ () =>
+ Object.entries(pageMeta)
+ .filter(([, meta]) => meta.sidebar)
+ .map(([id, meta]) => ({ id, label: meta.label, href: meta.url })),
+ [pageMeta],
+ )
+
+ return (
+ <>
+
+ Skip to content
+
+
+
+
+
+
+
+ {children}
+
+
+ >
+ )
+}
diff --git a/telcoinwiki-react/src/components/layout/Breadcrumbs.tsx b/telcoinwiki-react/src/components/layout/Breadcrumbs.tsx
new file mode 100644
index 0000000..631112e
--- /dev/null
+++ b/telcoinwiki-react/src/components/layout/Breadcrumbs.tsx
@@ -0,0 +1,49 @@
+import type { BreadcrumbNode, PageMeta, PageMetaMap } from '../../config/types'
+
+interface BreadcrumbsProps {
+ pageId: string
+ pageMeta: PageMetaMap
+}
+
+function buildBreadcrumbsTrail(pageId: string, pageMeta: PageMetaMap): BreadcrumbNode[] {
+ const trail: BreadcrumbNode[] = []
+ let pointer: string | null = pageId
+
+ while (pointer) {
+ const node: PageMeta | undefined = pageMeta[pointer]
+ if (!node) break
+ trail.unshift({ id: pointer, label: node.label, url: node.url })
+ pointer = node.parent
+ }
+
+ if (!trail.length || trail[0].id !== 'home') {
+ const home = pageMeta.home
+ if (home) {
+ trail.unshift({ id: 'home', label: home.label, url: home.url })
+ }
+ }
+
+ return trail
+}
+
+export function Breadcrumbs({ pageId, pageMeta }: BreadcrumbsProps) {
+ const trail = buildBreadcrumbsTrail(pageId, pageMeta)
+
+ return (
+
+ {trail.map((node, index) => {
+ const isLast = index === trail.length - 1
+ return (
+
+ {index > 0 ? / : null}
+ {isLast ? (
+ {node.label}
+ ) : (
+ {node.label}
+ )}
+
+ )
+ })}
+
+ )
+}
diff --git a/telcoinwiki-react/src/components/layout/Header.tsx b/telcoinwiki-react/src/components/layout/Header.tsx
new file mode 100644
index 0000000..55c2643
--- /dev/null
+++ b/telcoinwiki-react/src/components/layout/Header.tsx
@@ -0,0 +1,179 @@
+import { useEffect, useMemo, useRef, useState } from 'react'
+import type { NavItem, SearchConfig } from '../../config/types'
+
+interface HeaderProps {
+ navItems: NavItem[]
+ activeNavId?: string | null
+ searchConfig: SearchConfig
+}
+
+export function Header({ navItems, activeNavId, searchConfig }: HeaderProps) {
+ const [openMenuId, setOpenMenuId] = useState(null)
+ const [mobileNavOpen, setMobileNavOpen] = useState(false)
+ const navListRef = useRef(null)
+ const { dataUrl, faqUrl, maxResultsPerGroup } = searchConfig
+
+ useEffect(() => {
+ function handleDocumentClick(event: MouseEvent) {
+ if (!openMenuId) return
+ if (!(event.target instanceof Node)) return
+ if (navListRef.current && !navListRef.current.contains(event.target)) {
+ setOpenMenuId(null)
+ }
+ }
+
+ document.addEventListener('click', handleDocumentClick)
+ return () => document.removeEventListener('click', handleDocumentClick)
+ }, [openMenuId])
+
+ useEffect(() => {
+ function handleKeydown(event: KeyboardEvent) {
+ if (event.key === 'Escape') {
+ setOpenMenuId(null)
+ }
+ }
+
+ if (openMenuId) {
+ document.addEventListener('keydown', handleKeydown)
+ return () => document.removeEventListener('keydown', handleKeydown)
+ }
+
+ return undefined
+ }, [openMenuId])
+
+ const mobileItems = useMemo(() => navItems, [navItems])
+
+ function toggleMenu(itemId: string) {
+ setOpenMenuId((current) => (current === itemId ? null : itemId))
+ }
+
+ function closeMenus() {
+ setOpenMenuId(null)
+ }
+
+ function toggleMobileNav() {
+ setMobileNavOpen((current) => !current)
+ }
+
+ function handleMobileLinkClick() {
+ setMobileNavOpen(false)
+ }
+
+ return (
+
+ )
+}
diff --git a/telcoinwiki-react/src/components/layout/Sidebar.tsx b/telcoinwiki-react/src/components/layout/Sidebar.tsx
new file mode 100644
index 0000000..2c4694d
--- /dev/null
+++ b/telcoinwiki-react/src/components/layout/Sidebar.tsx
@@ -0,0 +1,58 @@
+import type { SidebarHeading } from '../../config/types'
+
+interface SidebarItemProps {
+ id: string
+ label: string
+ href: string
+}
+
+interface SidebarProps {
+ items: SidebarItemProps[]
+ activeId?: string | null
+ headings?: SidebarHeading[]
+ isOpen?: boolean
+}
+
+export function Sidebar({ items, activeId, headings = [], isOpen = false }: SidebarProps) {
+ return (
+
+ )
+}
diff --git a/telcoinwiki-react/src/config/README.md b/telcoinwiki-react/src/config/README.md
new file mode 100644
index 0000000..c5097ca
--- /dev/null
+++ b/telcoinwiki-react/src/config/README.md
@@ -0,0 +1,15 @@
+# Layout configuration guide
+
+The React layout consumes a trio of typed configuration modules. Editors can update site
+structure by editing these data exports instead of touching component code.
+
+- `navigation.ts` exports `NAV_ITEMS`, mirroring the legacy `js/main.js` array. Each item keeps the
+ same `id`, `label`, `href`, and optional `menu` shape so existing docs map 1:1.
+- `pageMeta.ts` exports `PAGE_META`, the breadcrumb tree used across the wiki. Entries now accept an
+ optional `sidebar: true` flag that replaces the hard-coded sidebar list from the static HTML.
+ Add/remove this flag to control the “Knowledge base” links.
+- `search.ts` exports `SEARCH_CONFIG`, preserving the `dataUrl`, `faqUrl`, and `maxResultsPerGroup`
+ keys from the static bundle.
+
+Component props (`NavItem`, `PageMeta`, `SearchConfig`, etc.) are defined in `types.ts`. Update these
+types if a field name changes so TypeScript guides you to adjust dependent components.
diff --git a/telcoinwiki-react/src/config/navigation.ts b/telcoinwiki-react/src/config/navigation.ts
new file mode 100644
index 0000000..319c5c6
--- /dev/null
+++ b/telcoinwiki-react/src/config/navigation.ts
@@ -0,0 +1,28 @@
+import type { NavItems } from './types'
+
+export const NAV_ITEMS: NavItems = [
+ { id: 'start-here', label: 'Start Here', href: '/start-here.html', menu: null },
+ {
+ id: 'faq',
+ label: 'FAQ',
+ href: '/#faq',
+ menu: [
+ { label: 'Basics', href: '/#faq-basics' },
+ { label: 'Network & MNOs', href: '/#faq-network' },
+ { label: 'Bank & eUSD', href: '/#faq-bank' },
+ { label: 'Using TEL & App', href: '/#faq-app' },
+ ],
+ },
+ {
+ id: 'deep-dive',
+ label: 'Deep-Dive',
+ href: '/deep-dive.html',
+ menu: [
+ { label: 'Telcoin Network', href: '/deep-dive.html#deep-network' },
+ { label: '$TEL Token', href: '/deep-dive.html#deep-token' },
+ { label: 'TELx Liquidity Engine', href: '/deep-dive.html#deep-telx' },
+ { label: 'Association & Governance', href: '/deep-dive.html#deep-governance' },
+ { label: 'Telcoin Holdings', href: '/deep-dive.html#deep-holdings' },
+ ],
+ },
+]
diff --git a/telcoinwiki-react/src/config/pageMeta.ts b/telcoinwiki-react/src/config/pageMeta.ts
new file mode 100644
index 0000000..a72f66e
--- /dev/null
+++ b/telcoinwiki-react/src/config/pageMeta.ts
@@ -0,0 +1,106 @@
+import type { PageMetaMap } from './types'
+
+export const PAGE_META: PageMetaMap = {
+ home: { label: 'Home', url: '/index.html', parent: null, navId: null },
+ 'start-here': {
+ label: 'Start Here',
+ url: '/start-here.html',
+ parent: 'home',
+ navId: 'start-here',
+ sidebar: true,
+ },
+ faq: { label: 'FAQ', url: '/faq/', parent: 'home', navId: 'faq', sidebar: true },
+ wallet: {
+ label: 'Telcoin Wallet',
+ url: '/wallet.html',
+ parent: 'home',
+ navId: 'wallet',
+ sidebar: true,
+ },
+ 'digital-cash': {
+ label: 'Digital Cash',
+ url: '/digital-cash.html',
+ parent: 'home',
+ navId: 'digital-cash',
+ sidebar: true,
+ },
+ remittances: {
+ label: 'Remittances',
+ url: '/remittances.html',
+ parent: 'home',
+ navId: 'remittances',
+ sidebar: true,
+ },
+ 'tel-token': {
+ label: 'TEL Token',
+ url: '/tel-token.html',
+ parent: 'home',
+ navId: 'tel-token',
+ sidebar: true,
+ },
+ network: {
+ label: 'Telcoin Network',
+ url: '/network.html',
+ parent: 'home',
+ navId: 'network',
+ sidebar: true,
+ },
+ telx: {
+ label: 'TELx Liquidity',
+ url: '/telx.html',
+ parent: 'home',
+ navId: 'telx',
+ sidebar: true,
+ },
+ governance: {
+ label: 'Governance & Association',
+ url: '/governance.html',
+ parent: 'home',
+ navId: 'governance',
+ sidebar: true,
+ },
+ builders: {
+ label: 'Builders',
+ url: '/builders.html',
+ parent: 'home',
+ navId: 'builders',
+ sidebar: true,
+ },
+ links: {
+ label: 'Official Links',
+ url: '/links.html',
+ parent: 'home',
+ navId: 'links',
+ sidebar: true,
+ },
+ 'deep-dive': {
+ label: 'Deep Dive',
+ url: '/deep-dive.html',
+ parent: 'home',
+ navId: 'deep-dive',
+ },
+ pools: {
+ label: 'TELx Pools Dashboard',
+ url: '/pools.html',
+ parent: 'builders',
+ navId: 'builders',
+ },
+ portfolio: {
+ label: 'TELx Portfolio Explorer',
+ url: '/portfolio.html',
+ parent: 'builders',
+ navId: 'builders',
+ },
+ about: {
+ label: 'About this project',
+ url: '/about.html',
+ parent: 'home',
+ navId: null,
+ },
+ '404': {
+ label: 'Page not found',
+ url: '/404.html',
+ parent: 'home',
+ navId: null,
+ },
+}
diff --git a/telcoinwiki-react/src/config/search.ts b/telcoinwiki-react/src/config/search.ts
new file mode 100644
index 0000000..ffb3ba5
--- /dev/null
+++ b/telcoinwiki-react/src/config/search.ts
@@ -0,0 +1,7 @@
+import type { SearchConfig } from './types'
+
+export const SEARCH_CONFIG: SearchConfig = {
+ dataUrl: '/data/search-index.json',
+ faqUrl: '/data/faq.json',
+ maxResultsPerGroup: 5,
+}
diff --git a/telcoinwiki-react/src/config/types.ts b/telcoinwiki-react/src/config/types.ts
new file mode 100644
index 0000000..aee9c3c
--- /dev/null
+++ b/telcoinwiki-react/src/config/types.ts
@@ -0,0 +1,40 @@
+export interface NavChild {
+ label: string
+ href: string
+}
+
+export interface NavItem {
+ id: string
+ label: string
+ href: string
+ menu?: NavChild[] | null
+}
+
+export type NavItems = NavItem[]
+
+export interface PageMeta {
+ label: string
+ url: string
+ parent: string | null
+ navId: string | null
+ sidebar?: boolean
+}
+
+export type PageMetaMap = Record
+
+export interface SearchConfig {
+ dataUrl: string
+ faqUrl: string
+ maxResultsPerGroup: number
+}
+
+export interface SidebarHeading {
+ id: string
+ text: string
+}
+
+export interface BreadcrumbNode {
+ id: string
+ label: string
+ url: string
+}