| --- |
| alwaysApply: true |
| --- |
|
|
| |
|
|
| This is a React + TypeScript project using Create React App, Tailwind CSS, and a modular feature-first architecture. |
|
|
| |
|
|
| - **`.tsx`** - Use for React components (components, pages, layouts) |
| - **`.ts`** - Use for non-JSX files (hooks, utils, types, services) |
| - **`.js`** - Only for config files (index.js, config files) |
| - **Always type everything**: props, functions, API responses, hook returns |
|
|
| |
|
|
| ``` |
| src/app/ |
| βββ App.tsx |
| βββ layouts/ |
| βββ pages/ |
| βββ components/ |
| β βββ common/ |
| βββ hooks/ |
| βββ services/ |
| βββ types/ |
| βββ utils/ |
| βββ constants/ |
| ``` |
|
|
| |
|
|
| |
| - Use functional components with plain arrow functions (avoid `React.FC` due to local React type limitations) |
| - Always type props with interfaces or types |
| - Use Tailwind CSS for styling (avoid custom CSS files) |
| - Keep components presentational - pass data via props |
|
|
| ```typescript |
| type MyComponentProps = { |
| title: string; |
| count?: number; |
| }; |
|
|
| const MyComponent = ({ title, count = 0 }: MyComponentProps) => { |
| return <div>{title}: {count}</div>; |
| }; |
| ``` |
|
|
| |
| - Use `useAuth` hook for reactive authentication state instead of static `isUserRegistered()` calls |
| - Always use `setUserIdAndNotify()` when logging in/registering to ensure immediate UI updates |
| - Never call `isUserRegistered()` directly in components - use the `useAuth` hook instead |
| - Authentication state should be reactive across all components |
| - Use custom hooks for any state that needs to be shared across multiple components |
|
|
| ```typescript |
| // src/app/hooks/useAuth.ts - Reactive authentication hook |
| import { useState, useEffect } from 'react'; |
| import { isUserRegistered } from '../utils/index.ts'; |
|
|
| export const useAuth = () => { |
| const [isAuthenticated, setIsAuthenticated] = useState(isUserRegistered()); |
|
|
| useEffect(() => { |
| const checkAuth = () => setIsAuthenticated(isUserRegistered()); |
|
|
| // Listen for auth changes |
| window.addEventListener('storage', (e) => { |
| if (e.key === 'user_id') checkAuth(); |
| }); |
| window.addEventListener('authChange', checkAuth); |
|
|
| return () => { |
| window.removeEventListener('storage', checkAuth); |
| window.removeEventListener('authChange', checkAuth); |
| }; |
| }, []); |
|
|
| return { isAuthenticated }; |
| }; |
|
|
| // Usage in components |
| const MyComponent = () => { |
| const { isAuthenticated } = useAuth(); |
| // Component will re-render when authentication state changes |
| }; |
|
|
| // When setting user authentication, use the notifying version |
| import { setUserIdAndNotify } from '../utils/index.ts'; |
| setUserIdAndNotify(user.id); // Triggers immediate UI updates |
|
|
| // When logging out, use the notifying version |
| import { logoutAndNotify } from '../utils/index.ts'; |
| logoutAndNotify(); // Clears user data and triggers immediate UI updates |
| ``` |
|
|
| |
| - Use `ProtectedRoute` component in `components/common/` for authentication-based routing |
| - Always type `children` as optional (`children?: any`) for route wrapper components |
| - Wrap protected content in React Fragment (`<>{children}</>`) when returning children |
|
|
| ```typescript |
| // src/app/components/common/ProtectedRoute.tsx |
| import React from 'react'; |
| import { Navigate } from 'react-router-dom'; |
| import { isUserRegistered } from '../../utils/index.ts'; |
|
|
| type ProtectedRouteProps = { |
| children?: any; // Optional children prop for JSX content |
| }; |
|
|
| const ProtectedRoute = ({ children }: ProtectedRouteProps) => { |
| const isAuthenticated = isUserRegistered(); |
|
|
| if (!isAuthenticated) { |
| return <Navigate to="/" replace />; |
| } |
|
|
| return <>{children}</>; // Wrap in fragment for proper React element return |
| }; |
|
|
| export default ProtectedRoute; |
| ``` |
|
|
| ```typescript |
| // Usage in App.tsx routing |
| <Route |
| path="/protected" |
| element={ |
| <ProtectedRoute> |
| <ProtectedPage /> |
| </ProtectedRoute> |
| } |
| /> |
| ``` |
|
|
| |
|
|
| - **React component types** |
| - β Avoid `React.FC`, `ReactNode`, and React-specific event types (`FormEvent`, `ChangeEvent`, etc.) β they are not available in this project's React type setup and will cause errors like "Namespace 'react' has no exported member 'FC'". |
| - β
Prefer plain arrow components with explicitly typed props: |
| - `type Props = { ... }` |
| - `const Component = (props: Props) => { ... }` |
| - For `children` props: |
| - **Route wrapper components** (like `ProtectedRoute`): Use `children?: any` (optional) |
| - **Regular components**: Use `children: any` (required) or omit if not needed |
|
|
| - **Hooks and generics** |
| - β Do **not** pass generic type parameters to React hooks such as `useState` (e.g., `useState<string>()` or `useState<MyType>()`). This triggers βExpected 0 type arguments, but got 1.β |
| - β
Let hooks infer types from initial values, and when generics are needed: |
| - Use a type assertion on the initial state: |
| - `const [state, setState] = useState({ ... } as MyStateType);` |
| - Or, for tuple-like state: |
| - `const [value, setValue] = useState(() => initialValue) as [MyType, (next: MyType | ((prev: MyType) => MyType)) => void];` |
|
|
| - **Error boundary** |
| - The only class component should be `ErrorBoundary`, and it should: |
| - Import only `Component` from React (no `ErrorInfo`, `ReactNode`). |
| - Extend `Component` without generic parameters: `class ErrorBoundary extends Component { ... }`. |
| - Declare its `state` field explicitly with a typed object (e.g., `state = { hasError: false, error: null }`). |
|
|
| |
| - Use `api-wrapper.ts` for direct API calls |
| - Use `useApi` hook for components needing loading/error states |
| - Always type API responses |
|
|
| ```typescript |
| // Direct API call (note: include .ts extension) |
| import api from '../services/api-wrapper.ts'; |
| const data = await api.get<User[]>('/api/users'); |
|
|
| // With hook (note: include .ts extension) |
| import { useApi } from '../hooks/useApi.ts'; |
| const { data, loading, error } = useApi<User[]>('/api/users'); |
| ``` |
|
|
| |
| - Add types to `src/app/types/` and export from `index.ts` |
| - Use descriptive type names |
| - Export types for reuse |
|
|
| ```typescript |
| // src/app/types/debater.types.ts |
| export type Debate = { |
| id: number; |
| topic: string; |
| arguments: Argument[]; |
| }; |
|
|
| // When importing types, use explicit extension: |
| import type { Debate } from '../types/debater.types.ts'; |
| ``` |
|
|
| |
| - Add constants to `src/app/constants/index.ts` |
| - Use `API_ENDPOINTS` for API paths |
| - Use `APP_CONFIG` for app configuration |
| - Use `UI` for UI-related constants |
|
|
| |
| - Add utility functions to `src/app/utils/index.ts` |
| - Keep functions pure and typed |
| - Export for reuse |
|
|
| |
| - Create custom hooks in `src/app/hooks/` for reusable data-fetching logic |
| - Use `useCallback` to memoize async functions and prevent unnecessary re-renders |
| - Implement intelligent caching with localStorage (use `cache.utils.ts`) |
| - Handle loading, error, and empty states explicitly |
| - Use defensive type checking for API responses: |
|
|
| ```typescript |
| // Example: Custom hook with caching and error handling |
| import { useState, useEffect, useCallback } from 'react'; |
| import api from '../services/api-wrapper.ts'; |
| import type { MyType } from '../types/index.ts'; |
| import { cacheMyData, getCachedMyData } from '../utils/cache.utils.ts'; |
|
|
| type MyDataState = { |
| data: MyType[]; |
| loading: boolean; |
| error: Error | null; |
| }; |
|
|
| const isValidData = (value: unknown): value is MyType => { |
| if (!value || typeof value !== 'object') return false; |
| const candidate = value as { id?: unknown; name?: unknown }; |
| return typeof candidate.id === 'number' && typeof candidate.name === 'string'; |
| }; |
|
|
| const normalizeResponse = (payload: unknown): MyType[] | null => { |
| if (Array.isArray(payload)) return payload.filter(isValidData); |
| if (payload && typeof payload === 'object') { |
| const candidate = payload as { data?: unknown; items?: unknown }; |
| if (Array.isArray(candidate.data)) return candidate.data.filter(isValidData); |
| if (Array.isArray(candidate.items)) return candidate.items.filter(isValidData); |
| } |
| return null; |
| }; |
|
|
| export const useMyData = () => { |
| const [state, setState] = useState<MyDataState>({ data: [], loading: false, error: null }); |
|
|
| const fetchData = useCallback(async () => { |
| try { |
| const cached = getCachedMyData(); |
| if (cached?.length > 0) { |
| setState({ data: cached, loading: false, error: null }); |
| } else { |
| setState(prev => ({ ...prev, loading: true, error: null })); |
| } |
| |
| const payload = await api.get<unknown>('/api/endpoint'); |
| const normalized = normalizeResponse(payload); |
| |
| if (!normalized) throw new Error('Invalid response shape'); |
| |
| setState({ data: normalized, loading: false, error: null }); |
| cacheMyData(normalized); |
| } catch (err) { |
| setState({ |
| data: getCachedMyData() ?? [], |
| loading: false, |
| error: err instanceof Error ? err : new Error('Failed to fetch data'), |
| }); |
| } |
| }, []); |
|
|
| useEffect(() => { |
| fetchData(); |
| }, [fetchData]); |
|
|
| return { ...state, refetch: fetchData }; |
| }; |
| ``` |
|
|
| |
| - Use refs (`useRef`) for managing DOM elements that need direct access (e.g., dropdown containers) |
| - Implement click-outside detection for dropdowns/modals: |
| - Add `mousedown` listener (not `click`) to capture events before bubbling |
| - Always clean up event listeners in `useEffect` return |
| - Implement state visibility toggles that also trigger data refetch on open: |
| - Useful for keeping data fresh when user opens a dropdown/modal |
| - Add proper ARIA attributes for accessibility: |
| - `aria-expanded` on toggle buttons |
| - `aria-haspopup` on buttons that open menus |
| - Limit list item display (e.g., `slice(0, 6)`) to prevent UI overflow |
|
|
| ```typescript |
| // Dropdown component pattern |
| const dropdownRef = useRef<HTMLDivElement>(null); |
| const [showDropdown, setShowDropdown] = useState(false); |
|
|
| // Close on click outside |
| useEffect(() => { |
| const handleClickOutside = (event: MouseEvent) => { |
| if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { |
| setShowDropdown(false); |
| } |
| }; |
|
|
| document.addEventListener('mousedown', handleClickOutside); |
| return () => { |
| document.removeEventListener('mousedown', handleClickOutside); |
| }; |
| }, []); |
|
|
| // Toggle with refetch on open |
| const toggleDropdown = () => { |
| const nextState = !showDropdown; |
| setShowDropdown(nextState); |
| if (nextState) { |
| refetch(); // Refresh data when opening |
| } |
| }; |
| ``` |
|
|
| |
|
|
| 1. **Authentication**: |
| - Use `setUserIdAndNotify()` for login/registration (immediate UI updates) |
| - Use `logoutAndNotify()` for logout (immediate UI updates) |
| - Use `setUserId()` only for background operations or initialization |
| - Always use `useAuth()` hook in components instead of `isUserRegistered()` |
|
|
| 2. **Imports**: |
| - Use relative paths from `src/` |
| - **ALWAYS include explicit file extensions** in imports: |
| - Use `.tsx` for React components (e.g., `import Component from './Component.tsx'`) |
| - Use `.ts` for TypeScript files (e.g., `import { func } from './utils/index.ts'`) |
| - Use `/index.ts` for directory barrel exports (e.g., `import { something } from './utils/index.ts'`) |
| - Use `type` keyword for type imports (e.g., `import type { Type } from './types.ts'`) |
| - This is required for webpack module resolution in this project |
| - **Import organization order**: |
| 1. React and React hooks (e.g., `import React, { useState } from 'react'`) |
| 2. Third-party libraries (e.g., `import { Icon } from 'lucide-react'`) |
| 3. Local imports (hooks, types, components, services, utils) |
| 4. Type imports last (e.g., `import type { MyType } from './types.ts'`) |
| 2. **Styling**: |
| - Tailwind-first, avoid custom CSS files |
| - For utility classes needed globally (e.g., `.scrollbar-hide`), add to `src/index.css` with appropriate vendor prefixes |
| - Use Tailwind modifiers for responsive, dark mode, and interactive states |
| - Example utilities to `src/index.css`: |
| ```css |
| .scrollbar-hide { |
| scrollbar-width: none; |
| } |
| .scrollbar-hide::-webkit-scrollbar { |
| display: none; |
| } |
| ``` |
| 3. **Error Handling**: Use ErrorBoundary for React errors, try/catch for API errors |
| 4. **Loading States**: Use `Loading` component from `components/common/Loading` |
| 5. **Environment Variables**: All must start with `REACT_APP_` prefix |
| 6. **Testing**: Use React Testing Library, co-locate tests |
|
|
| |
|
|
| - **Assume API responses may vary**: Backend APIs can return data in different shapes (direct array, `.data` property, `.items` property, etc.) |
| - **Always implement type guards**: Use predicate functions to validate each item before using |
| - **Normalize responses early**: Convert varied response shapes into a consistent internal format in hooks/services |
| - **Handle edge cases**: Plan for empty responses, null values, and unexpected data structures |
|
|
| ```typescript |
| // β
GOOD: Defensive response handling |
| const isValidTool = (value: unknown): value is MCPTool => { |
| if (!value || typeof value !== 'object') return false; |
| const candidate = value as { name?: unknown; description?: unknown }; |
| return typeof candidate.name === 'string' && |
| (candidate.description === undefined || typeof candidate.description === 'string'); |
| }; |
|
|
| const normalizeToolsResponse = (payload: unknown): MCPTool[] | null => { |
| if (Array.isArray(payload)) return payload.filter(isValidTool); |
| if (payload && typeof payload === 'object') { |
| const candidate = payload as { tools?: unknown; data?: unknown }; |
| if (Array.isArray(candidate.tools)) return candidate.tools.filter(isValidTool); |
| if (Array.isArray(candidate.data)) return candidate.data.filter(isValidTool); |
| } |
| return null; |
| }; |
| ``` |
|
|
| |
|
|
| - β Don't use class components (except ErrorBoundary) |
| - β Don't use `.jsx` - use `.tsx` for components |
| - β Don't use raw `fetch` - use `api-wrapper.ts` |
| - β Don't create custom CSS files - use Tailwind |
| - β Don't commit `.env*` files (except `.env.example`) |
| - β Don't skip TypeScript types |
| - β Don't call `isUserRegistered()` directly in components - use `useAuth()` hook instead |
| - β Don't assume API response shapes - always normalize and validate |
| - β Don't forget to clean up event listeners in useEffect return statements |
| - β Don't use inline anonymous functions in onClick/onChange - prefer named handlers |
| - β Don't skip error states in loading components - implement proper error UI |
|
|
| |
|
|
| 1. **New Page**: Create in `src/app/pages/` (`.tsx`), wire via `App.tsx` |
| - Import with `.tsx` extension: `import NewPage from './pages/NewPage.tsx'` |
| 2. **New Component**: Create in `src/app/components/` (`.tsx`), type props |
| - Import with `.tsx` extension: `import Component from './components/Component.tsx'` |
| 3. **New API Endpoint**: Add to `API_ENDPOINTS` in constants, use `api-wrapper.ts` |
| - Import services with `.ts` extension: `import { service } from './services/service.ts'` |
| 4. **New Type**: Add to `src/app/types/`, export from `index.ts` |
| - Import types with `.ts` extension: `import type { Type } from '../types/type.types.ts'` |
| 5. **New Hook**: Create in `src/app/hooks/` (`.ts`), type return values |
| - Import hooks with `.ts` extension: `import { useHook } from '../hooks/useHook.ts'` |
| 6. **New Utility**: Add to `src/app/utils/index.ts`, keep pure and typed |
| - Export with `.ts` extension: `export * from './new-util.ts'` |
| - Import from index: `import { util } from '../utils/index.ts'` |
|
|
| |
|
|
| - Run `npm run build` to verify TypeScript compilation |
| - Run `npm test` before committing |
| - Ensure all props are typed |
| - Ensure all API calls use typed responses |
| - Follow existing code patterns and structure |
|
|