# Frontend Development Rules for AI/LLM This document provides guidelines and rules that AI/LLM assistants should follow when writing or modifying frontend code for the Crafting Shop application. ## Architecture Principles ### State Management - **ALWAYS** use React Context API for global state management - **NEVER** use Zustand, Redux, or other state management libraries - Store context in `frontend/src/context/` - Use useContext() hook for accessing context ```jsx // ✅ CORRECT import { useContext } from "react" import { AppContext } from "../context/AppContext" function Component() { const { user, setUser } = useContext(AppContext) // ... } // ❌ WRONG import { create } from "zustand" ``` ### API Calls - **ALWAYS** use the custom `useApi` hook for all API calls - **NEVER** use axios directly in components - Import useApi from `frontend/src/hooks/useApi.js` ```jsx // ✅ CORRECT import useApi from "../hooks/useApi" function Products() { const { api } = useApi() const [products, setProducts] = useState([]) useEffect(() => { api.get("/api/products") .then(response => setProducts(response.data)) .catch(error => console.error(error)) }, [api]) } // ❌ WRONG import axios from "axios" function Products() { useEffect(() => { axios.get("/api/products") // Don't do this! }, []) } ``` ### API Integration with Loaders and Toasts - **ALWAYS** create custom hooks for API operations that integrate loader and toast logic - **NEVER** handle errors in components when the hook already shows toasts - Custom hooks should use `useLoader` and `useToast` for consistent UX - Hooks should return data, refetch function, and optionally error state for debugging - Components should NOT display error UI when toasts are already shown ```jsx // ✅ CORRECT - Custom hook with loader and toast integration import { useState, useEffect } from "react" import useApi from "./useApi" import { useLoader } from "../context/loaders/useLoader" import { useToast } from "../context/toasts/useToast" function useProducts() { const [products, setProducts] = useState([]) const [error, setError] = useState(null) const { getProducts } = useApi() const { withLoader } = useLoader() const { addNotification } = useToast() const fetchProducts = async () => { try { setError(null) // Use withLoader to show loading state const data = await withLoader( () => getProducts(), 'Loading products...' ) setProducts(data) // Show success toast addNotification({ type: 'success', title: 'Products Loaded', message: `Successfully loaded ${data.length} products.`, duration: 3000, }) return data } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to load products' setError(errorMessage) // Show error toast addNotification({ type: 'error', title: 'Error Loading Products', message: errorMessage, duration: 5000, }) return [] } } useEffect(() => { fetchProducts() }, []) return { products, error, // For debugging, not for UI display loading: false, // Loading is handled by global loader refetch: fetchProducts, } } export default useProducts ``` ```jsx // ✅ CORRECT - Component using the custom hook function Products() { const { products, refetch } = useProducts() // Note: No error UI here - toast is already shown by the hook return (

Products

{products.map(product => ( ))}
) } ``` ```jsx // ❌ WRONG - Handling errors in component when toast already shown function Products() { const { products, error, refetch } = useProducts() // BAD PRACTICE: This duplicates the error message already shown in toast if (error) { return (

Failed to load products

) } return (
{products.map(product => ( ))}
) } ``` **Why this is bad practice:** 1. **Duplicate UI**: Users see the same error message twice (toast + component error UI) 2. **Inconsistent UX**: Some operations show toasts, others show component error messages 3. **Confusing Experience**: Users don't know which error message to act on 4. **Redundant Code**: You're writing error handling twice (in hook and component) 5. **Maintenance Issue**: If you change error handling in hook, component UI doesn't update **The error state in hooks is for:** - Debugging purposes - Conditional rendering based on error type (rare cases) - Logging or analytics - NOT for displaying error UI to users **Benefits of this pattern:** 1. **Consistent UX**: All errors shown via toasts in the same place (top-right) 2. **Separation of Concerns**: Hook handles data fetching, component handles rendering 3. **Global Loading**: Loader covers entire app, preventing double-submissions 4. **Auto-Cleanup**: `withLoader` ensures loader is hidden even on errors 5. **Clean Components**: Components focus on displaying data, not handling errors ``` ## Code Style ### Formatting - Use double quotes for strings and object properties - Use functional components with hooks (no class components) - Use arrow functions for callbacks - Follow React best practices - Maximum line length: 100 characters ### Naming Conventions - Components: PascalCase (e.g., `ProductCard`, `Navbar`) - Hooks: camelCase with `use` prefix (e.g., `useProducts`, `useAuth`) - Files: kebab-case (e.g., `product-card.jsx`, `navbar.jsx`) - Directories: lowercase (e.g., `components/`, `pages/`) ## Component Rules ### Functional Components - **ALWAYS** use functional components with React hooks - **NEVER** use class components - Destructure props at the top of component ```jsx // ✅ CORRECT import React from "react" function ProductCard({ name, price, onClick }) { return (

