/** * SettingsPage - Settings page component * Derived from Page component * Displays all settings fragment routes as panels */ import React, { useMemo } from 'react'; import { Page } from '../components/Page.jsx'; import { YStack, Input, Text } from 'tamagui'; import { useRouter, useRoute } from '../components/Router.jsx'; import { getRootItem } from '../../platform/menu.js'; import { securityService, useSecurityState } from '../../security/runtime/security-service.js'; /** * SettingsPage Component * Settings page with icon and title * Queries Router for all routes under /settings and renders them as panels */ export function SettingsPage() { const router = useRouter(); const route = useRoute(); const securityState = useSecurityState(); // Initialize search query from fragment path if we navigated via a fragment route const initialSearchQuery = useMemo(() => { if (route.fragment_path) { // Extract the last segment from fragment path (e.g., "general" from "/settings/general") const segments = route.fragment_path.split('/').filter(s => s.length > 0); if (segments.length > 1) { return segments[segments.length - 1]; // Return last segment } } return ''; // No fragment, show all }, [route.fragment_path]); const [searchQuery, setSearchQuery] = React.useState(initialSearchQuery); // Update search query when fragment path changes (e.g., navigating between fragments) React.useEffect(() => { setSearchQuery(initialSearchQuery); }, [initialSearchQuery]); // Get the base path for this settings page (flexible - could be /settings, /config, etc.) const basePath = useMemo(() => { // Get current route path and extract the base (e.g., /settings from /settings/general) if (route.path) { // If it's a fragment route, use fragment_path, otherwise use path const currentPath = route.fragment_path || route.path; // Extract base path (first segment) const segments = currentPath.split('/').filter(s => s.length > 0); return segments.length > 0 ? `/${segments[0]}` : '/settings'; } return '/settings'; // Default fallback }, [route.path, route.fragment_path]); // Query Router for all routes under the base path // Depend on router.currentRoute to trigger recalculation when routes are registered // (currentRoute depends on routesVersion which changes when routes are registered) const childRoutes = useMemo(() => { const allRoutes = router.getRoutes(); const settingsRoot = getRootItem('settings'); const settingsItems = settingsRoot ? Array.from(settingsRoot.items.values()) : []; const security = { ...securityState, isPermitted: (rights, resourcePath, options = {}) => securityService.isPermitted(rights, resourcePath, options) }; const routes = []; // Find all routes that start with basePath but are not the basePath itself for (const [path, routeData] of allRoutes.entries()) { if (path.startsWith(basePath + '/') && path !== basePath) { // Only include fragment routes if (routeData.is_fragment) { const matchingMenuItem = settingsItems.find((item) => item.invoke_target === path); if (matchingMenuItem && !matchingMenuItem.isRenderable(security)) { continue; } routes.push({ path, component: routeData.component, options: routeData.options || {} }); } } } // Sort by path for consistent ordering routes.sort((a, b) => a.path.localeCompare(b.path)); return routes; }, [router, basePath, router.currentRoute, securityState]); // Include router.currentRoute to trigger when routes are registered // Filter routes based on search query const filteredRoutes = useMemo(() => { if (!searchQuery.trim()) { return childRoutes; } const query = searchQuery.toLowerCase(); return childRoutes.filter(route => { // Extract the last segment of the path as the route name const segments = route.path.split('/').filter(s => s.length > 0); const routeName = segments[segments.length - 1] || ''; return routeName.toLowerCase().includes(query); }); }, [childRoutes, searchQuery]); return ( {/* Search bar */} {/* Render all child route components */} {filteredRoutes.length > 0 ? ( filteredRoutes.map((routeItem) => { const RouteComponent = routeItem.component; return ( ); }) ) : ( {searchQuery ? 'No settings found matching your search.' : 'No settings available.'} )} ); } export default SettingsPage;