# Skill: React Admin Dashboard

## Identity
- **Skill ID**: `admin-dashboard-react`
- **Domain**: Admin Panel Frontend, React UI
- **Technologies**: React 18, TypeScript, Vite, TanStack Query, Zustand
- **Source Agent**: `backend-app-expert.md`

## When to Load This Skill

Load this skill when working on:
- React component development for admin panel
- Admin dashboard UI/UX
- State management with Zustand
- Data fetching with TanStack Query
- Admin authentication flow
- Charts and analytics visualization
- User management interface
- System settings UI

**File patterns:**
- `admin-panel/src/**/*.tsx`
- `admin-panel/src/**/*.ts`
- `admin-panel/src/components/**/*`
- `admin-panel/src/pages/**/*`
- `admin-panel/src/hooks/**/*`

## Core Patterns

### 1. Project Structure

```
admin-panel/
├── src/
│   ├── components/       # Reusable UI components
│   ├── pages/            # Page components
│   ├── hooks/            # Custom React hooks
│   ├── stores/           # Zustand state stores
│   ├── services/         # API service layer
│   ├── types/            # TypeScript types
│   ├── utils/            # Utility functions
│   ├── App.tsx           # Root component
│   └── main.tsx          # Entry point
├── public/
├── index.html
├── vite.config.ts
├── tsconfig.json
└── package.json
```

### 2. Authentication Store (Zustand)

```typescript
// stores/authStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface User {
  id: string;
  email: string;
  role: 'admin' | 'support';
}

interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  refreshToken: () => Promise<void>;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      isAuthenticated: false,
      
      login: async (email: string, password: string) => {
        const response = await fetch('/api/auth/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ email, password }),
          credentials: 'include' // Important for cookies
        });
        
        if (!response.ok) {
          throw new Error('Login failed');
        }
        
        const data = await response.json();
        set({ user: data.user, isAuthenticated: true });
      },
      
      logout: async () => {
        await fetch('/api/auth/logout', {
          method: 'POST',
          credentials: 'include'
        });
        set({ user: null, isAuthenticated: false });
      },
      
      refreshToken: async () => {
        const response = await fetch('/api/auth/refresh', {
          method: 'POST',
          credentials: 'include'
        });
        
        if (!response.ok) {
          set({ user: null, isAuthenticated: false });
          throw new Error('Token refresh failed');
        }
      }
    }),
    {
      name: 'auth-storage',
      partialize: (state) => ({ user: state.user, isAuthenticated: state.isAuthenticated })
    }
  )
);
```

### 3. API Service Layer

```typescript
// services/api.ts
class ApiError extends Error {
  constructor(public status: number, message: string) {
    super(message);
    this.name = 'ApiError';
  }
}

async function fetchApi<T>(url: string, options?: RequestInit): Promise<T> {
  const response = await fetch(`/api${url}`, {
    ...options,
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      ...options?.headers
    }
  });
  
  if (response.status === 401) {
    // Token expired, try to refresh
    const refreshed = await useAuthStore.getState().refreshToken();
    if (refreshed) {
      // Retry original request
      return fetchApi<T>(url, options);
    } else {
      throw new ApiError(401, 'Unauthorized');
    }
  }
  
  if (!response.ok) {
    const error = await response.json().catch(() => ({ error: 'Unknown error' }));
    throw new ApiError(response.status, error.error || 'Request failed');
  }
  
  return response.json();
}

// Analytics service
export const analyticsService = {
  getDashboardMetrics: () =>
    fetchApi<DashboardMetrics>('/admin/analytics'),
    
  getUserStats: (params: { startDate: string; endDate: string }) =>
    fetchApi<UserStats>(`/admin/analytics/users?${new URLSearchParams(params)}`)
};

// Users service
export const usersService = {
  getUsers: (page: number, limit: number) =>
    fetchApi<PaginatedUsers>(`/admin/users?page=${page}&limit=${limit}`),
    
  getUser: (id: string) =>
    fetchApi<User>(`/admin/users/${id}`),
    
  updateUser: (id: string, data: Partial<User>) =>
    fetchApi<User>(`/admin/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data)
    }),
    
  allocateCredits: (userId: string, amount: number, description: string) =>
    fetchApi(`/admin/credits`, {
      method: 'POST',
      body: JSON.stringify({ user_id: userId, amount, description })
    })
};
```

### 4. Data Fetching with TanStack Query

```typescript
// hooks/useUsers.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { usersService } from '../services/api';

