Skip to main content

Navegación y Routing

App Router Structure

Layout Principal

// app/layout.tsx - Layout principal
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="es">
<body>
<Providers>
<ChakraProvider theme={theme}>
{children}
</ChakraProvider>
</Providers>
</body>
</html>
);
}

Layout Admin

// app/admin/layout.tsx - Layout admin
export default function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<Box minH="100vh">
<Sidebar />
<Box ml={{ base: 0, xl: "300px" }}>
<Navbar />
<Box p={4}>
{children}
</Box>
</Box>
</Box>
);
}

Estructura de Rutas

Rutas Públicas

app/
├── page.tsx # / (landing page)
├── auth/
│ ├── sign-in/
│ │ └── page.tsx # /auth/sign-in
│ ├── forgot-password/
│ │ └── page.tsx # /auth/forgot-password
│ ├── reset-password/
│ │ └── page.tsx # /auth/reset-password
│ └── plans/
│ └── page.tsx # /auth/plans

Rutas Protegidas

app/admin/
├── page.tsx # /admin (dashboard)
├── properties/
│ ├── page.tsx # /admin/properties
│ ├── create/
│ │ └── page.tsx # /admin/properties/create
│ └── [id]/
│ ├── page.tsx # /admin/properties/[id]
│ └── edit/
│ └── page.tsx # /admin/properties/[id]/edit
├── users/
│ ├── page.tsx # /admin/users
│ └── [id]/
│ └── page.tsx # /admin/users/[id]
├── contracts/
│ ├── page.tsx # /admin/contracts
│ └── [id]/
│ └── page.tsx # /admin/contracts/[id]
└── settings/
└── page.tsx # /admin/settings

// src/components/sidebar/Sidebar.tsx
const Sidebar = () => {
const menuItems = useSelector(getMenuItems);
const { isActive: hasActiveSubscription } = useSubscription();

// Filtrar menús por permisos
const filteredMenuItems = menuItems.filter((item) => {
if (!item.permissions_required) return true;

const requiredProfiles = item.permissions_required
.split(',')
.map(profile => profile.trim().toLowerCase());

return activeProfiles.some(userProfile =>
requiredProfiles.includes(userProfile.toLowerCase())
);
});

// Convertir a rutas
const routes: IRoute[] = filteredMenuItems.map((item) => ({
name: item.menu_title,
layout: "/admin",
path: item.path,
icon: iconMap[item.icon] || null,
component: () => <div>{item.menu_title}</div>
}));

return <SidebarContent routes={routes} />;
};

Route Guard

// components/RouteGuard.tsx
export const RouteGuard = ({ children }: { children: React.ReactNode }) => {
const { isAuthenticated } = useAuth();
const pathname = usePathname();

useEffect(() => {
if (!isAuthenticated && pathname.startsWith('/admin')) {
redirect('/auth/sign-in');
}
}, [isAuthenticated, pathname]);

if (!isAuthenticated && pathname.startsWith('/admin')) {
return <Spinner />;
}

return <>{children}</>;
};

Programmatic Navigation

useRouter Hook

import { useRouter } from 'next/navigation';

export const PropertyCard = ({ property }: { property: Property }) => {
const router = useRouter();

const handleViewProperty = () => {
router.push(`/admin/properties/${property.id}`);
};

const handleEditProperty = () => {
router.push(`/admin/properties/${property.id}/edit`);
};

return (
<Card>
<CardBody>
<Button onClick={handleViewProperty}>Ver</Button>
<Button onClick={handleEditProperty}>Editar</Button>
</CardBody>
</Card>
);
};
// utils/navigation.ts
export const navigationUtils = {
goToProperty: (id: string | number) => `/admin/properties/${id}`,
goToUser: (id: string | number) => `/admin/users/${id}`,
goToContract: (id: string | number) => `/admin/contracts/${id}`,
goToCreateProperty: () => '/admin/properties/create',
goToDashboard: () => '/admin',
goToLogin: () => '/auth/sign-in'
};

URL Parameters

Dynamic Routes

// app/admin/properties/[id]/page.tsx
interface PropertyPageProps {
params: { id: string };
searchParams: { [key: string]: string | string[] | undefined };
}

export default function PropertyPage({ params }: PropertyPageProps) {
const propertyId = params.id;

return (
<div>
<h1>Propiedad {propertyId}</h1>
{/* Contenido de la propiedad */}
</div>
);
}

Query Parameters

import { useSearchParams } from 'next/navigation';

export const PropertiesPage = () => {
const searchParams = useSearchParams();
const filter = searchParams.get('filter') || '';
const page = Number(searchParams.get('page')) || 1;

return (
<div>
{/* Lista de propiedades filtrada */}
</div>
);
};

// components/Breadcrumb.tsx
export const Breadcrumb = () => {
const pathname = usePathname();
const segments = pathname.split('/').filter(Boolean);

const breadcrumbItems = segments.map((segment, index) => {
const href = '/' + segments.slice(0, index + 1).join('/');
const label = segment.charAt(0).toUpperCase() + segment.slice(1);

return { href, label };
});

return (
<ChakraBreadcrumb>
<BreadcrumbItem>
<BreadcrumbLink href="/admin">Dashboard</BreadcrumbLink>
</BreadcrumbItem>
{breadcrumbItems.map((item, index) => (
<BreadcrumbItem key={index} isCurrentPage={index === breadcrumbItems.length - 1}>
{index === breadcrumbItems.length - 1 ? (
<BreadcrumbText>{item.label}</BreadcrumbText>
) : (
<BreadcrumbLink href={item.href}>{item.label}</BreadcrumbLink>
)}
</BreadcrumbItem>
))}
</ChakraBreadcrumb>
);
};

Active Route Tracking

// hooks/useActiveRoute.ts
export const useActiveRoute = () => {
const pathname = usePathname();
const dispatch = useDispatch();

useEffect(() => {
dispatch(setActiveRoute(pathname));
}, [pathname, dispatch]);

return pathname;
};
// components/sidebar/SidebarItem.tsx
export const SidebarItem = ({ route }: { route: IRoute }) => {
const pathname = usePathname();
const isActive = pathname === route.layout + route.path;

return (
<Link href={route.layout + route.path}>
<Box
bg={isActive ? 'brand.500' : 'transparent'}
color={isActive ? 'white' : 'gray.600'}
p={3}
borderRadius="md"
_hover={{ bg: isActive ? 'brand.600' : 'gray.100' }}
>
{route.icon && <Icon as={route.icon} mr={3} />}
{route.name}
</Box>
</Link>
);
};

Route Configuration

Route Types

// types/navigation.ts
export interface IRoute {
name: string;
layout: string;
path: string;
icon?: React.ComponentType;
component?: React.ComponentType;
permissions?: string[];
children?: IRoute[];
}

Route Definitions

// config/routes.ts
export const adminRoutes: IRoute[] = [
{
name: 'Dashboard',
layout: '/admin',
path: '',
icon: HomeIcon,
permissions: ['owner', 'tenant', 'admin']
},
{
name: 'Propiedades',
layout: '/admin',
path: '/properties',
icon: BuildingIcon,
permissions: ['owner', 'admin']
},
{
name: 'Usuarios',
layout: '/admin',
path: '/users',
icon: UsersIcon,
permissions: ['admin']
}
];