mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
refactor: eliminate code duplication in App.tsx navigation logic
- Extract findNavItemByPath as a reusable helper function - Remove duplicate findItem function definitions - Use consistent updateSEO function in both handleItemClick and popstate handler - Move updateSEO function definition before its usage to fix reference order This improves code maintainability by reducing duplication and ensuring consistent SEO update behavior across all navigation paths. Co-authored-by: Mervin Praison <[email protected]>
This commit is contained in:
1 parent
de6d655250
commit
963735699c
1 file changed
+87
-117
+87
-117
@@ -31,131 +31,32 @@ export default function App() {
|
||||
const [selectedItem, setSelectedItem] = useState<NavItem | null>(null)
|
||||
const [activeItemPath, setActiveItemPath] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
async function loadManifests() {
|
||||
try {
|
||||
const [configRes, navRes, routesRes] = await Promise.all([
|
||||
fetch('/ui-config.json'),
|
||||
fetch('/docs-nav.json'),
|
||||
fetch('/route-manifest.json'),
|
||||
])
|
||||
|
||||
if (!configRes.ok || !navRes.ok || !routesRes.ok) {
|
||||
throw new Error('Failed to load manifests')
|
||||
// Helper function to find nav item by path
|
||||
const findNavItemByPath = (navData: DocsNav, path: string): NavItem | null => {
|
||||
const findItem = (items: NavItem[]): NavItem | null => {
|
||||
for (const item of items) {
|
||||
if (item.path === path || item.path === path.replace(/^\//, '')) {
|
||||
return item
|
||||
}
|
||||
|
||||
const configData = await configRes.json()
|
||||
const navData = await navRes.json()
|
||||
setConfig(configData)
|
||||
setNav(navData)
|
||||
setRoutes(await routesRes.json())
|
||||
|
||||
// Apply theme from YAML config
|
||||
const theme = configData.site?.theme
|
||||
applyTheme(
|
||||
theme?.preset || 'zinc',
|
||||
theme?.darkMode !== false,
|
||||
theme?.radius || 'md'
|
||||
)
|
||||
|
||||
// Handle initial URL path for SPA routing
|
||||
const currentPath = window.location.pathname
|
||||
if (currentPath && currentPath !== '/') {
|
||||
// Find matching nav item by path
|
||||
const findItem = (items: NavItem[]): NavItem | null => {
|
||||
for (const item of items) {
|
||||
if (item.path === currentPath || item.path === currentPath.replace(/^\//, '')) {
|
||||
return item
|
||||
}
|
||||
if (item.children) {
|
||||
const found = findItem(item.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Search all nav groups
|
||||
if (navData.items) {
|
||||
for (const group of navData.items) {
|
||||
if (group.children) {
|
||||
const found = findItem(group.children)
|
||||
if (found) {
|
||||
setSelectedItem(found)
|
||||
setActiveItemPath(found.path || found.title)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.children) {
|
||||
const found = findItem(item.children)
|
||||
if (found) return found
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
loadManifests()
|
||||
}, [])
|
||||
|
||||
// Handle browser back/forward navigation in a separate effect
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
const path = window.location.pathname
|
||||
|
||||
// Find matching nav item by path
|
||||
const findItem = (items: NavItem[]): NavItem | null => {
|
||||
for (const item of items) {
|
||||
if (item.path === path || item.path === path.replace(/^\//, '')) {
|
||||
return item
|
||||
}
|
||||
if (item.children) {
|
||||
const found = findItem(item.children)
|
||||
if (found) return found
|
||||
}
|
||||
// Search all nav groups
|
||||
if (navData.items) {
|
||||
for (const group of navData.items) {
|
||||
if (group.children) {
|
||||
const found = findItem(group.children)
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Search all nav groups for the matching path
|
||||
let found: NavItem | null = null
|
||||
if (nav.items) {
|
||||
for (const group of nav.items) {
|
||||
if (group.children) {
|
||||
found = findItem(group.children)
|
||||
if (found) break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
setSelectedItem(found)
|
||||
setActiveItemPath(found.path || found.title)
|
||||
// Update SEO meta tags with current page title
|
||||
const siteName = config.site?.title || 'Documentation'
|
||||
const titleTemplate = config.seo?.titleTemplate || '%s | %s'
|
||||
if (titleTemplate.includes('%s')) {
|
||||
// Replace first %s with page title, second %s (if exists) with site name
|
||||
let formattedTitle = titleTemplate.replace('%s', found.title)
|
||||
if (formattedTitle.includes('%s')) {
|
||||
formattedTitle = formattedTitle.replace('%s', siteName)
|
||||
}
|
||||
document.title = formattedTitle
|
||||
} else {
|
||||
document.title = `${found.title} | ${siteName}`
|
||||
}
|
||||
} else if (path === '/') {
|
||||
setSelectedItem(null)
|
||||
setActiveItemPath('')
|
||||
// Reset title to site title
|
||||
document.title = config.site?.title || 'Documentation'
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handlePopState)
|
||||
return () => window.removeEventListener('popstate', handlePopState)
|
||||
}, [nav, config])
|
||||
return null
|
||||
}
|
||||
|
||||
// Update SEO meta tags dynamically
|
||||
const updateSEO = (title: string, path: string, description?: string) => {
|
||||
@@ -233,6 +134,75 @@ export default function App() {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function loadManifests() {
|
||||
try {
|
||||
const [configRes, navRes, routesRes] = await Promise.all([
|
||||
fetch('/ui-config.json'),
|
||||
fetch('/docs-nav.json'),
|
||||
fetch('/route-manifest.json'),
|
||||
])
|
||||
|
||||
if (!configRes.ok || !navRes.ok || !routesRes.ok) {
|
||||
throw new Error('Failed to load manifests')
|
||||
}
|
||||
|
||||
const configData = await configRes.json()
|
||||
const navData = await navRes.json()
|
||||
setConfig(configData)
|
||||
setNav(navData)
|
||||
setRoutes(await routesRes.json())
|
||||
|
||||
// Apply theme from YAML config
|
||||
const theme = configData.site?.theme
|
||||
applyTheme(
|
||||
theme?.preset || 'zinc',
|
||||
theme?.darkMode !== false,
|
||||
theme?.radius || 'md'
|
||||
)
|
||||
|
||||
// Handle initial URL path for SPA routing
|
||||
const currentPath = window.location.pathname
|
||||
if (currentPath && currentPath !== '/') {
|
||||
const found = findNavItemByPath(navData, currentPath)
|
||||
if (found) {
|
||||
setSelectedItem(found)
|
||||
setActiveItemPath(found.path || found.title)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadManifests()
|
||||
}, [])
|
||||
|
||||
// Handle browser back/forward navigation in a separate effect
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
const path = window.location.pathname
|
||||
const found = findNavItemByPath(nav, path)
|
||||
|
||||
if (found) {
|
||||
setSelectedItem(found)
|
||||
setActiveItemPath(found.path || found.title)
|
||||
// Use updateSEO function for consistency
|
||||
updateSEO(found.title, path)
|
||||
} else if (path === '/') {
|
||||
setSelectedItem(null)
|
||||
setActiveItemPath('')
|
||||
// Reset title to site title
|
||||
document.title = config.site?.title || 'Documentation'
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handlePopState)
|
||||
return () => window.removeEventListener('popstate', handlePopState)
|
||||
}, [nav, config])
|
||||
|
||||
const handleItemClick = (item: NavItem) => {
|
||||
setSelectedItem(item)
|
||||
setActiveItemPath(item.path || item.title)
|
||||
|
||||
Reference in new issue
Block a user