import React, { useState } from 'react';
/**
* React Grid Layout
* @url https://github.com/react-grid-layout/react-grid-layout
*/
import { WidthProvider, Responsive } from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
import 'bootstrap/dist/css/bootstrap.min.css';// Import Bootstrap CSS
import 'bootstrap/dist/js/bootstrap.bundle.min.js';// Import Bootstrap JS, includes Popper.js
import 'bootstrap-icons/font/bootstrap-icons.css';// Bootstrap Icons
import './App.css';

const ResponsiveGridLayout = WidthProvider(Responsive);

const BLOCK_SIZES = [
	{ id: 'photo', width: 2, height: 2, content: 'Photo' },
	{ id: 'contact', width: 2, height: 3, content: 'Contact Us' },
	{ id: 'logo', width: 1, height: 1, content: 'Logo' },
];

const App = () => {
	
	const [blocks, setBlocks] = useState([]);
	const [newBlockCount, setNewBlockCount] = useState(0);
	const [layout, setLayout] = useState([]);
	const [editingBlock, setEditingBlock] = useState(null);
	
	const ROW_HEIGHT = 100;
	const GRID_COLS = 6;
	const GRID_ROWS = 10;
	
	/**
	* Calculate Maximum Blocks per Grid
	* Function to calculate max blocks based on current layout
	*
	* Replaces:
	*		const MAX_BLOCKS = GRID_COLS * GRID_ROWS;
	*/
	const calculateMaxBlocks = (blockList) => {
		
		const occupiedSpaces = blockList.reduce((acc, block) => {
			
			for (let dy = 0; dy < block.h; dy++) {
				for (let dx = 0; dx < block.w; dx++) {
					
					const key = `${block.x + dx},${block.y + dy}`;
					acc[key] = true;// Marking occupied space
				}
			}
			
			return acc;
		}, {});
		
		return GRID_COLS * GRID_ROWS - Object.keys(occupiedSpaces).length;
	};
	
	/**
	* Add BLock
	*/
	const handleAddBlock = (width, height, content, id) => {
		
		const MAX_BLOCKS = calculateMaxBlocks(blocks);
		
		if (blocks.length >= MAX_BLOCKS) {
			
			alert("Maximum number of blocks reached!");
			return;
		}
		
		const newId = `block-${id}-${newBlockCount}`;
		let availablePosition = null;	
		
		for (let y = 0; y < GRID_ROWS; y++) {
			for (let x = 0; x < GRID_COLS; x++) {
				
				let canPlace = true;
				
				// Check if new block fits within grid boundaries
				if (x + width > GRID_COLS || y + height > GRID_ROWS) {
					
					canPlace = false;
					continue;
				}
				
				// Check for overlapping blocks
				for (let dy = 0; dy < height; dy++) {
					for (let dx = 0; dx < width; dx++) {
					
						//if (blocks.some(block => block.x === x + dx && block.y === y + dy)) {
						if (blocks.some(block => {
							
							const isOverlap = block.x === (x + dx) && block.y === (y + dy);
							return isOverlap;
						})) {
							canPlace = false;
							break;
						}
					}
					
					if (!canPlace) {
					
						break;
					}
				}
				if (canPlace) {
					
					availablePosition = { x, y, w: width, h: height };
					break;
				}
			}
			if (availablePosition) {
				
				break;
			}
		}
		if (availablePosition) {
			
			setBlocks(prevBlocks => [
				
				...prevBlocks,
				{ i: newId, x: availablePosition.x, y: availablePosition.y, w: width, h: height, content: content }
			]);
			
			setNewBlockCount(prevCount => prevCount + 1);
			setLayout(prevLayout => [...prevLayout, { i: newId, ...availablePosition }]);
		}
		else {
		
			alert("No available space to add a new block!");
		}
	};
	
	/**
	* Remove Block
	*/
	const handleRemoveBlock = (id) => {
		
		setBlocks(prevBlocks => prevBlocks.filter(block => block.i !== id));
		setLayout(prevLayout => prevLayout.filter(item => item.i !== id));
	};
	
	/**
	* Edit Block
	*/
	const handleEditBlock = (block) => {
		
		setEditingBlock(block);
	};
	
	/**
	* Save Block Content
	*/
	const handleSaveBlockContent = (newContent) => {
		
		if (!editingBlock) {
			
			return;
		}
		
		setBlocks(prevBlocks => 
			prevBlocks.map(block => (block.i === editingBlock.i ? { ...block, content: newContent } : block))
		);
		
		setEditingBlock(null);
	};
	
	/**
	* Layout Change
	*/
	const handleLayoutChange = (newLayout) => {
		
		const updatedLayout = newLayout.map(item => {
			
			return {
				...item,
				y: Math.min(item.y, GRID_ROWS - 1),// Ensure y position does not exceed GRID_ROWS
			};
		});
		
		setLayout(updatedLayout);
		
		// Check if the newly set layout is valid
		const validLayout = updatedLayout.filter((item, index) => {
			
			const block = blocks.find(b => b.i === item.i);
			return block && item.x + block.w <= GRID_COLS && item.y + block.h <= GRID_ROWS;
		});
		
		setBlocks(layout => layout.map(block => {
			
			const layoutItem = updatedLayout.find(item => item.i === block.i);
			return layoutItem ? { ...block, x: layoutItem.x, y: layoutItem.y, w: layoutItem.w, h: layoutItem.h } : block;
		}));
	};
	
	/**
	* Add Random Block
	* Function to add a block with random size
	*
	const handleAddRandomBlock = () => {
		
		const randomSize = BLOCK_SIZES[Math.floor(Math.random() * BLOCK_SIZES.length)];
		handleAddBlock(randomSize.width, randomSize.height, randomSize.content, randomSize.id);
	};//*/
	
	/**
	* Render Block for Grid
	*/
	const renderBlockContent = (block) => (
		
		<div className="flex flex-col h-full justify-between" style={{ maxWidth: '18rem' }}>
		
			<div className="header d-flex justify-content-between align-items-center">
				
				<span className="drag-handle" style={{ cursor: 'move' }}>
					<i class="bi bi-three-dots-vertical"></i>
				</span>
			
				<button
					className="btn btn-secondary btn-sm me-2" 
					onClick={() => handleEditBlock(block)}>
					<i className="bi bi-pencil"></i>
				</button>
					
				<button
					className="btn btn-danger btn-sm" 
					onClick={() => handleRemoveBlock(block.i)}>
					<i className="bi bi-trash"></i>
				</button>
			</div>
			
			<div className="body">
				<p>{block.content}</p>
			</div>
		</div>
	);
	
	return (
		
		<div className="container-md">
			<h1>Tetris-Like Layout Editor</h1>
			
			{/* Create buttons for each block size */}
			{BLOCK_SIZES.map((size, index) => (
				<button
					key={index}
					className="btn btn-primary mb-3"
					onClick={() => handleAddBlock(size.width, size.height, size.content, size.id)}
				>
					Add {size.content} Block
				</button>
			))}
			
			{/* Add button for random block 
			<button className="btn btn-success mb-3" onClick={handleAddRandomBlock}>
				Add Random Block
			</button>
			*/}
			
			<ResponsiveGridLayout
				className="layout"
				layouts={{ lg: layout }}
				Breakpoints={{ lg: 1200, md: 996, sm: 768 }}
				cols={{ lg: GRID_COLS, md: GRID_COLS, sm: GRID_COLS }}
				rowHeight={ ROW_HEIGHT }
				onLayoutChange={ handleLayoutChange }
				isBounded={ false }// Prevent blocks from being dragged outside the grid
				preventCollision={ true }// Prevent collisions when dragging
				isResizable={ false }// Disable resizing of blocks
				draggableHandle=".drag-handle"
			>
			
			{blocks.map((block) => (
				<div key={block.i} className="block" data-grid={block}>
					
					<div className="content-area">
						<div className="drag-handle" style={{ cursor: 'move' }}></div>
							{renderBlockContent(block)}
						</div>
					</div>
				))}
			</ResponsiveGridLayout>
		</div>
	);
};

export default App;


Uncaught runtime errors:
ERROR
ResponsiveReactGridLayout: `cols` entry for breakpoint xxs is missing!

Why do i need it?