{name}

${price}

) } export default ProductCard ``` ### Props Validation - Use PropTypes for component props - Define PropTypes at the bottom of file - Mark required props ```jsx import PropTypes from "prop-types" function ProductCard({ name, price, onClick }) { // ... } ProductCard.propTypes = { name: PropTypes.string.isRequired, price: PropTypes.number.isRequired, onClick: PropTypes.func } ``` ### Conditional Rendering - Use ternary operators for simple conditions - Use logical AND for conditional elements - Keep JSX readable ```jsx // ✅ CORRECT {isLoading ? : } {error && } {user && } ``` ### Lists Rendering - **ALWAYS** provide unique `key` prop - Use `.map()` for transforming arrays to elements ```jsx // ✅ CORRECT {products.map(product => ( ))} ``` ## Hook Rules ### Custom Hooks - Create custom hooks for reusable logic - Name hooks with `use` prefix - Place hooks in `frontend/src/hooks/` ```jsx // ✅ CORRECT - Create custom hook import { useState, useEffect } from "react" import useApi from "./useApi" function useProducts() { const { api } = useApi() const [products, setProducts] = useState([]) const [loading, setLoading] = useState(false) useEffect(() => { setLoading(true) api.get("/api/products") .then(response => setProducts(response.data)) .finally(() => setLoading(false)) }, [api]) return { products, loading } } export default useProducts ``` ### Effect Rules - **ALWAYS** include all dependencies in dependency array - Use proper cleanup in useEffect - Avoid infinite loops ```jsx // ✅ CORRECT useEffect(() => { const fetchData = async () => { const response = await api.get("/api/products") setData(response.data) } fetchData() return () => { // Cleanup } }, [api]) // Include all dependencies ``` ## Styling Rules ### Tailwind CSS - **ALWAYS** use Tailwind CSS for styling - Use utility classes for styling - Avoid custom CSS files when possible - Use `className` prop (not `class`) ```jsx // ✅ CORRECT

Welcome

// ❌ WRONG
// Wrong prop name

Welcome

``` ### Dark Mode - Default to dark mode styling - Use Tailwind's dark mode utilities if needed - Maintain consistent dark theme ```jsx // ✅ CORRECT

Dark mode text

``` ### Responsive Design - Use Tailwind responsive prefixes - Mobile-first approach - Test on multiple screen sizes ```jsx // ✅ CORRECT
{/* Cards */}
``` ### Icons - **ALWAYS** use inline SVG icons - **NEVER** use icon libraries like lucide-react, react-icons, or font-awesome - Create reusable SVG icon components when needed - SVGs should be defined as functional components ```jsx // ✅ CORRECT - Inline SVG as a component const TrashIcon = () => ( ); function DeleteButton() { return ( ); } // ❌ WRONG - Using lucide-react import { Trash2 } from "lucide-react"; function DeleteButton() { return ( ); } // ❌ WRONG - Using react-icons import { FaTrash } from "react-icons/fa"; function DeleteButton() { return ( ); } ``` **Why inline SVGs?** 1. **No dependencies**: Reduces bundle size and eliminates external dependencies 2. **Full control**: You can customize SVG properties directly in JSX 3. **Performance**: No runtime overhead from library wrappers 4. **Consistency**: All icons follow to same pattern and styling 5. **TypeScript support**: Full type safety without any issues ## Routing Rules ### React Router - Use `react-router-dom` for navigation - Define routes in `frontend/src/main.jsx` - Use `` for navigation (not ``) ```jsx // ✅ CORRECT import { Link } from "react-router-dom" function Navbar() { return ( ) } ``` ### Protected Routes - Use context to check authentication - Redirect to login if not authenticated - Store JWT in context ```jsx // ✅ CORRECT import { useContext } from "react" import { Navigate } from "react-router-dom" import { AppContext } from "../context/AppContext" function ProtectedRoute({ children }) { const { token } = useContext(AppContext) if (!token) { return } return children } ``` ## Form Rules ### Form Handling - Use controlled components for forms - Store form state in useState - Handle onChange events properly ```jsx // ✅ CORRECT function LoginForm() { const [email, setEmail] = useState("") const [password, setPassword] = useState("") const handleSubmit = (e) => { e.preventDefault() // Handle submission } return (
setEmail(e.target.value)} className="border rounded p-2" /> setPassword(e.target.value)} className="border rounded p-2" />
) } ``` ### Form Validation - Validate form data before submission - Show error messages to users - Disable submit button during submission ```jsx // ✅ CORRECT function RegisterForm() { const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [error, setError] = useState("") const [loading, setLoading] = useState(false) const handleSubmit = async (e) => { e.preventDefault() setError("") // Validation if (!email || !password) { setError("Please fill in all fields") return } if (password.length < 6) { setError("Password must be at least 6 characters") return } setLoading(true) try { // Submit form } catch (err) { setError(err.message) } finally { setLoading(false) } } return (
{/* Form fields */} {error &&
{error}
}
) } ``` ## Error Handling Rules ### Error Boundaries - Use error boundaries for component errors - Display user-friendly error messages - Log errors for debugging ### API Errors - Catch errors from useApi hook - Display error messages to users - Use try-catch for async operations ```jsx // ✅ CORRECT function Products() { const { api } = useApi() const [error, setError] = useState(null) const loadProducts = async () => { try { const response = await api.get("/api/products") setProducts(response.data) } catch (err) { setError("Failed to load products") console.error(err) } } if (error) { return
{error}
} // Render products } ``` ## Performance Rules ### Memoization - Use `React.memo()` for expensive components - Use `useMemo()` for expensive calculations - Use `useCallback()` for stable function references ```jsx // ✅ CORRECT import React, { useMemo, useCallback } from "react" function ExpensiveComponent({ items }) { const sortedItems = useMemo(() => { return [...items].sort((a, b) => a.name.localeCompare(b.name)) }, [items]) const handleClick = useCallback((id) => { console.log("Clicked:", id) }, []) return (
{sortedItems.map(item => (
handleClick(item.id)}> {item.name}
))}
) } export default React.memo(ExpensiveComponent) ``` ### Lazy Loading - Use React.lazy() for code splitting - Use Suspense for loading states - Lazy load routes and heavy components ```jsx // ✅ CORRECT import { lazy, Suspense } from "react" const Products = lazy(() => import("./pages/Products")) const Cart = lazy(() => import("./pages/Cart")) function App() { return ( Loading...}> } /> } /> ) } ``` ## File Structure for New Features When adding new features, follow this structure: ``` frontend/src/ ├── components/ │ └── feature/ │ ├── FeatureList.jsx │ └── FeatureCard.jsx ├── pages/ │ └── FeaturePage.jsx ├── context/ │ └── FeatureContext.jsx (if needed) ├── hooks/ │ └── useFeature.js (if needed) └── services/ └── featureService.js (if needed) ``` ## Common Patterns ### Creating a New Component ```jsx import React, { useState } from "react" import PropTypes from "prop-types" function NewComponent({ title, onAction }) { const [isOpen, setIsOpen] = useState(false) return (

