Initial commit: Developer Tools MVP with visual editor

- Complete React app with 7 developer tools
- JSON Tool with visual structured editor
- Serialize Tool with visual structured editor
- URL, Base64, CSV/JSON, Beautifier, Diff tools
- Responsive navigation with dropdown menu
- Dark/light mode toggle
- Mobile-responsive design with sticky header
- All tools working with copy/paste functionality
This commit is contained in:
dwindown
2025-08-02 09:31:26 +07:00
commit 7f2dd5260f
45657 changed files with 4730486 additions and 0 deletions

184
src/pages/Base64Tool.js Normal file
View File

@@ -0,0 +1,184 @@
import React, { useState } from 'react';
import { Hash, Upload, Download } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
const Base64Tool = () => {
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [mode, setMode] = useState('encode'); // 'encode' or 'decode'
const encodeBase64 = () => {
try {
const encoded = btoa(unescape(encodeURIComponent(input)));
setOutput(encoded);
} catch (err) {
setOutput(`Error: ${err.message}`);
}
};
const decodeBase64 = () => {
try {
const decoded = decodeURIComponent(escape(atob(input)));
setOutput(decoded);
} catch (err) {
setOutput(`Error: Invalid Base64 string`);
}
};
const handleProcess = () => {
if (mode === 'encode') {
encodeBase64();
} else {
decodeBase64();
}
};
const handleFileUpload = (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
if (mode === 'encode') {
setInput(e.target.result);
} else {
// For decode mode, read as text
const textReader = new FileReader();
textReader.onload = (textEvent) => {
setInput(textEvent.target.result);
};
textReader.readAsText(file);
}
};
if (mode === 'encode') {
reader.readAsText(file);
} else {
reader.readAsText(file);
}
}
};
const clearAll = () => {
setInput('');
setOutput('');
};
const loadSample = () => {
if (mode === 'encode') {
setInput('Hello, World! This is a sample text for Base64 encoding.');
} else {
setInput('SGVsbG8sIFdvcmxkISBUaGlzIGlzIGEgc2FtcGxlIHRleHQgZm9yIEJhc2U2NCBlbmNvZGluZy4=');
}
};
return (
<ToolLayout
title="Base64 Encoder/Decoder"
description="Convert text to Base64 and back with support for files"
icon={Hash}
>
{/* Mode Toggle */}
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
<button
onClick={() => setMode('encode')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'encode'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Encode
</button>
<button
onClick={() => setMode('decode')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'decode'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Decode
</button>
</div>
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={handleProcess} className="tool-button">
{mode === 'encode' ? 'Encode to Base64' : 'Decode from Base64'}
</button>
<label className="tool-button-secondary cursor-pointer flex items-center space-x-2">
<Upload className="h-4 w-4" />
<span>Upload File</span>
<input
type="file"
onChange={handleFileUpload}
className="hidden"
accept=".txt,.json,.xml,.csv"
/>
</label>
<button onClick={loadSample} className="tool-button-secondary">
Load Sample
</button>
<button onClick={clearAll} className="tool-button-secondary">
Clear All
</button>
</div>
{/* Input/Output Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Input */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'encode' ? 'Text to Encode' : 'Base64 to Decode'}
</label>
<div className="relative">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={
mode === 'encode'
? 'Enter text to encode to Base64...'
: 'Enter Base64 string to decode...'
}
className="tool-input h-96"
/>
</div>
</div>
{/* Output */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'encode' ? 'Base64 Output' : 'Decoded Text'}
</label>
<div className="relative">
<textarea
value={output}
readOnly
placeholder={
mode === 'encode'
? 'Base64 encoded text will appear here...'
: 'Decoded text will appear here...'
}
className="tool-input h-96 bg-gray-50 dark:bg-gray-800"
/>
{output && <CopyButton text={output} />}
</div>
</div>
</div>
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> Base64 encoding is commonly used for data transmission and storage</li>
<li> Upload text files to encode/decode their contents</li>
<li> Encoded Base64 strings are safe for URLs and email transmission</li>
<li> Use the toggle to switch between encode and decode modes</li>
</ul>
</div>
</ToolLayout>
);
};
export default Base64Tool;

450
src/pages/BeautifierTool.js Normal file
View File

@@ -0,0 +1,450 @@
import React, { useState } from 'react';
import { FileText } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
const BeautifierTool = () => {
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [language, setLanguage] = useState('json');
const [mode, setMode] = useState('beautify'); // 'beautify' or 'minify'
const beautifyJson = (text) => {
try {
const parsed = JSON.parse(text);
return JSON.stringify(parsed, null, 2);
} catch (err) {
throw new Error(`Invalid JSON: ${err.message}`);
}
};
const minifyJson = (text) => {
try {
const parsed = JSON.parse(text);
return JSON.stringify(parsed);
} catch (err) {
throw new Error(`Invalid JSON: ${err.message}`);
}
};
const beautifyXml = (text) => {
try {
// Clean input text first
let cleanText = text.trim();
if (!cleanText) {
throw new Error('Empty XML input');
}
// Parse and validate XML
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(cleanText, 'text/xml');
if (xmlDoc.getElementsByTagName('parsererror').length > 0) {
throw new Error('Invalid XML syntax');
}
// Format XML properly
const formatXmlNode = (node, depth = 0) => {
const indent = ' '.repeat(depth);
let result = '';
if (node.nodeType === Node.ELEMENT_NODE) {
const tagName = node.tagName;
const attributes = Array.from(node.attributes)
.map(attr => `${attr.name}="${attr.value}"`)
.join(' ');
const openTag = `<${tagName}${attributes ? ' ' + attributes : ''}`;
if (node.childNodes.length === 0) {
result += `${indent}${openTag} />\n`;
} else {
const hasTextContent = Array.from(node.childNodes)
.some(child => child.nodeType === Node.TEXT_NODE && child.textContent.trim());
if (hasTextContent && node.childNodes.length === 1) {
// Single text node - keep on same line
result += `${indent}${openTag}>${node.textContent.trim()}</${tagName}>\n`;
} else {
// Multiple children or mixed content
result += `${indent}${openTag}>\n`;
Array.from(node.childNodes).forEach(child => {
if (child.nodeType === Node.ELEMENT_NODE) {
result += formatXmlNode(child, depth + 1);
} else if (child.nodeType === Node.TEXT_NODE) {
const text = child.textContent.trim();
if (text) {
result += `${' '.repeat(depth + 1)}${text}\n`;
}
}
});
result += `${indent}</${tagName}>\n`;
}
}
}
return result;
};
let formatted = '';
if (xmlDoc.documentElement) {
formatted = formatXmlNode(xmlDoc.documentElement);
}
return formatted.trim();
} catch (err) {
throw new Error(`XML formatting error: ${err.message}`);
}
};
const minifyXml = (text) => {
return text.replace(/>\s+</g, '><').replace(/\s+/g, ' ').trim();
};
const beautifyCss = (text) => {
let formatted = text
.replace(/\{/g, ' {\n ')
.replace(/\}/g, '\n}\n')
.replace(/;/g, ';\n ')
.replace(/,/g, ',\n')
.replace(/\n\s*\n/g, '\n')
.trim();
// Clean up extra spaces and indentation
const lines = formatted.split('\n');
let result = '';
let indent = 0;
lines.forEach(line => {
const trimmed = line.trim();
if (trimmed) {
if (trimmed === '}') {
indent--;
}
result += ' '.repeat(Math.max(0, indent)) + trimmed + '\n';
if (trimmed.endsWith('{')) {
indent++;
}
}
});
return result.trim();
};
const minifyCss = (text) => {
return text
.replace(/\s+/g, ' ')
.replace(/;\s*}/g, '}')
.replace(/\s*{\s*/g, '{')
.replace(/;\s*/g, ';')
.replace(/,\s*/g, ',')
.trim();
};
const beautifyHtml = (text) => {
try {
// Clean input text first
let cleanText = text.trim();
if (!cleanText) {
throw new Error('Empty HTML input');
}
// Self-closing tags that don't need closing tags
const selfClosingTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
// Inline tags that should stay on same line
const inlineTags = ['a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'button', 'cite', 'code', 'dfn', 'em', 'i', 'img', 'input', 'kbd', 'label', 'map', 'object', 'q', 'samp', 'script', 'select', 'small', 'span', 'strong', 'sub', 'sup', 'textarea', 'tt', 'var'];
let formatted = '';
let indent = 0;
const tab = ' ';
// Better HTML parsing
const tokens = cleanText.match(/<\/?[^>]+>|[^<]+/g) || [];
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i].trim();
if (!token) continue;
if (token.startsWith('<')) {
// It's a tag
if (token.startsWith('</')) {
// Closing tag
indent = Math.max(0, indent - 1);
formatted += tab.repeat(indent) + token + '\n';
} else if (token.endsWith('/>')) {
// Self-closing tag
formatted += tab.repeat(indent) + token + '\n';
} else if (token.startsWith('<!')) {
// Comment or doctype
formatted += tab.repeat(indent) + token + '\n';
} else {
// Opening tag
const tagMatch = token.match(/<([^\s>]+)/);
const tagName = tagMatch ? tagMatch[1].toLowerCase() : '';
formatted += tab.repeat(indent) + token + '\n';
if (!selfClosingTags.includes(tagName)) {
indent++;
}
}
} else {
// It's text content
const textContent = token.trim();
if (textContent) {
formatted += tab.repeat(indent) + textContent + '\n';
}
}
}
return formatted.trim();
} catch (err) {
// Fallback to simple formatting if advanced parsing fails
let formatted = '';
let indent = 0;
const tab = ' ';
text = text.replace(/></g, '>\n<');
const lines = text.split('\n');
lines.forEach(line => {
const trimmed = line.trim();
if (trimmed) {
if (trimmed.startsWith('</')) {
indent = Math.max(0, indent - 1);
}
formatted += tab.repeat(indent) + trimmed + '\n';
if (trimmed.startsWith('<') && !trimmed.startsWith('</') && !trimmed.endsWith('/>') && !trimmed.includes('<!')) {
indent++;
}
}
});
return formatted.trim();
}
};
const minifyHtml = (text) => {
return text
.replace(/>\s+</g, '><')
.replace(/\s+/g, ' ')
.trim();
};
const beautifySql = (text) => {
const keywords = ['SELECT', 'FROM', 'WHERE', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'ORDER BY', 'GROUP BY', 'HAVING', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP'];
let formatted = text.toUpperCase();
keywords.forEach(keyword => {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
formatted = formatted.replace(regex, `\n${keyword}`);
});
return formatted
.split('\n')
.map(line => line.trim())
.filter(line => line)
.join('\n')
.trim();
};
const minifySql = (text) => {
return text.replace(/\s+/g, ' ').trim();
};
const handleProcess = () => {
try {
let result = '';
if (mode === 'beautify') {
switch (language) {
case 'json':
result = beautifyJson(input);
break;
case 'xml':
result = beautifyXml(input);
break;
case 'css':
result = beautifyCss(input);
break;
case 'html':
result = beautifyHtml(input);
break;
case 'sql':
result = beautifySql(input);
break;
default:
result = input;
}
} else {
switch (language) {
case 'json':
result = minifyJson(input);
break;
case 'xml':
result = minifyXml(input);
break;
case 'css':
result = minifyCss(input);
break;
case 'html':
result = minifyHtml(input);
break;
case 'sql':
result = minifySql(input);
break;
default:
result = input;
}
}
setOutput(result);
} catch (err) {
setOutput(`Error: ${err.message}`);
}
};
const clearAll = () => {
setInput('');
setOutput('');
// Force re-render to ensure clean state
setTimeout(() => {
setInput('');
setOutput('');
}, 10);
};
const loadSample = () => {
// Clear first to ensure clean state
setInput('');
setOutput('');
const samples = {
json: '{"name":"John Doe","age":30,"city":"New York","address":{"street":"123 Main St","zipCode":"10001"},"hobbies":["reading","coding","traveling"],"isActive":true}',
xml: '<?xml version="1.0" encoding="UTF-8"?><root><person id="1"><name>John Doe</name><age>30</age><email>john@example.com</email><address><street>123 Main St</street><city>New York</city><zipCode>10001</zipCode></address></person><person id="2"><name>Jane Smith</name><age>25</age><email>jane@example.com</email></person></root>',
css: 'body{margin:0;padding:0;font-family:Arial,sans-serif;}h1{color:#333;font-size:24px;margin-bottom:10px;}.container{max-width:1200px;margin:0 auto;padding:20px;}.button{background-color:#007bff;color:white;padding:10px 20px;border:none;border-radius:4px;cursor:pointer;}.button:hover{background-color:#0056b3;}',
html: '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Sample Page</title></head><body><div class="container"><header><h1>Welcome to Our Website</h1><nav><ul><li><a href="#home">Home</a></li><li><a href="#about">About</a></li><li><a href="#contact">Contact</a></li></ul></nav></header><main><section><h2>About Us</h2><p>This is a sample paragraph with <strong>bold text</strong> and <em>italic text</em>.</p><img src="image.jpg" alt="Sample Image" width="300" height="200"></section></main><footer><p>&copy; 2024 Sample Website. All rights reserved.</p></footer></div></body></html>',
sql: 'SELECT u.id, u.name, u.email, p.title, p.created_at FROM users u INNER JOIN posts p ON u.id = p.user_id WHERE u.age > 18 AND p.status = "published" ORDER BY p.created_at DESC, u.name ASC LIMIT 10;'
};
// Set sample with slight delay to ensure clean state
setTimeout(() => {
setInput(samples[language] || '');
}, 50);
};
return (
<ToolLayout
title="Code Beautifier/Minifier"
description="Format and minify JSON, XML, SQL, CSS, and HTML code"
icon={FileText}
>
{/* Language and Mode Selection */}
<div className="flex flex-wrap gap-4 mb-6">
<div className="flex items-center space-x-2">
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
Language:
</label>
<select
value={language}
onChange={(e) => setLanguage(e.target.value)}
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
>
<option value="json">JSON</option>
<option value="xml">XML</option>
<option value="css">CSS</option>
<option value="html">HTML</option>
<option value="sql">SQL</option>
</select>
</div>
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1">
<button
onClick={() => setMode('beautify')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'beautify'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Beautify
</button>
<button
onClick={() => setMode('minify')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'minify'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Minify
</button>
</div>
</div>
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={handleProcess} className="tool-button">
{mode === 'beautify' ? `Beautify ${language.toUpperCase()}` : `Minify ${language.toUpperCase()}`}
</button>
<button onClick={loadSample} className="tool-button-secondary">
Load Sample
</button>
<button onClick={clearAll} className="tool-button-secondary">
Clear All
</button>
</div>
{/* Input/Output Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Input */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{language.toUpperCase()} Input
</label>
<div className="relative">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={`Paste your ${language.toUpperCase()} code here...`}
className="tool-input h-96"
/>
</div>
</div>
{/* Output */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'beautify' ? 'Beautified' : 'Minified'} Output
</label>
<div className="relative">
<textarea
value={output}
readOnly
placeholder={`${mode === 'beautify' ? 'Beautified' : 'Minified'} code will appear here...`}
className="tool-input h-96 bg-gray-50 dark:bg-gray-800"
/>
{output && <CopyButton text={output} />}
</div>
</div>
</div>
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> Beautify adds proper indentation and formatting for readability</li>
<li> Minify removes unnecessary whitespace to reduce file size</li>
<li> Select the appropriate language for optimal formatting</li>
<li> Use beautified code for development and minified for production</li>
</ul>
</div>
</ToolLayout>
);
};
export default BeautifierTool;

276
src/pages/CsvJsonTool.js Normal file
View File

@@ -0,0 +1,276 @@
import React, { useState } from 'react';
import { RefreshCw, Upload } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
const CsvJsonTool = () => {
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [mode, setMode] = useState('csv-to-json'); // 'csv-to-json' or 'json-to-csv'
const [delimiter, setDelimiter] = useState(',');
const [hasHeaders, setHasHeaders] = useState(true);
const csvToJson = () => {
try {
const lines = input.trim().split('\n');
if (lines.length === 0) {
setOutput('Error: No data to convert');
return;
}
const headers = hasHeaders ? lines[0].split(delimiter).map(h => h.trim()) : null;
const dataLines = hasHeaders ? lines.slice(1) : lines;
const result = dataLines.map((line, index) => {
const values = line.split(delimiter).map(v => v.trim());
if (hasHeaders && headers) {
const obj = {};
headers.forEach((header, i) => {
obj[header] = values[i] || '';
});
return obj;
} else {
return values;
}
});
setOutput(JSON.stringify(result, null, 2));
} catch (err) {
setOutput(`Error: ${err.message}`);
}
};
const jsonToCsv = () => {
try {
const data = JSON.parse(input);
if (!Array.isArray(data)) {
setOutput('Error: JSON must be an array of objects');
return;
}
if (data.length === 0) {
setOutput('Error: Empty array');
return;
}
// Get headers from first object
const headers = Object.keys(data[0]);
let csv = '';
// Add headers if enabled
if (hasHeaders) {
csv += headers.join(delimiter) + '\n';
}
// Add data rows
data.forEach(row => {
const values = headers.map(header => {
const value = row[header] || '';
// Escape values containing delimiter or quotes
if (typeof value === 'string' && (value.includes(delimiter) || value.includes('"') || value.includes('\n'))) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
});
csv += values.join(delimiter) + '\n';
});
setOutput(csv.trim());
} catch (err) {
setOutput(`Error: ${err.message}`);
}
};
const handleProcess = () => {
if (mode === 'csv-to-json') {
csvToJson();
} else {
jsonToCsv();
}
};
const handleFileUpload = (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
setInput(e.target.result);
};
reader.readAsText(file);
}
};
const clearAll = () => {
setInput('');
setOutput('');
};
const loadSample = () => {
if (mode === 'csv-to-json') {
setInput(`name,age,email,city
John Doe,30,john@example.com,New York
Jane Smith,25,jane@example.com,Los Angeles
Bob Johnson,35,bob@example.com,Chicago`);
} else {
setInput(`[
{
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"city": "New York"
},
{
"name": "Jane Smith",
"age": 25,
"email": "jane@example.com",
"city": "Los Angeles"
}
]`);
}
};
return (
<ToolLayout
title="CSV ↔ JSON Converter"
description="Convert between CSV and JSON formats with custom delimiters"
icon={RefreshCw}
>
{/* Mode Toggle */}
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
<button
onClick={() => setMode('csv-to-json')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'csv-to-json'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
CSV JSON
</button>
<button
onClick={() => setMode('json-to-csv')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'json-to-csv'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
JSON CSV
</button>
</div>
{/* Options */}
<div className="flex flex-wrap items-center gap-4 mb-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex items-center space-x-2">
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
Delimiter:
</label>
<select
value={delimiter}
onChange={(e) => setDelimiter(e.target.value)}
className="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 text-sm"
>
<option value=",">Comma (,)</option>
<option value=";">Semicolon (;)</option>
<option value="\t">Tab</option>
<option value="|">Pipe (|)</option>
</select>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="hasHeaders"
checked={hasHeaders}
onChange={(e) => setHasHeaders(e.target.checked)}
className="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<label htmlFor="hasHeaders" className="text-sm font-medium text-gray-700 dark:text-gray-300">
First row contains headers
</label>
</div>
</div>
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={handleProcess} className="tool-button">
{mode === 'csv-to-json' ? 'Convert to JSON' : 'Convert to CSV'}
</button>
<label className="tool-button-secondary cursor-pointer flex items-center space-x-2">
<Upload className="h-4 w-4" />
<span>Upload File</span>
<input
type="file"
onChange={handleFileUpload}
className="hidden"
accept=".csv,.json,.txt"
/>
</label>
<button onClick={loadSample} className="tool-button-secondary">
Load Sample
</button>
<button onClick={clearAll} className="tool-button-secondary">
Clear All
</button>
</div>
{/* Input/Output Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Input */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'csv-to-json' ? 'CSV Input' : 'JSON Input'}
</label>
<div className="relative">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={
mode === 'csv-to-json'
? 'Paste your CSV data here...'
: 'Paste your JSON array here...'
}
className="tool-input h-96"
/>
</div>
</div>
{/* Output */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'csv-to-json' ? 'JSON Output' : 'CSV Output'}
</label>
<div className="relative">
<textarea
value={output}
readOnly
placeholder={
mode === 'csv-to-json'
? 'JSON output will appear here...'
: 'CSV output will appear here...'
}
className="tool-input h-96 bg-gray-50 dark:bg-gray-800"
/>
{output && <CopyButton text={output} />}
</div>
</div>
</div>
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> CSV to JSON converts each row to an object with column headers as keys</li>
<li> JSON to CSV requires an array of objects with consistent properties</li>
<li> Choose the appropriate delimiter for your CSV format</li>
<li> Toggle "First row contains headers" based on your data structure</li>
</ul>
</div>
</ToolLayout>
);
};
export default CsvJsonTool;

280
src/pages/DiffTool.js Normal file
View File

@@ -0,0 +1,280 @@
import React, { useState } from 'react';
import { GitCompare, Upload } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
const DiffTool = () => {
const [leftText, setLeftText] = useState('');
const [rightText, setRightText] = useState('');
const [diffResult, setDiffResult] = useState('');
const [diffMode, setDiffMode] = useState('unified'); // 'unified' or 'side-by-side'
// Simple diff implementation
const computeDiff = () => {
const leftLines = leftText.split('\n');
const rightLines = rightText.split('\n');
const maxLines = Math.max(leftLines.length, rightLines.length);
let result = '';
let diffCount = 0;
if (diffMode === 'unified') {
result += `--- Text A\n+++ Text B\n`;
for (let i = 0; i < maxLines; i++) {
const leftLine = leftLines[i] || '';
const rightLine = rightLines[i] || '';
if (leftLine !== rightLine) {
diffCount++;
if (leftLine && !rightLine) {
result += `- ${leftLine}\n`;
} else if (!leftLine && rightLine) {
result += `+ ${rightLine}\n`;
} else {
result += `- ${leftLine}\n+ ${rightLine}\n`;
}
} else {
result += ` ${leftLine}\n`;
}
}
} else {
// Side by side format
result += `${'Text A'.padEnd(50)} | Text B\n`;
result += `${'-'.repeat(50)} | ${'-'.repeat(50)}\n`;
for (let i = 0; i < maxLines; i++) {
const leftLine = leftLines[i] || '';
const rightLine = rightLines[i] || '';
if (leftLine !== rightLine) {
diffCount++;
const leftDisplay = leftLine.padEnd(50);
result += `${leftDisplay} | ${rightLine}\n`;
} else {
const leftDisplay = leftLine.padEnd(50);
result += `${leftDisplay} | ${rightLine}\n`;
}
}
}
if (diffCount === 0) {
result = '✅ No differences found - texts are identical!';
} else {
result = `Found ${diffCount} difference(s):\n\n${result}`;
}
setDiffResult(result);
};
const handleFileUpload = (side, event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
if (side === 'left') {
setLeftText(e.target.result);
} else {
setRightText(e.target.result);
}
};
reader.readAsText(file);
}
};
const clearAll = () => {
setLeftText('');
setRightText('');
setDiffResult('');
};
const loadSample = () => {
setLeftText(`function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price;
}
return total;
}
const user = {
name: "John Doe",
age: 30,
email: "john@example.com"
};`);
setRightText(`function calculateTotal(items) {
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
return total;
}
const user = {
name: "John Smith",
age: 30,
email: "john.smith@example.com",
active: true
};`);
};
const swapTexts = () => {
const temp = leftText;
setLeftText(rightText);
setRightText(temp);
};
return (
<ToolLayout
title="Text Diff Checker"
description="Compare two texts and highlight differences line by line"
icon={GitCompare}
>
{/* Mode Toggle */}
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
<button
onClick={() => setDiffMode('unified')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
diffMode === 'unified'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Unified Diff
</button>
<button
onClick={() => setDiffMode('side-by-side')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
diffMode === 'side-by-side'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Side by Side
</button>
</div>
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={computeDiff} className="tool-button">
Compare Texts
</button>
<button onClick={swapTexts} className="tool-button-secondary">
Swap A B
</button>
<button onClick={loadSample} className="tool-button-secondary">
Load Sample
</button>
<button onClick={clearAll} className="tool-button-secondary">
Clear All
</button>
</div>
{/* Input Texts Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
{/* Left Text */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Text A (Original)
</label>
<label className="tool-button-secondary cursor-pointer flex items-center space-x-2 text-xs px-2 py-1">
<Upload className="h-3 w-3" />
<span>Upload</span>
<input
type="file"
onChange={(e) => handleFileUpload('left', e)}
className="hidden"
accept=".txt,.js,.json,.xml,.csv,.html,.css,.md"
/>
</label>
</div>
<div className="relative">
<textarea
value={leftText}
onChange={(e) => setLeftText(e.target.value)}
placeholder="Paste your first text here..."
className="tool-input h-64"
/>
</div>
</div>
{/* Right Text */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Text B (Modified)
</label>
<label className="tool-button-secondary cursor-pointer flex items-center space-x-2 text-xs px-2 py-1">
<Upload className="h-3 w-3" />
<span>Upload</span>
<input
type="file"
onChange={(e) => handleFileUpload('right', e)}
className="hidden"
accept=".txt,.js,.json,.xml,.csv,.html,.css,.md"
/>
</label>
</div>
<div className="relative">
<textarea
value={rightText}
onChange={(e) => setRightText(e.target.value)}
placeholder="Paste your second text here..."
className="tool-input h-64"
/>
</div>
</div>
</div>
{/* Diff Result */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Comparison Result
</label>
<div className="relative">
<textarea
value={diffResult}
readOnly
placeholder="Comparison result will appear here..."
className="tool-input h-96 bg-gray-50 dark:bg-gray-800"
/>
{diffResult && <CopyButton text={diffResult} />}
</div>
</div>
{/* Legend */}
<div className="bg-gray-50 dark:bg-gray-800 rounded-md p-4 mt-6">
<h4 className="text-gray-800 dark:text-gray-200 font-medium mb-3">Diff Legend</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
<div className="flex items-center space-x-2">
<span className="font-mono text-red-600">- line</span>
<span className="text-gray-600 dark:text-gray-400">Removed from Text A</span>
</div>
<div className="flex items-center space-x-2">
<span className="font-mono text-green-600">+ line</span>
<span className="text-gray-600 dark:text-gray-400">Added in Text B</span>
</div>
<div className="flex items-center space-x-2">
<span className="font-mono text-gray-600"> line</span>
<span className="text-gray-600 dark:text-gray-400">Unchanged</span>
</div>
</div>
</div>
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> Upload files or paste text directly to compare</li>
<li> Use "Unified Diff" for traditional diff format with +/- indicators</li>
<li> Use "Side by Side" for parallel comparison view</li>
<li> Perfect for comparing code versions, configurations, or any text files</li>
</ul>
</div>
</ToolLayout>
);
};
export default DiffTool;

155
src/pages/Home.js Normal file
View File

@@ -0,0 +1,155 @@
import React, { useState } from 'react';
import { Search, Code, Link2, FileText, Hash, RefreshCw, GitCompare, Database } from 'lucide-react';
import ToolCard from '../components/ToolCard';
const Home = () => {
const [searchTerm, setSearchTerm] = useState('');
const tools = [
{
icon: Code,
title: 'JSON Encoder/Decoder',
description: 'Format, validate, and minify JSON data with syntax highlighting',
path: '/json',
tags: ['JSON', 'Format', 'Validate']
},
{
icon: Database,
title: 'Serialize Encoder/Decoder',
description: 'Encode and decode serialized data (PHP serialize format)',
path: '/serialize',
tags: ['PHP', 'Serialize', 'Unserialize']
},
{
icon: Link2,
title: 'URL Encoder/Decoder',
description: 'Encode and decode URLs and query parameters',
path: '/url',
tags: ['URL', 'Encode', 'Decode']
},
{
icon: Hash,
title: 'Base64 Encoder/Decoder',
description: 'Convert text to Base64 and back with support for files',
path: '/base64',
tags: ['Base64', 'Encode', 'Binary']
},
{
icon: RefreshCw,
title: 'CSV ↔ JSON Converter',
description: 'Convert between CSV and JSON formats with custom delimiters',
path: '/csv-json',
tags: ['CSV', 'JSON', 'Convert']
},
{
icon: FileText,
title: 'Code Beautifier/Minifier',
description: 'Format and minify JSON, XML, SQL, CSS, and HTML code',
path: '/beautifier',
tags: ['Format', 'Minify', 'Beautify']
},
{
icon: GitCompare,
title: 'Text Diff Checker',
description: 'Compare two texts and highlight differences line by line',
path: '/diff',
tags: ['Diff', 'Compare', 'Text']
}
];
const filteredTools = tools.filter(tool =>
tool.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
tool.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
tool.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()))
);
return (
<div className="max-w-6xl mx-auto">
{/* Hero Section */}
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
Developer Tools
</h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8">
Essential utilities for web developers - fast, local, and easy to use
</p>
{/* Search */}
<div className="relative max-w-md mx-auto">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400" />
<input
type="text"
placeholder="Search tools..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-primary-500 focus:border-transparent"
/>
</div>
</div>
{/* Tools Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredTools.map((tool, index) => (
<ToolCard
key={index}
icon={tool.icon}
title={tool.title}
description={tool.description}
path={tool.path}
tags={tool.tags}
/>
))}
</div>
{/* No Results */}
{filteredTools.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500 dark:text-gray-400 text-lg">
No tools found matching "{searchTerm}"
</p>
</div>
)}
{/* Features */}
<div className="mt-16 grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="text-center">
<div className="bg-primary-100 dark:bg-primary-900 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
<RefreshCw className="h-8 w-8 text-primary-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Lightning Fast
</h3>
<p className="text-gray-600 dark:text-gray-300">
All processing happens locally in your browser for maximum speed and privacy
</p>
</div>
<div className="text-center">
<div className="bg-primary-100 dark:bg-primary-900 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
<FileText className="h-8 w-8 text-primary-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Handle Large Files
</h3>
<p className="text-gray-600 dark:text-gray-300">
Process large text files and data with ease, no size limitations
</p>
</div>
<div className="text-center">
<div className="bg-primary-100 dark:bg-primary-900 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
<Code className="h-8 w-8 text-primary-600" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Developer Friendly
</h3>
<p className="text-gray-600 dark:text-gray-300">
Clean interface with syntax highlighting and easy copy-paste functionality
</p>
</div>
</div>
</div>
);
};
export default Home;

247
src/pages/JsonTool.js Normal file
View File

@@ -0,0 +1,247 @@
import React, { useState } from 'react';
import { Code, AlertCircle, CheckCircle, Edit3 } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
import StructuredEditor from '../components/StructuredEditor';
const JsonTool = () => {
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [error, setError] = useState('');
const [isValid, setIsValid] = useState(null);
const [editorMode, setEditorMode] = useState('text'); // 'text' or 'visual'
const [structuredData, setStructuredData] = useState({});
const formatJson = () => {
try {
const parsed = JSON.parse(input);
const formatted = JSON.stringify(parsed, null, 2);
setOutput(formatted);
setError('');
setIsValid(true);
} catch (err) {
setError(`Invalid JSON: ${err.message}`);
setOutput('');
setIsValid(false);
}
};
const minifyJson = () => {
try {
const parsed = JSON.parse(input);
const minified = JSON.stringify(parsed);
setOutput(minified);
setError('');
setIsValid(true);
} catch (err) {
setError(`Invalid JSON: ${err.message}`);
setOutput('');
setIsValid(false);
}
};
const validateJson = () => {
try {
JSON.parse(input);
setError('');
setIsValid(true);
setOutput('✅ Valid JSON');
} catch (err) {
setError(`Invalid JSON: ${err.message}`);
setIsValid(false);
setOutput('');
}
};
const clearAll = () => {
setInput('');
setOutput('');
setError('');
setIsValid(null);
};
const handleStructuredDataChange = (newData) => {
setStructuredData(newData);
setInput(JSON.stringify(newData, null, 2));
setError('');
setIsValid(true);
};
const switchToVisualEditor = () => {
try {
const parsed = input ? JSON.parse(input) : {};
setStructuredData(parsed);
setEditorMode('visual');
setError('');
setIsValid(true);
} catch (err) {
setError(`Cannot switch to visual editor: ${err.message}`);
setIsValid(false);
}
};
const switchToTextEditor = () => {
setEditorMode('text');
};
const loadSample = () => {
const sample = {
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"address": {
"street": "123 Main St",
"city": "New York",
"zipCode": "10001"
},
"hobbies": ["reading", "coding", "traveling"],
"isActive": true
};
setInput(JSON.stringify(sample, null, 2));
setStructuredData(sample);
};
return (
<ToolLayout
title="JSON Encoder/Decoder"
description="Format, validate, and minify JSON data with syntax highlighting"
icon={Code}
>
{/* Editor Mode Toggle */}
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
<button
onClick={switchToTextEditor}
className={`flex items-center space-x-2 px-4 py-2 rounded-md font-medium transition-colors ${
editorMode === 'text'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
<Code className="h-4 w-4" />
<span>Text Editor</span>
</button>
<button
onClick={switchToVisualEditor}
className={`flex items-center space-x-2 px-4 py-2 rounded-md font-medium transition-colors ${
editorMode === 'visual'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
<Edit3 className="h-4 w-4" />
<span>Visual Editor</span>
</button>
</div>
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={formatJson} className="tool-button">
Format JSON
</button>
<button onClick={minifyJson} className="tool-button">
Minify JSON
</button>
<button onClick={validateJson} className="tool-button">
Validate JSON
</button>
<button onClick={loadSample} className="tool-button-secondary">
Load Sample
</button>
<button onClick={clearAll} className="tool-button-secondary">
Clear All
</button>
</div>
{/* Status Indicator */}
{isValid !== null && (
<div className={`flex items-center space-x-2 p-3 rounded-md mb-4 ${
isValid
? 'bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300'
: 'bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300'
}`}>
{isValid ? (
<CheckCircle className="h-5 w-5" />
) : (
<AlertCircle className="h-5 w-5" />
)}
<span className="font-medium">
{isValid ? 'Valid JSON' : 'Invalid JSON'}
</span>
</div>
)}
{/* Error Display */}
{error && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md p-4 mb-4">
<div className="flex items-start space-x-2">
<AlertCircle className="h-5 w-5 text-red-500 mt-0.5" />
<div>
<h4 className="text-red-800 dark:text-red-200 font-medium">Error</h4>
<p className="text-red-700 dark:text-red-300 text-sm mt-1">{error}</p>
</div>
</div>
</div>
)}
{/* Input/Output Grid */}
<div className={`grid gap-6 ${
editorMode === 'visual'
? 'grid-cols-1'
: 'grid-cols-1 lg:grid-cols-2'
}`}>
{/* Input */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{editorMode === 'text' ? 'Input JSON' : 'Visual JSON Editor'}
</label>
<div className="relative">
{editorMode === 'text' ? (
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Paste your JSON here..."
className="tool-input h-96"
/>
) : (
<div className="min-h-96">
<StructuredEditor
initialData={structuredData}
onDataChange={handleStructuredDataChange}
/>
</div>
)}
</div>
</div>
{/* Output */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Output
</label>
<div className="relative">
<textarea
value={output}
readOnly
placeholder="Formatted JSON will appear here..."
className="tool-input h-96 bg-gray-50 dark:bg-gray-800"
/>
{output && <CopyButton text={output} />}
</div>
</div>
</div>
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> Use "Format JSON" to beautify and indent your JSON</li>
<li> Use "Minify JSON" to compress JSON by removing whitespace</li>
<li> Use "Validate JSON" to check if your JSON syntax is correct</li>
<li> Click the copy button to copy the output to your clipboard</li>
</ul>
</div>
</ToolLayout>
);
};
export default JsonTool;

390
src/pages/SerializeTool.js Normal file
View File

@@ -0,0 +1,390 @@
import React, { useState } from 'react';
import { Database, Edit3 } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
import StructuredEditor from '../components/StructuredEditor';
const SerializeTool = () => {
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [mode, setMode] = useState('serialize'); // 'serialize' or 'unserialize'
const [error, setError] = useState('');
const [editorMode, setEditorMode] = useState('text'); // 'text' or 'visual'
const [structuredData, setStructuredData] = useState({});
// Simple PHP serialize implementation for common data types
const phpSerialize = (data) => {
if (data === null) return 'N;';
if (typeof data === 'boolean') return data ? 'b:1;' : 'b:0;';
if (typeof data === 'number') {
return Number.isInteger(data) ? `i:${data};` : `d:${data};`;
}
if (typeof data === 'string') {
return `s:${data.length}:"${data}";`;
}
if (Array.isArray(data)) {
let result = `a:${data.length}:{`;
data.forEach((item, index) => {
result += phpSerialize(index) + phpSerialize(item);
});
result += '}';
return result;
}
if (typeof data === 'object') {
const keys = Object.keys(data);
let result = `a:${keys.length}:{`;
keys.forEach(key => {
result += phpSerialize(key) + phpSerialize(data[key]);
});
result += '}';
return result;
}
return 'N;';
};
// Simple PHP unserialize implementation
const phpUnserialize = (str) => {
let index = 0;
const parseValue = () => {
const type = str[index];
index += 2; // Skip type and ':'
switch (type) {
case 'N':
index++; // Skip ';'
return null;
case 'b':
const boolVal = str[index] === '1';
index += 2; // Skip value and ';'
return boolVal;
case 'i':
let intStr = '';
while (str[index] !== ';') {
intStr += str[index++];
}
index++; // Skip ';'
return parseInt(intStr);
case 'd':
let floatStr = '';
while (str[index] !== ';') {
floatStr += str[index++];
}
index++; // Skip ';'
return parseFloat(floatStr);
case 's':
let lenStr = '';
while (str[index] !== ':') {
lenStr += str[index++];
}
index++; // Skip ':'
index++; // Skip '"'
const length = parseInt(lenStr);
const stringVal = str.substr(index, length);
index += length + 2; // Skip string and '";'
return stringVal;
case 'a':
let arrayLenStr = '';
while (str[index] !== ':') {
arrayLenStr += str[index++];
}
index += 2; // Skip ':{'
const arrayLength = parseInt(arrayLenStr);
const result = {};
let isArray = true;
for (let i = 0; i < arrayLength; i++) {
const key = parseValue();
const value = parseValue();
result[key] = value;
if (typeof key !== 'number' || key !== i) {
isArray = false;
}
}
index++; // Skip '}'
// Convert to array if all keys are sequential integers
if (isArray && arrayLength > 0) {
const arr = [];
for (let i = 0; i < arrayLength; i++) {
arr[i] = result[i];
}
return arr;
}
return result;
default:
throw new Error(`Unknown type: ${type}`);
}
};
return parseValue();
};
const handleSerialize = () => {
try {
const data = JSON.parse(input);
const serialized = phpSerialize(data);
setOutput(serialized);
} catch (err) {
setOutput(`Error: ${err.message}`);
}
};
const handleUnserialize = () => {
try {
const unserialized = phpUnserialize(input);
setOutput(JSON.stringify(unserialized, null, 2));
} catch (err) {
setOutput(`Error: ${err.message}`);
}
};
const handleProcess = () => {
if (mode === 'serialize') {
handleSerialize();
} else {
handleUnserialize();
}
};
// Editor mode switching functions
const switchToTextEditor = () => {
if (editorMode === 'visual') {
try {
const jsonString = JSON.stringify(structuredData, null, 2);
setInput(jsonString);
} catch (err) {
setError('Error converting structured data to JSON');
}
}
setEditorMode('text');
};
const switchToVisualEditor = () => {
if (editorMode === 'text' && input.trim()) {
try {
const parsed = JSON.parse(input);
setStructuredData(parsed);
setError('');
} catch (err) {
setError('Invalid JSON format. Please fix the JSON before switching to visual editor.');
return;
}
}
setEditorMode('visual');
};
const handleStructuredDataChange = (newData) => {
setStructuredData(newData);
try {
const jsonString = JSON.stringify(newData, null, 2);
setInput(jsonString);
setError('');
} catch (err) {
setError('Error updating JSON from structured data');
}
};
const clearAll = () => {
setInput('');
setOutput('');
};
const loadSample = () => {
if (mode === 'serialize') {
setInput(`{
"name": "John Doe",
"age": 30,
"active": true,
"scores": [85, 92, 78],
"address": {
"street": "123 Main St",
"city": "New York"
}
}`);
} else {
setInput('a:4:{s:4:"name";s:8:"John Doe";s:3:"age";i:30;s:6:"active";b:1;s:6:"scores";a:3:{i:0;i:85;i:1;i:92;i:2;i:78;}}');
}
};
return (
<ToolLayout
title="Serialize Encoder/Decoder"
description="Encode and decode serialized data (PHP serialize format)"
icon={Database}
>
{/* Mode Toggle */}
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
<button
onClick={() => setMode('serialize')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'serialize'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Serialize
</button>
<button
onClick={() => setMode('unserialize')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'unserialize'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Unserialize
</button>
</div>
{/* Editor Mode Toggle - only show in serialize mode */}
{mode === 'serialize' && (
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
<button
onClick={switchToTextEditor}
className={`flex items-center space-x-2 px-4 py-2 rounded-md font-medium transition-colors ${
editorMode === 'text'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
<Database className="h-4 w-4" />
<span>Text Editor</span>
</button>
<button
onClick={switchToVisualEditor}
className={`flex items-center space-x-2 px-4 py-2 rounded-md font-medium transition-colors ${
editorMode === 'visual'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
<Edit3 className="h-4 w-4" />
<span>Visual Editor</span>
</button>
</div>
)}
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={handleProcess} className="tool-button">
{mode === 'serialize' ? 'Serialize Data' : 'Unserialize Data'}
</button>
<button onClick={loadSample} className="tool-button-secondary">
Load Sample
</button>
<button onClick={clearAll} className="tool-button-secondary">
Clear All
</button>
</div>
{/* Input/Output Grid */}
<div className={`grid gap-6 ${
mode === 'serialize' && editorMode === 'visual'
? 'grid-cols-1'
: 'grid-cols-1 lg:grid-cols-2'
}`}>
{/* Input */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'serialize'
? (editorMode === 'text' ? 'JSON to Serialize' : 'Visual Data Editor')
: 'Serialized Data to Decode'
}
</label>
<div className="relative">
{mode === 'serialize' && editorMode === 'visual' ? (
<div className="min-h-96">
<StructuredEditor
initialData={structuredData}
onDataChange={handleStructuredDataChange}
/>
</div>
) : (
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={
mode === 'serialize'
? 'Enter JSON data to serialize...'
: 'Enter serialized data to decode...'
}
className="tool-input h-96"
/>
)}
</div>
{error && (
<p className="text-sm text-red-600 dark:text-red-400 mt-2">
{error}
</p>
)}
</div>
{/* Output */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'serialize' ? 'Serialized Output' : 'JSON Output'}
</label>
<div className="relative">
<textarea
value={output}
readOnly
placeholder={
mode === 'serialize'
? 'Serialized data will appear here...'
: 'Decoded JSON will appear here...'
}
className="tool-input h-96 bg-gray-50 dark:bg-gray-800"
/>
{output && <CopyButton text={output} />}
</div>
</div>
</div>
{/* Serialize Format Reference */}
<div className="bg-gray-50 dark:bg-gray-800 rounded-md p-4 mt-6">
<h4 className="text-gray-800 dark:text-gray-200 font-medium mb-3">PHP Serialize Format Reference</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-600 dark:text-gray-400">String:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">s:length:"value";</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">Integer:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">i:value;</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">Boolean:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">b:0; or b:1;</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">Null:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">N;</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">Array:</span>
<span className="ml-2 font-mono">a:length:&#123;...&#125;</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">Float:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">d:value;</span>
</div>
</div>
</div>
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> PHP serialize format is commonly used for storing complex data structures</li>
<li> Input JSON data to serialize it into PHP format</li>
<li> Paste serialized data to convert it back to readable JSON</li>
<li> Supports strings, integers, floats, booleans, arrays, and objects</li>
</ul>
</div>
</ToolLayout>
);
};
export default SerializeTool;

188
src/pages/UrlTool.js Normal file
View File

@@ -0,0 +1,188 @@
import React, { useState } from 'react';
import { Link2 } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
const UrlTool = () => {
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [mode, setMode] = useState('encode'); // 'encode' or 'decode'
const encodeUrl = () => {
try {
const encoded = encodeURIComponent(input);
setOutput(encoded);
} catch (err) {
setOutput(`Error: ${err.message}`);
}
};
const decodeUrl = () => {
try {
const decoded = decodeURIComponent(input);
setOutput(decoded);
} catch (err) {
setOutput(`Error: Invalid URL encoding`);
}
};
const handleProcess = () => {
if (mode === 'encode') {
encodeUrl();
} else {
decodeUrl();
}
};
const clearAll = () => {
setInput('');
setOutput('');
};
const loadSample = () => {
if (mode === 'encode') {
setInput('https://example.com/search?q=hello world&category=web development&sort=date');
} else {
setInput('https%3A//example.com/search%3Fq%3Dhello%20world%26category%3Dweb%20development%26sort%3Ddate');
}
};
return (
<ToolLayout
title="URL Encoder/Decoder"
description="Encode and decode URLs and query parameters"
icon={Link2}
>
{/* Mode Toggle */}
<div className="flex bg-gray-100 dark:bg-gray-800 rounded-lg p-1 mb-6 w-fit">
<button
onClick={() => setMode('encode')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'encode'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Encode
</button>
<button
onClick={() => setMode('decode')}
className={`px-4 py-2 rounded-md font-medium transition-colors ${
mode === 'decode'
? 'bg-white dark:bg-gray-700 text-primary-600 shadow-sm'
: 'text-gray-600 dark:text-gray-400'
}`}
>
Decode
</button>
</div>
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={handleProcess} className="tool-button">
{mode === 'encode' ? 'Encode URL' : 'Decode URL'}
</button>
<button onClick={loadSample} className="tool-button-secondary">
Load Sample
</button>
<button onClick={clearAll} className="tool-button-secondary">
Clear All
</button>
</div>
{/* Input/Output Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Input */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'encode' ? 'URL to Encode' : 'Encoded URL to Decode'}
</label>
<div className="relative">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={
mode === 'encode'
? 'Enter URL or text to encode...'
: 'Enter encoded URL to decode...'
}
className="tool-input h-96"
/>
</div>
</div>
{/* Output */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{mode === 'encode' ? 'Encoded URL' : 'Decoded URL'}
</label>
<div className="relative">
<textarea
value={output}
readOnly
placeholder={
mode === 'encode'
? 'Encoded URL will appear here...'
: 'Decoded URL will appear here...'
}
className="tool-input h-96 bg-gray-50 dark:bg-gray-800"
/>
{output && <CopyButton text={output} />}
</div>
</div>
</div>
{/* Common URL Characters Reference */}
<div className="bg-gray-50 dark:bg-gray-800 rounded-md p-4 mt-6">
<h4 className="text-gray-800 dark:text-gray-200 font-medium mb-3">Common URL Encoding Reference</h4>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<span className="text-gray-600 dark:text-gray-400">Space:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">%20</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">!:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">%21</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">#:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">%23</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">$:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">%24</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">&:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">%26</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">':</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">%27</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">(:</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">%28</span>
</div>
<div>
<span className="text-gray-600 dark:text-gray-400">):</span>
<span className="ml-2 font-mono text-gray-800 dark:text-gray-200">%29</span>
</div>
</div>
</div>
{/* Usage Tips */}
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> URL encoding is essential for passing special characters in URLs</li>
<li> Spaces become %20, ampersands become %26, etc.</li>
<li> Use encoding when building query parameters with special characters</li>
<li> Decoding helps you read encoded URLs and parameters</li>
</ul>
</div>
</ToolLayout>
);
};
export default UrlTool;