10 KiB
10 KiB
shadcn/ui and React Router Implementation Guide
This document outlines how shadcn/ui components and React Router are implemented in the Inogen project.
📋 Overview
The project has been successfully updated to use:
- shadcn/ui components for consistent, accessible UI elements
- React Router v7 for client-side routing and navigation
🎨 shadcn/ui Implementation
Available Components
The project includes the following shadcn/ui components:
inogen/app/components/ui/
├── button.tsx # Button component with variants
├── card.tsx # Card, CardHeader, CardContent, etc.
├── input.tsx # Form input component
├── label.tsx # Form label component
└── design-system.tsx
Configuration
shadcn/ui is configured via components.json:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/app.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "~/components",
"utils": "~/lib/utils",
"ui": "~/components/ui"
}
}
Login Form Implementation
The login form has been refactored to use shadcn/ui components:
Before (Plain HTML):
<input
className="w-full px-4 py-3 border border-gray-300..."
placeholder="نام کاربری"
/>
<button className="w-full bg-green-500...">
ورود
</button>
After (shadcn/ui):
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card";
<Card className="shadow-xl">
<CardHeader>
<CardTitle className="font-persian">ورود به سیستم</CardTitle>
<CardDescription className="font-persian">
لطفاً اطلاعات ورود خود را وارد کنید
</CardDescription>
</CardHeader>
<CardContent>
<Label htmlFor="username" className="font-persian">نام کاربری</Label>
<Input
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="font-persian text-right"
placeholder="نام کاربری خود را وارد کنید"
/>
<Button variant="green" size="lg" className="w-full font-persian">
ورود
</Button>
</CardContent>
</Card>
Button Variants
The Button component includes custom variants for the project:
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
green: "bg-green-500 text-white hover:bg-green-600 focus:ring-green-400", // Custom
blue: "bg-blue-500 text-white hover:bg-blue-600 focus:ring-blue-400", // Custom
}
🚀 React Router Implementation
Version & Configuration
- React Router v7.7.0 is used throughout the project
- SSR is enabled by default in
react-router.config.ts
Route Structure
// app/routes.ts
export default [
index("routes/home.tsx"), // /
route("login", "routes/login.tsx"), // /login
route("dashboard", "routes/dashboard.tsx"), // /dashboard
route("dashboard/projects", "routes/dashboard.projects.tsx"), // /dashboard/projects
route("404", "routes/404.tsx"), // /404
route("unauthorized", "routes/unauthorized.tsx"), // /unauthorized
route("*", "routes/$.tsx"), // Catch-all for 404s
] satisfies RouteConfig;
Navigation Implementation
1. Basic Navigation with Link
import { Link } from "react-router";
<Link
to="/forgot-password"
className="text-green-600 hover:text-green-500 font-persian"
>
رمز عبور خود را فراموش کردهاید؟
</Link>
2. Programmatic Navigation
import { useNavigate } from "react-router";
const navigate = useNavigate();
// Navigate to dashboard after login
const handleLoginSuccess = () => {
navigate("/dashboard", { replace: true });
};
// Navigate back
const handleGoBack = () => {
navigate(-1);
};
3. Active Link Styling
import { Link, useLocation } from "react-router";
function NavigationLink({ to, label }: NavigationLinkProps) {
const location = useLocation();
const isActive = location.pathname === to;
return (
<Link
to={to}
className={`px-3 py-2 rounded-md font-persian ${
isActive
? "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300"
: "text-gray-600 hover:text-gray-900 dark:text-gray-300"
}`}
>
{label}
</Link>
);
}
Route Protection
The project implements comprehensive route protection:
1. Protected Route Component
// app/components/auth/protected-route.tsx
export function ProtectedRoute({ children, requireAuth = true }: ProtectedRouteProps) {
const { isAuthenticated, isLoading } = useAuth();
const location = useLocation();
if (isLoading) {
return <LoadingSpinner />;
}
if (requireAuth && !isAuthenticated) {
const returnTo = location.pathname + location.search;
const loginPath = `/login?returnTo=${encodeURIComponent(returnTo)}`;
return <Navigate to={loginPath} replace />;
}
return <>{children}</>;
}
2. Global Route Guard
// app/components/auth/global-route-guard.tsx
export function GlobalRouteGuard({ children }: GlobalRouteGuardProps) {
const { isAuthenticated, isLoading, token } = useAuth();
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
// Handle authentication-based redirects
if (!isLoading) {
handleRouteProtection();
}
}, [isAuthenticated, isLoading, location.pathname]);
return <>{children}</>;
}
Advanced Navigation Features
1. Return URL Handling
// Login page automatically redirects to intended destination
const [searchParams] = useSearchParams();
const returnTo = searchParams.get("returnTo");
useEffect(() => {
if (isAuthenticated && !isLoading) {
const redirectPath = returnTo && returnTo !== "/login" ? returnTo : "/dashboard";
navigate(redirectPath, { replace: true });
}
}, [isAuthenticated, isLoading, navigate, returnTo]);
2. Dashboard Navigation Menu
// app/components/dashboard/dashboard-layout.tsx
<nav className="hidden md:flex items-center space-x-8 space-x-reverse">
<NavigationLink to="/dashboard" label="داشبورد" />
<NavigationLink to="/dashboard/projects" label="پروژهها" />
</nav>
🔒 Authentication Integration
Auth Context with Router
// app/contexts/auth-context.tsx
export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
// Auto-validate tokens and handle expired sessions
useEffect(() => {
const interval = setInterval(async () => {
const isValid = await validateToken();
if (!isValid) {
clearAuthData();
toast.error("جلسه کاری شما منقضی شده است");
// Router will handle redirect via GlobalRouteGuard
}
}, 5 * 60 * 1000); // Every 5 minutes
return () => clearInterval(interval);
}, [token, user]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
📱 Responsive Design
Both shadcn/ui components and React Router navigation are fully responsive:
{/* Mobile-friendly navigation */}
<nav className="hidden md:flex items-center space-x-8 space-x-reverse">
<NavigationLink to="/dashboard" label="داشبورد" />
<NavigationLink to="/dashboard/projects" label="پروژهها" />
</nav>
{/* Mobile logo for small screens */}
<div className="lg:hidden text-center mb-8">
<div className="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-4">
{/* Mobile logo */}
</div>
</div>
🎯 Benefits Achieved
shadcn/ui Benefits:
- ✅ Consistent design system
- ✅ Accessible components by default
- ✅ Customizable with Tailwind CSS
- ✅ TypeScript support
- ✅ Reduced boilerplate code
React Router Benefits:
- ✅ Client-side routing (SPA experience)
- ✅ Programmatic navigation
- ✅ Route protection and guards
- ✅ URL state management
- ✅ Return URL handling
- ✅ Active link styling
- ✅ SEO-friendly with SSR support
🛠️ Next Steps
To further enhance the implementation:
- Add more shadcn/ui components (Dialog, DropdownMenu, Sheet for mobile nav)
- Implement breadcrumb navigation using React Router location
- Add loading states for route transitions
- Create reusable navigation components for different sections
- Add route-based animations using Framer Motion
📝 Usage Examples
Creating a New Protected Page
// app/routes/new-page.tsx
import { ProtectedRoute } from "~/components/auth/protected-route";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
export default function NewPage() {
return (
<ProtectedRoute>
<Card>
<CardHeader>
<CardTitle className="font-persian">صفحه جدید</CardTitle>
</CardHeader>
<CardContent>
<p className="font-persian">محتوای صفحه جدید</p>
</CardContent>
</Card>
</ProtectedRoute>
);
}
Adding Navigation Link
// Add to routes.ts
route("new-page", "routes/new-page.tsx"),
// Add to dashboard navigation
<NavigationLink to="/new-page" label="صفحه جدید" />
This implementation provides a solid foundation for scalable, maintainable React Router navigation with consistent shadcn/ui components throughout the application.