{title}

{isOpen && (

Content goes here

)}
) } NewComponent.propTypes = { title: PropTypes.string.isRequired, onAction: PropTypes.func.isRequired } export default NewComponent ``` ### Creating a New Page ```jsx import React, { useEffect } from "react" import useApi from "../hooks/useApi" function NewPage() { const { api } = useApi() const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { const fetchData = async () => { try { const response = await api.get("/api/endpoint") setData(response.data) } catch (err) { setError("Failed to load data") } finally { setLoading(false) } } fetchData() }, [api]) if (loading) { return
Loading...
} if (error) { return
{error}
} return (

New Page

{/* Page content */}
) } export default NewPage ``` ### Creating a New Context ```jsx import React, { createContext, useContext, useState } from "react" const NewContext = createContext(null) export function NewContextProvider({ children }) { const [value, setValue] = useState(null) return ( {children} ) } export function useNewContext() { const context = useContext(NewContext) if (!context) { throw new Error("useNewContext must be used within NewContextProvider") } return context } ``` ## DO NOT DO List ❌ **NEVER** use Zustand, Redux, or other state management ❌ **NEVER** use axios directly (always use useApi hook) ❌ **NEVER** use class components ❌ **NEVER** use `class` attribute (use `className`) ❌ **NEVER** forget `key` prop in lists ❌ **NEVER** ignore useEffect dependencies ❌ **NEVER** use `this.state` or `this.props` ❌ **NEVER** create custom CSS files (use Tailwind) ❌ **NEVER** use single quotes for strings ❌ **NEVER** forget error handling ❌ **NEVER** hardcode API URLs ❌ **NEVER** skip PropTypes validation ❌ **NEVER** use `
` for navigation (use ``) ❌ **NEVER** use icon libraries like lucide-react, react-icons, or font-awesome (always use inline SVGs) ## Checklist Before Committing - [ ] Component uses functional syntax with hooks - [ ] All API calls use useApi hook - [ ] State uses Context API (not Redux/Zustand) - [ ] All props have PropTypes validation - [ ] Tailwind classes used for styling - [ ] Dark mode styling applied - [ ] Responsive design considered - [ ] Error handling implemented - [ ] Loading states added - [ ] Keys provided for list items - [ ] useEffect dependencies correct - [ ] Code follows project conventions - [ ] Files properly named (kebab-case)