625 lines
No EOL
14 KiB
Markdown
625 lines
No EOL
14 KiB
Markdown
# 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!
|
|
}, [])
|
|
}
|
|
```
|
|
|
|
## 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>
|
|
```
|
|
|
|
## 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>`)
|
|
|
|
## 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) |