inogen/ROUTER_SHADCN_IMPLEMENTATION.md

9.7 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("404", "routes/404.tsx"),             // /404
  route("unauthorized", "routes/unauthorized.tsx"), // /unauthorized
  route("*", "routes/$.tsx"),                 // Catch-all for 404s
] satisfies RouteConfig;

Navigation Implementation

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);
};
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">
</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="داشبورد" />
</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:

  1. Add more shadcn/ui components (Dialog, DropdownMenu, Sheet for mobile nav)
  2. Implement breadcrumb navigation using React Router location
  3. Add loading states for route transitions
  4. Create reusable navigation components for different sections
  5. 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>
  );
}
// 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.