# 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