react-frontend

React components for Chat, Evaluation, Report, Admin with TypeScript, Tailwind, hooks

16 stars

Best use case

react-frontend is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

React components for Chat, Evaluation, Report, Admin with TypeScript, Tailwind, hooks

Teams using react-frontend should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/react-frontend/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/react-frontend/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/react-frontend/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How react-frontend Compares

Feature / Agentreact-frontendStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

React components for Chat, Evaluation, Report, Admin with TypeScript, Tailwind, hooks

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# React Frontend — CEI-001

## Project Structure

```
src/
├── components/
│   ├── chat/
│   │   ├── ChatWindow.tsx
│   │   ├── ChatMessage.tsx
│   │   ├── ChatInput.tsx
│   │   └── useChat.ts (custom hook)
│   ├── evaluation/
│   │   ├── EvaluationWizard.tsx
│   │   ├── QuestionCard.tsx
│   │   ├── ProgressBar.tsx
│   │   └── useEvaluation.ts
│   ├── report/
│   │   ├── ReportDashboard.tsx
│   │   ├── ScoreCard.tsx
│   │   ├── RadarChart.tsx
│   │   └── useReport.ts
│   ├── admin/
│   │   ├── DocumentList.tsx
│   │   ├── DocumentUpload.tsx
│   │   ├── PipelineStatus.tsx
│   │   └── useDocuments.ts
│   └── shared/
│       ├── Navbar.tsx
│       ├── Footer.tsx
│       ├── Button.tsx
│       └── Card.tsx
├── pages/
│   ├── HomePage.tsx
│   ├── ChatPage.tsx
│   ├── EvaluationPage.tsx
│   ├── ReportPage.tsx
│   ├── AdminPage.tsx
│   └── LoginPage.tsx
├── services/
│   ├── api.ts (axios client with auth)
│   ├── chatService.ts
│   ├── evaluationService.ts
│   ├── reportService.ts
│   └── adminService.ts
├── hooks/
│   ├── useAuth.ts
│   ├── useAPI.ts
│   └── useLocalStorage.ts
├── store/
│   ├── authStore.ts (Zustand)
│   ├── evaluationStore.ts
│   └── chatStore.ts
├── types/
│   ├── api.ts
│   ├── evaluation.ts
│   ├── chat.ts
│   └── admin.ts
├── styles/
│   └── tailwind.css
└── App.tsx
```

## Component Pattern with TypeScript

### Basic Component
```typescript
import React, { FC } from 'react';

interface EvaluationHeaderProps {
  title: string;
  progress: number;
  onBack: () => void;
}

export const EvaluationHeader: FC<EvaluationHeaderProps> = ({
  title,
  progress,
  onBack
}) => {
  return (
    <div className="bg-white border-b">
      <div className="container mx-auto px-4 py-4 flex items-center justify-between">
        <button
          onClick={onBack}
          className="text-gray-600 hover:text-gray-900"
        >
          ← Retour
        </button>
        <div className="flex-1 mx-4">
          <h1 className="text-2xl font-bold text-gray-900">{title}</h1>
          <div className="w-full bg-gray-200 rounded-full h-2 mt-2">
            <div
              className="bg-blue-600 h-2 rounded-full transition-all"
              style={{ width: `${progress}%` }}
            />
          </div>
        </div>
      </div>
    </div>
  );
};
```