export function useUsers(page: number, limit: number = 20) {
  return useQuery({
    queryKey: ['users', page, limit],
    queryFn: () => usersService.getUsers(page, limit),
    staleTime: 30000, // 30 seconds
    gcTime: 300000 // 5 minutes (formerly cacheTime)
  });
}

export function useUser(id: string) {
  return useQuery({
    queryKey: ['user', id],
    queryFn: () => usersService.getUser(id),
    enabled: !!id // Only run if ID exists
  });
}

export function useUpdateUser() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: ({ id, data }: { id: string; data: Partial<User> }) =>
      usersService.updateUser(id, data),
      
    onSuccess: (updatedUser) => {
      // Update user list cache
      queryClient.invalidateQueries({ queryKey: ['users'] });
      
      // Update specific user cache
      queryClient.setQueryData(['user', updatedUser.id], updatedUser);
    }
  });
}
```

### 5. Dashboard Page Component

```typescript
// pages/Dashboard.tsx
import { useQuery } from '@tanstack/react-query';
import { analyticsService } from '../services/api';
import { MetricCard } from '../components/MetricCard';
import { RevenueChart } from '../components/RevenueChart';

export function Dashboard() {
  const { data: metrics, isLoading, error } = useQuery({
    queryKey: ['dashboard-metrics'],
    queryFn: analyticsService.getDashboardMetrics,
    refetchInterval: 60000 // Refresh every minute
  });
  
  if (isLoading) {
    return <LoadingSpinner />;
  }
  
  if (error) {
    return <ErrorMessage error={error} />;
  }
  
  return (
    <div className="dashboard">
      <h1>Dashboard</h1>
      
      <div className="metrics-grid">
        <MetricCard
          title="Total Users"
          value={metrics.totalUsers}
          change={metrics.usersChange}
          icon={<UsersIcon />}
        />
        <MetricCard
          title="Monthly Revenue"
          value={`$${metrics.revenue.toFixed(2)}`}
          change={metrics.revenueChange}
          icon={<DollarIcon />}
        />
        <MetricCard
          title="Active Jobs"
          value={metrics.activeJobs}
          icon={<JobsIcon />}
        />
        <MetricCard
          title="Tokens Processed"
          value={metrics.tokensProcessed.toLocaleString()}
          icon={<TokensIcon />}
        />
      </div>
      
      <div className="charts">
        <RevenueChart data={metrics.revenueHistory} />
        <UsageChart data={metrics.usageHistory} />
      </div>
    </div>
  );
}
```

### 6. User Management Page

```typescript
// pages/Users.tsx
import { useState } from 'react';
import { useUsers } from '../hooks/useUsers';
import { UserTable } from '../components/UserTable';
import { Pagination } from '../components/Pagination';

export function Users() {
  const [page, setPage] = useState(1);
  const { data, isLoading, error } = useUsers(page, 20);
  
  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  
  return (
    <div className="users-page">
      <div className="page-header">
        <h1>Users</h1>
        <SearchInput onSearch={(query) => {/* Filter users */}} />
      </div>
      
      <UserTable users={data.users} />
      
      <Pagination
        currentPage={page}
        totalPages={data.pagination.pages}
        onPageChange={setPage}
      />
    </div>
  );
}

// components/UserTable.tsx
interface UserTableProps {
  users: User[];
}

