2026-02-14 16:56:10 +00:00
# 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!
}, [])
}
```
2026-02-24 11:03:23 +00:00
### 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 (
< div >
< h1 > Products< / h1 >
< button onClick = {() = > refetch()}>Refresh< / button >
{products.map(product => (
< ProductCard key = {product.id} product = {product} / >
))}
< / div >
)
}
```
```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 (
< div className = "text-center py-12" >
< p className = "text-red-400 mb-4" > Failed to load products< / p >
< button onClick = {() = > refetch()}>Retry< / button >
< / div >
)
}
return (
< div >
{products.map(product => (
< ProductCard key = {product.id} product = {product} / >
))}
< / div >
)
}
```
**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
```
2026-02-14 16:56:10 +00:00
## 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 (
< div className = "product-card" onClick = {onClick} >
< h3 > {name}< / h3 >
< p > ${price}< / p >
< / div >
)
}
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 ? < Loader / > : < Content / > }
{error & & < ErrorMessage message = {error} / > }
{user & & < WelcomeMessage user = {user} / > }
```
### Lists Rendering
- **ALWAYS** provide unique `key` prop
- Use `.map()` for transforming arrays to elements
```jsx
// ✅ CORRECT
{products.map(product => (
< ProductCard
key={product.id}
product={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
< div className = "bg-gray-900 text-white p-4 rounded-lg" >
< h1 className = "text-2xl font-bold" > Welcome< / h1 >
< / div >
// ❌ WRONG
< div class = "custom-card" > // Wrong prop name
< h1 class = "title" > Welcome< / h1 >
< / div >
```
### Dark Mode
- Default to dark mode styling
- Use Tailwind's dark mode utilities if needed
- Maintain consistent dark theme
```jsx
// ✅ CORRECT
< div className = "bg-gray-900 text-gray-100" >
< p className = "text-gray-300" > Dark mode text< / p >
< / div >
```
### Responsive Design
- Use Tailwind responsive prefixes
- Mobile-first approach
- Test on multiple screen sizes
```jsx
// ✅ CORRECT
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" >
{/* Cards */}
< / div >
```
2026-02-27 12:41:44 +00:00
### 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 = () => (
< svg xmlns = "http://www.w3.org/2000/svg" width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" >
< polyline points = "3 6 5 6 21 6" > < / polyline >
< path d = "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" > < / path >
< / svg >
);
function DeleteButton() {
return (
< button className = "flex items-center gap-2" >
< span className = "w-4 h-4" > < TrashIcon / > < / span >
Delete
< / button >
);
}
// ❌ WRONG - Using lucide-react
import { Trash2 } from "lucide-react";
function DeleteButton() {
return (
< button className = "flex items-center gap-2" >
< Trash2 className = "w-4 h-4" / >
Delete
< / button >
);
}
// ❌ WRONG - Using react-icons
import { FaTrash } from "react-icons/fa";
function DeleteButton() {
return (
< button className = "flex items-center gap-2" >
< FaTrash className = "w-4 h-4" / >
Delete
< / button >
);
}
```
**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
2026-02-14 16:56:10 +00:00
## Routing Rules
### React Router
- Use `react-router-dom` for navigation
- Define routes in `frontend/src/main.jsx`
- Use `<Link>` for navigation (not `<a>` )
```jsx
// ✅ CORRECT
import { Link } from "react-router-dom"
function Navbar() {
return (
< nav >
< Link to = "/" > Home< / Link >
< Link to = "/products" > Products< / Link >
< Link to = "/cart" > Cart< / Link >
< / nav >
)
}
```
### 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 < Navigate to = "/login" / >
}
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 (
< form onSubmit = {handleSubmit} >
< input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="border rounded p-2"
/>
< input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="border rounded p-2"
/>
< button type = "submit" className = "bg-blue-500 text-white p-2" >
Login
< / button >
< / form >
)
}
```
### 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 onSubmit = {handleSubmit} >
{/* Form fields */}
{error & & < div className = "text-red-500" > {error}< / div > }
< button type = "submit" disabled = {loading} className = "..." >
{loading ? "Submitting..." : "Register"}
< / button >
< / form >
)
}
```
## 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 < div className = "text-red-500" > {error}< / div >
}
// 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 (
< div >
{sortedItems.map(item => (
< div key = {item.id} onClick = {() = > handleClick(item.id)}>
{item.name}
< / div >
))}
< / div >
)
}
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 (
< Suspense fallback = {<div > Loading...< / div > }>
< Routes >
< Route path = "/products" element = {<Products / > } />
< Route path = "/cart" element = {<Cart / > } />
< / Routes >
< / Suspense >
)
}
```
## 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 (
< div className = "bg-gray-800 p-4 rounded" >
< h2 className = "text-xl font-bold text-white" > {title}< / h2 >
< button
onClick={() => setIsOpen(!isOpen)}
className="bg-blue-500 text-white px-4 py-2 rounded"
>
{isOpen ? "Close" : "Open"}
< / button >
{isOpen & & (
< div className = "mt-4" >
< p > Content goes here< / p >
< button onClick = {onAction} > Action< / button >
< / div >
)}
< / div >
)
}
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 < div className = "text-white" > Loading...< / div >
}
if (error) {
return < div className = "text-red-500" > {error}< / div >
}
return (
< div className = "container mx-auto p-4" >
< h1 className = "text-3xl font-bold text-white mb-4" >
New Page
< / h1 >
{/* Page content */}
< / div >
)
}
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 (
< NewContext.Provider value = {{ value , setValue } } >
{children}
< / NewContext.Provider >
)
}
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 `<a>` for navigation (use `<Link>` )
2026-02-27 12:41:44 +00:00
❌ **NEVER** use icon libraries like lucide-react, react-icons, or font-awesome (always use inline SVGs)
2026-02-14 16:56:10 +00:00
## 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)