### Chat Component with Streaming
```typescript
import React, { useState, useRef, useEffect } from 'react';
import { chatService } from '@/services/chatService';

interface ChatMessage {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  sources?: Array<{ title: string; excerpt: string }>;
  timestamp: Date;
}

export const ChatWindow: FC = () => {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [inputValue, setInputValue] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const messagesEndRef = useRef<HTMLDivElement>(null);

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  };

  useEffect(() => {
    scrollToBottom();
  }, [messages]);

  const handleSendMessage = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!inputValue.trim()) return;

    // Add user message
    const userMessage: ChatMessage = {
      id: Date.now().toString(),
      role: 'user',
      content: inputValue,
      timestamp: new Date()
    };
    setMessages(prev => [...prev, userMessage]);
    setInputValue('');
    setIsLoading(true);

    try {
      // Stream assistant response
      let assistantContent = '';
      let sources: ChatMessage['sources'] = [];

      const stream = await chatService.sendMessage({
        conversation_id: 'current', // From context
        content: inputValue
      });

      for await (const chunk of stream) {
        if (chunk.type === 'content') {
          assistantContent += chunk.data;
          
          // Update last message in real-time
          setMessages(prev => {
            const newMessages = [...prev];
            if (newMessages[newMessages.length - 1]?.role === 'assistant') {
              newMessages[newMessages.length - 1].content = assistantContent;
            }
            return newMessages;
          });
        } else if (chunk.type === 'sources') {
          sources = chunk.data;
        }
      }

      // Add assistant message
      const assistantMessage: ChatMessage = {
        id: Date.now().toString(),
        role: 'assistant',
        content: assistantContent,
        sources,
        timestamp: new Date()
      };
      setMessages(prev => [...prev, assistantMessage]);
    } catch (error) {
      console.error('Error sending message:', error);
      // Add error message
      setMessages(prev => [...prev, {
        id: Date.now().toString(),
        role: 'assistant',
        content: 'Erreur: Impossible de générer une réponse.',
        timestamp: new Date()
      }]);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="flex flex-col h-screen bg-gray-50">
      {/* Messages */}
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map(msg => (
          <ChatMessage key={msg.id} message={msg} />
        ))}
        {isLoading && <TypingIndicator />}
        <div ref={messagesEndRef} />
      </div>

      {/* Input */}
      <div className="border-t bg-white p-4">
        <form onSubmit={handleSendMessage} className="flex gap-2">
          <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            placeholder="Posez votre question..."
            disabled={isLoading}
            className="flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
          />
          <button
            type="submit"
            disabled={isLoading || !inputValue.trim()}
            className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
          >
            Envoyer
          </button>
        </form>
      </div>
    </div>
  );
};
```

### Form with Validation (Evaluation)
```typescript
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const questionSchema = z.object({
  answer: z.enum(['oui', 'non', 'partiellement']),
  comment: z.string().optional()
});

type QuestionFormData = z.infer<typeof questionSchema>;

interface QuestionCardProps {
  question: {
    id: string;
    text: string;
    category: string;
    type: 'yesno' | 'scale' | 'multiple';
  };
  onSubmit: (answer: QuestionFormData) => void;
}

export const QuestionCard: FC<QuestionCardProps> = ({ question, onSubmit }) => {
  const { control, handleSubmit, formState: { errors } } = useForm<QuestionFormData>({
    resolver: zodResolver(questionSchema)
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="bg-white rounded-lg shadow p-6">
      <h3 className="text-lg font-semibold mb-4">{question.text}</h3>

      <div className="space-y-3 mb-6">
        {['oui', 'non', 'partiellement'].map(option => (
          <label key={option} className="flex items-center">
            <Controller
              name="answer"
              control={control}
              render={({ field }) => (
                <input
                  {...field}
                  type="radio"
                  value={option}
                  className="w-4 h-4 text-blue-600"
                />
              )}
            />
            <span className="ml-3 capitalize">{option}</span>
          </label>
        ))}
      </div>

      {errors.answer && (
        <p className="text-red-600 text-sm mb-4">{errors.answer.message}</p>
      )}

      <Controller
        name="comment"
        control={control}
        render={({ field }) => (
          <textarea
            {...field}
            placeholder="Commentaires optionnels..."
            rows={3}
            className="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
          />
        )}
      />

      <button
        type="submit"
        className="mt-4 w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
      >
        Suivant
      </button>
    </form>
  );
};
```

## Custom Hooks

```typescript
// hooks/useChat.ts
import { useState, useCallback } from 'react';
import { useQuery, useMutation } from '@tanstack/react-query';
import { chatService } from '@/services/chatService';

export const useChat = (conversationId: string) => {
  const [messages, setMessages] = useState([]);

  const { data: history, isLoading } = useQuery({
    queryKey: ['chat', conversationId],
    queryFn: () => chatService.getHistory(conversationId)
  });

  const sendMutation = useMutation({
    mutationFn: (content: string) =>
      chatService.sendMessage({ conversation_id: conversationId, content }),
    onSuccess: (response) => {
      setMessages(prev => [...prev, response]);
    }
  });

  return {
    messages: history || [],
    isLoading,
    sendMessage: sendMutation.mutate,
    isLoading: sendMutation.isPending
  };
};
```

## Auth & API Client

```typescript
// services/api.ts
import axios from 'axios';
import { useAuthStore } from '@/store/authStore';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000'
});

api.interceptors.request.use((config) => {
  const token = useAuthStore.getState().token;
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

api.interceptors.response.use(
  response => response,
  error => {
    if (error.response?.status === 401) {
      useAuthStore.getState().logout();
    }
    return Promise.reject(error);
  }
);

export default api;
```