export function UserTable({ users }: UserTableProps) {
  const updateUser = useUpdateUser();
  
  const handleSuspend = async (userId: string) => {
    if (!confirm('Suspend this user?')) return;
    
    await updateUser.mutateAsync({
      id: userId,
      data: { status: 'suspended' }
    });
  };
  
  return (
    <table className="user-table">
      <thead>
        <tr>
          <th>Email</th>
          <th>Plan</th>
          <th>Credits</th>
          <th>Status</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        {users.map(user => (
          <tr key={user.id}>
            <td>{user.email}</td>
            <td>{user.subscription?.plan_tier || 'None'}</td>
            <td>{user.credits?.toLocaleString()}</td>
            <td>
              <StatusBadge status={user.status} />
            </td>
            <td>
              <button onClick={() => handleSuspend(user.id)}>
                Suspend
              </button>
              <button onClick={() => {/* Navigate to user detail */}}>
                View
              </button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
```

### 7. Protected Routes

```typescript
// components/ProtectedRoute.tsx
import { Navigate } from 'react-router-dom';
import { useAuthStore } from '../stores/authStore';

interface ProtectedRouteProps {
  children: React.ReactNode;
  requiredRole?: 'admin' | 'support';
}

export function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) {
  const { isAuthenticated, user } = useAuthStore();
  
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  
  if (requiredRole && user?.role !== requiredRole) {
    return <Navigate to="/unauthorized" replace />;
  }
  
  return <>{children}</>;
}

// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        
        <Route
          path="/"
          element={
            <ProtectedRoute>
              <Layout />
            </ProtectedRoute>
          }
        >
          <Route index element={<Dashboard />} />
          <Route path="users" element={<Users />} />
          <Route path="users/:id" element={<UserDetail />} />
          <Route path="jobs" element={<Jobs />} />
          <Route
            path="settings"
            element={
              <ProtectedRoute requiredRole="admin">
                <Settings />
              </ProtectedRoute>
            }
          />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
```

## Anti-Patterns (Forbidden)

| ❌ Mistake | ✅ Fix |
|-----------|--------|
| Fetching data in `useEffect` | Use TanStack Query hooks |
| Storing server state in component state | Use TanStack Query for server data |
| Not handling loading/error states | Always show loaders and error messages |
| Prop drilling | Use Zustand for global state |
| Inline styles | Use CSS modules or styled-components |
| Not memoizing expensive computations | Use `useMemo` and `useCallback` |
| Mutating state directly | Use immutable updates |
| No TypeScript types | Define interfaces for all data |
| Hardcoding API URLs | Use environment variables |
| Not using React Router for navigation | Use `<Link>` and `useNavigate()` |

## Integration with Other Skills

**Often combined with:**
- `api-endpoint-creation` - Consuming backend APIs
- `authentication-security` - JWT token handling
- `error-handling-logging` - Error reporting to backend

## Environment Variables Required

```bash
# .env
VITE_API_BASE_URL=http://localhost:3000
VITE_APP_NAME="TranslatePressZone Admin"
```

## Quick Reference

### TanStack Query Configuration

```typescript
// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60000, // 1 minute
      gcTime: 300000, // 5 minutes
      retry: 1,
      refetchOnWindowFocus: false
    }
  }
});

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>
);
```

### Common Hooks

| Hook | Purpose |
|------|---------|
| `useQuery` | Fetch and cache data |
| `useMutation` | Create/update/delete data |
| `useQueryClient` | Access query cache |
| `useInfiniteQuery` | Infinite scroll/pagination |

## Validation Checklist

- [ ] All API calls use service layer
- [ ] TanStack Query for all server data
- [ ] Loading states handled
- [ ] Error states handled with user-friendly messages
- [ ] Protected routes implemented
- [ ] JWT token refresh on 401
- [ ] Zustand for global UI state (not server data)
- [ ] TypeScript types for all data
- [ ] Environment variables for API URLs
- [ ] React Router for navigation
- [ ] Responsive design (mobile-friendly)
- [ ] Accessibility (ARIA labels, keyboard nav)

## Build Configuration

```typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true
      }
    }
  }
});
```
