Development Guide
Learn how to extend and customize LegoCity for your city's needs.
Overview
This guide covers:
- 🧱 Creating Blocks - Build custom UI components
- 🔌 Writing Plugins - Extend PayloadCMS functionality
- 🌱 Seed Data - Populate sample data
- 🗺️ Map Customization - Add custom map layers (see User Guide)
- 🧪 Testing - Write and run tests (coming soon)
Development Philosophy
Configuration Over Code
Prefer configuring in PayloadCMS admin panel over writing code:
❌ Don't: Hard-code page layouts in components
✅ Do: Create configurable blocks in PayloadCMS
❌ Don't: Hard-code API endpoints in frontend
✅ Do: Use server-side API proxies
Extensibility
Build features as reusable blocks and plugins:
❌ Don't: Modify core components directly
✅ Do: Create new blocks and extend existing ones
❌ Don't: Fork the repository for customization
✅ Do: Use plugin system and configuration
Type Safety
Use TypeScript for all custom code:
❌ Don't: Use any types
✅ Do: Define proper interfaces and types
// ❌ Bad
const processData = (data: any) => { ... }
// ✅ Good
interface CityData {
id: string;
name: string;
sensors: Sensor[];
}
const processData = (data: CityData) => { ... }Project Structure
dashboard/src/
├── app/ # Next.js App Router
│ ├── (frontend)/ # Public pages
│ │ ├── page.tsx # Home page
│ │ └── [slug]/ # Dynamic pages
│ ├── (payload)/ # Admin routes
│ └── api/ # API routes
│
├── blocks/ # PayloadCMS Blocks
│ ├── RenderBlocks.tsx # Block renderer
│ ├── ArchiveBlock/ # List posts
│ ├── MediaBlock/ # Display media
│ └── CustomBlock/ # Your custom blocks
│
├── collections/ # PayloadCMS Collections
│ ├── Pages.ts # Page content type
│ ├── Posts.ts # Blog posts
│ ├── Media.ts # File uploads
│ └── Users.ts # Admin users
│
├── components/ # React Components
│ ├── Card/ # Reusable card
│ ├── Map/ # Map components
│ └── ui/ # Shadcn components
│
├── fields/ # Custom Fields
│ ├── link.ts # Link field
│ └── linkGroup.ts # Link group
│
├── plugins/ # PayloadCMS Plugins
│ └── index.ts # Plugin registration
│
├── providers/ # React Context
│ ├── Theme/ # Theme provider
│ └── HeaderTheme/ # Header theme
│
└── utilities/ # Helper Functions
├── generateMeta.ts # SEO metadata
└── getDocument.ts # Fetch documentsDevelopment Workflow
1. Plan Your Feature
Before coding:
- 📝 Define requirements
- 🎨 Design UI mockups
- 🏗️ Choose architecture (block, plugin, component)
- 📋 Break into tasks
2. Create Branch
git checkout -b feature/your-feature-name3. Implement
Follow these steps:
Create Types (if needed)
typescript// types/city-data.ts export interface CityEntity { id: string; type: string; location: GeoJSON.Point; }Build Component/Block
tsx// blocks/MyBlock/Component.tsx export const MyBlock: React.FC<Props> = ({ data }) => { return <div>{/* Implementation */}</div>; };Add Configuration
typescript// blocks/MyBlock/config.ts export const MyBlock: Block = { slug: "myBlock", fields: [ /* ... */ ], };Write Tests
typescript// blocks/MyBlock/MyBlock.test.tsx describe("MyBlock", () => { it("renders correctly", () => { // Test implementation }); });Update Documentation
- Add to relevant docs
- Include usage examples
4. Test
# Run tests
pnpm test
# Check types
pnpm type-check
# Lint code
pnpm lint
# Build
pnpm build5. Create Pull Request
git add .
git commit -m "feat: add custom block for city stats"
git push origin feature/your-feature-nameCreate PR on GitHub with:
- Clear description
- Screenshots (if UI changes)
- Test results
- Documentation updates
Common Development Tasks
Creating a Block
See Creating Blocks Guide for detailed instructions.
Quick example:
// blocks/CityStats/config.ts
import { Block } from "payload/types";
export const CityStats: Block = {
slug: "cityStats",
fields: [
{
name: "title",
type: "text",
required: true,
},
{
name: "metrics",
type: "array",
fields: [
{ name: "label", type: "text" },
{ name: "value", type: "number" },
],
},
],
};// blocks/CityStats/Component.tsx
export const CityStatsBlock = ({ title, metrics }) => (
<div>
<h2>{title}</h2>
{metrics.map((m) => (
<div key={m.label}>
<span>{m.label}</span>: {m.value}
</div>
))}
</div>
);Creating a Plugin
See Writing Plugins Guide for details.
Example:
// plugins/customPlugin.ts
import { Plugin } from "payload/config";
export const customPlugin = (): Plugin => ({
name: "custom-plugin",
init: (payload) => {
// Initialize plugin
},
hooks: {
beforeChange: [
(args) => {
// Hook logic
},
],
},
});Creating a Collection
// collections/CityServices.ts
import { CollectionConfig } from "payload/types";
export const CityServices: CollectionConfig = {
slug: "city-services",
admin: {
useAsTitle: "name",
group: "Content",
},
access: {
read: () => true,
},
fields: [
{
name: "name",
type: "text",
required: true,
},
{
name: "description",
type: "textarea",
},
{
name: "location",
type: "point",
},
],
};Register in payload.config.ts:
import { CityServices } from "./collections/CityServices";
export default buildConfig({
collections: [
// ... existing
CityServices,
],
});Adding API Route
// app/api/city-data/route.ts
import { NextRequest } from "next/server";
export async function GET(req: NextRequest) {
const city = req.nextUrl.searchParams.get("city");
// Fetch data
const data = await fetchCityData(city);
return Response.json(data);
}Custom Map Layer
// lib/mapLayers/trafficLayer.ts
export const trafficLayer: LayerDefinition = {
id: "traffic",
type: "line",
source: {
type: "geojson",
data: "/api/traffic-data",
},
paint: {
"line-color": [
"interpolate",
["linear"],
["get", "congestion"],
0,
"#00ff00",
50,
"#ffff00",
100,
"#ff0000",
],
"line-width": 3,
},
};Best Practices
Code Organization
✅ One component per file
✅ Colocate related files (component + styles + tests)
✅ Use index files for exports
✅ Group by feature, not by type
Performance
✅ Use React Server Components when possible
✅ Lazy load heavy components
✅ Optimize images with next/image
✅ Implement data caching
✅ Monitor bundle size
Security
✅ Validate all inputs
✅ Use server-side API keys
✅ Implement CSRF protection
✅ Sanitize user content
✅ Set proper CORS headers
Accessibility
✅ Use semantic HTML
✅ Add ARIA labels
✅ Support keyboard navigation
✅ Test with screen readers
✅ Maintain color contrast
Development Tools
Recommended VS Code Extensions
- ESLint - Linting
- Prettier - Code formatting
- TypeScript and JavaScript Language Features - IntelliSense
- Tailwind CSS IntelliSense - CSS autocomplete
- MongoDB for VS Code - Database management
- GitLens - Git integration
Browser DevTools
- React DevTools - Component inspection
- Network Tab - API debugging
- Console - Error logging
- Performance Tab - Profiling
CLI Tools
# Generate Payload types
pnpm payload generate:types
# Check for outdated dependencies
pnpm outdated
# Analyze bundle size
pnpm build --analyze
# Run security audit
pnpm auditDebugging
Server-Side Debugging
// Enable detailed logging
if (process.env.NODE_ENV === "development") {
console.log("Debug:", data);
}Client-Side Debugging
// Use React DevTools
useEffect(() => {
console.log("Component mounted", props);
}, []);Database Debugging
# MongoDB shell
mongosh legocity
# Enable profiling
db.setProfilingLevel(2)Testing
Quick test:
import { render, screen } from "@testing-library/react";
import { MyComponent } from "./Component";
describe("MyComponent", () => {
it("renders title", () => {
render(<MyComponent title="Test" />);
expect(screen.getByText("Test")).toBeInTheDocument();
});
});Documentation
When adding features:
- Code Comments - Explain complex logic
- JSDoc - Document functions and types
- README - Update relevant READMEs
- Docs Site - Add to MkDocs documentation
Example JSDoc:
/**
* Fetches city data from NGSI-LD broker
* @param cityId - Unique city identifier
* @param options - Query options
* @returns Promise resolving to city data
* @throws Error if city not found
*/
export async function fetchCityData(
cityId: string,
options?: QueryOptions
): Promise<CityData> {
// Implementation
}Contributing
See CONTRIBUTING.md for:
- Code of Conduct
- Git workflow
- PR guidelines
- Review process
Resources
- 📖 Next.js Documentation
- 📖 PayloadCMS Documentation
- 📖 React Documentation
- 📖 TypeScript Handbook
- 📖 Tailwind CSS
- 📖 Mapbox GL JS
Start developing: Choose your topic:
- Creating Blocks - Build UI components
- Writing Plugins - Extend functionality
- Seed Data - Populate test data