## State Management (Zustand)

```typescript
// store/authStore.ts
import { create } from 'zustand';

interface AuthStore {
  user: any | null;
  token: string | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

export const useAuthStore = create<AuthStore>((set) => ({
  user: null,
  token: localStorage.getItem('token'),
  login: async (email, password) => {
    const response = await api.post('/auth/login', { email, password });
    const { access_token, user } = response.data;
    localStorage.setItem('token', access_token);
    set({ token: access_token, user });
  },
  logout: () => {
    localStorage.removeItem('token');
    set({ token: null, user: null });
  }
}));
```

## Types

```typescript
// types/api.ts
export interface ChatMessage {
  id: string;
  role: 'user' | 'assistant';
  content: string;
  sources?: Array<{ title: string; excerpt: string }>;
  timestamp: string;
}

export interface Evaluation {
  id: string;
  userId: string;
  status: 'in_progress' | 'completed';
  answers: Record<string, string>;
  score: number;
  createdAt: string;
  completedAt?: string;
}

export interface EvaluationQuestion {
  id: string;
  moduleId: string;
  text: string;
  type: 'yesno' | 'scale' | 'multiple';
  weight: number;
}
```

## Tailwind Classes

- Spacing: `px-4`, `py-2`, `mx-4`, `gap-2`
- Colors: `bg-blue-600`, `text-gray-900`, `border-gray-200`
- Layout: `flex`, `grid`, `container mx-auto`
- Responsive: `md:`, `lg:`
- States: `hover:`, `focus:`, `disabled:`, `dark:`

## Conventions

- Files: PascalCase for components, camelCase for hooks/services
- Props interfaces end with `Props`
- Type everything (no `any`)
- Hooks use `use` prefix
- Components use FC type or function syntax
- Import order: react, third-party, local
- Error boundaries for error handling
- Accessible: alt text, labels, semantic HTML
- No hardcoded strings (use constants/i18n)

Related Skills

react

16
from diegosouzapw/awesome-omni-skill

Full React 19 engineering, architecture, Server Components, hooks, Zustand, TanStack Query, forms, performance, testing, production deploy.

react-ui-patterns

16
from diegosouzapw/awesome-omni-skill

Modern React UI patterns for loading states, error handling, and data fetching. Use when building UI components, handling async data, or managing UI states.

react-to-wx-miniprogram-migrator

16
from diegosouzapw/awesome-omni-skill

Migrates a React + TailwindCSS H5 web application to a native WeChat Mini Program. Use when the user wants to convert their existing web project into a mini program, preserving structure, styling, and functionality.

react-synapse

16
from diegosouzapw/awesome-omni-skill

A React state management library using Preact Signals with fine-grained reactivity. Use it when you need global state without providers, minimal re-renders, or immutable updates via draft mutations. Works with React 18+.

react-patterns

16
from diegosouzapw/awesome-omni-skill

Modern React patterns and principles. Hooks, composition, performance, TypeScript best practices.

react-observability

16
from diegosouzapw/awesome-omni-skill

Logging, error messages, and debugging patterns for React. Use when adding logging, designing error messages, debugging production issues, or improving code observability. Works for both React web and React Native.

react-nextjs-development

16
from diegosouzapw/awesome-omni-skill

React and Next.js 14+ application development with App Router, Server Components, TypeScript, Tailwind CSS, and modern frontend patterns.

react-native-architecture

16
from diegosouzapw/awesome-omni-skill

Build production React Native apps with Expo, navigation, native modules, offline sync, and cross-platform patterns. Use when developing mobile apps, implementing native integrations, or architecti...

react-modernization

16
from diegosouzapw/awesome-omni-skill

Upgrade React applications to latest versions, migrate from class components to hooks, and adopt concurrent features. Use when modernizing React codebases, migrating to React Hooks, or upgrading to...

react-guidelines

16
from diegosouzapw/awesome-omni-skill

React coding guidelines and best practices. MUST follow these rules. Use when reviewing or writing React code or tasks.

react-gradual-architecture

16
from diegosouzapw/awesome-omni-skill

Incremental React code organization guidelines. Start small, then extract when scanning and responsibilities start to blur. Use when creating features, organizing files, refactoring components, or deciding when to extract hooks, UI, or utils.

react-grab

16
from diegosouzapw/awesome-omni-skill

Installs and configures React Grab for visual UI element selection in React/Electron apps. Use when user wants to edit UI visually, select components by hovering, or capture element context.