mirror of
https://github.com/danielvici/tool-website.git
synced 2026-01-16 18:31:26 +00:00
Added favicon and logo to website
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
export default function About() {
|
||||
return (
|
||||
<main className="min-h-screen p-8">
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">About</h1>
|
||||
|
||||
@@ -13,13 +13,9 @@ export default function About() {
|
||||
The Source Code is available on GitHub (link below).
|
||||
</p>
|
||||
|
||||
<h2 className="text-2xl font-bold text-white mt-6 mb-4">Features</h2>
|
||||
<ul className="list-disc pl-6 text-gray-300 space-y-2">
|
||||
<li>Password Generator - Create secure passwords with customizable options</li>
|
||||
<li>Image Converter - Convert images between different formats</li>
|
||||
<li>Website Bookmarks - Save and organize your favorite websites</li>
|
||||
<li>More tools coming soon!</li>
|
||||
</ul>
|
||||
<div className="bg-zinc-900 rounded-lg p-4 mb-6">
|
||||
<p className="text-sm text-blue-400 font-mono">Current Version: 0.2.0</p>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold text-white mt-6 mb-4">Privacy</h2>
|
||||
<p className="text-gray-300 mb-6">
|
||||
|
||||
@@ -34,6 +34,15 @@ export async function POST(req: NextRequest) {
|
||||
case 'gif':
|
||||
convertedBuffer = await sharpInstance.gif().toBuffer()
|
||||
break
|
||||
case 'ico':
|
||||
convertedBuffer = await sharpInstance
|
||||
.resize(256, 256, {
|
||||
fit: 'contain',
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||
})
|
||||
.png()
|
||||
.toBuffer()
|
||||
break
|
||||
default:
|
||||
throw new Error('Unsupported format')
|
||||
}
|
||||
|
||||
186
app/calculator/scientific/page.tsx
Normal file
186
app/calculator/scientific/page.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function ScientificCalculator() {
|
||||
const [display, setDisplay] = useState('0')
|
||||
const [memory, setMemory] = useState<number>(0)
|
||||
const [lastOperation, setLastOperation] = useState<string | null>(null)
|
||||
const [newNumber, setNewNumber] = useState(true)
|
||||
|
||||
const operations = {
|
||||
'+': (a: number, b: number) => a + b,
|
||||
'-': (a: number, b: number) => a - b,
|
||||
'×': (a: number, b: number) => a * b,
|
||||
'÷': (a: number, b: number) => a / b,
|
||||
'xⁿ': (a: number, b: number) => Math.pow(a, b),
|
||||
'ⁿ√x': (a: number, b: number) => Math.pow(a, 1/b),
|
||||
}
|
||||
|
||||
const handleNumber = (num: string) => {
|
||||
if (newNumber) {
|
||||
setDisplay(num)
|
||||
setNewNumber(false)
|
||||
} else {
|
||||
setDisplay(display === '0' ? num : display + num)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDecimal = () => {
|
||||
if (newNumber) {
|
||||
setDisplay('0.')
|
||||
setNewNumber(false)
|
||||
} else if (!display.includes('.')) {
|
||||
setDisplay(display + '.')
|
||||
}
|
||||
}
|
||||
|
||||
const handleOperation = (op: string) => {
|
||||
setMemory(parseFloat(display))
|
||||
setLastOperation(op)
|
||||
setNewNumber(true)
|
||||
}
|
||||
|
||||
const handleEquals = () => {
|
||||
if (lastOperation && !newNumber) {
|
||||
const current = parseFloat(display)
|
||||
const operation = operations[lastOperation as keyof typeof operations]
|
||||
const result = operation(memory, current)
|
||||
setDisplay(result.toString())
|
||||
setNewNumber(true)
|
||||
setLastOperation(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleFunction = (func: string) => {
|
||||
const num = parseFloat(display)
|
||||
let result: number
|
||||
|
||||
switch (func) {
|
||||
case 'sin':
|
||||
result = Math.sin(num * Math.PI / 180)
|
||||
break
|
||||
case 'cos':
|
||||
result = Math.cos(num * Math.PI / 180)
|
||||
break
|
||||
case 'tan':
|
||||
result = Math.tan(num * Math.PI / 180)
|
||||
break
|
||||
case 'log':
|
||||
result = Math.log10(num)
|
||||
break
|
||||
case 'ln':
|
||||
result = Math.log(num)
|
||||
break
|
||||
case '√':
|
||||
result = Math.sqrt(num)
|
||||
break
|
||||
case 'x²':
|
||||
result = num * num
|
||||
break
|
||||
case '1/x':
|
||||
result = 1 / num
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
setDisplay(result.toString())
|
||||
setNewNumber(true)
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
setDisplay('0')
|
||||
setMemory(0)
|
||||
setLastOperation(null)
|
||||
setNewNumber(true)
|
||||
}
|
||||
|
||||
const handleBackspace = () => {
|
||||
if (display.length > 1) {
|
||||
setDisplay(display.slice(0, -1))
|
||||
} else {
|
||||
setDisplay('0')
|
||||
setNewNumber(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePlusMinus = () => {
|
||||
setDisplay(display.startsWith('-') ? display.slice(1) : '-' + display)
|
||||
}
|
||||
|
||||
const Button = ({ children, onClick, className = '' }: { children: React.ReactNode, onClick: () => void, className?: string }) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`p-3 text-white rounded-md transition-colors ${className}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Scientific Calculator</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<div className="prose prose-invert max-w-none mb-6">
|
||||
<p className="text-gray-300">
|
||||
A scientific calculator with advanced mathematical functions including trigonometry,
|
||||
logarithms, and exponential operations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="bg-zinc-900 p-4 rounded-lg">
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={display}
|
||||
className="w-full bg-transparent text-right text-2xl text-white font-mono outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{/* Scientific Functions */}
|
||||
<Button onClick={() => handleFunction('sin')} className="bg-zinc-700 hover:bg-zinc-600">sin</Button>
|
||||
<Button onClick={() => handleFunction('cos')} className="bg-zinc-700 hover:bg-zinc-600">cos</Button>
|
||||
<Button onClick={() => handleFunction('tan')} className="bg-zinc-700 hover:bg-zinc-600">tan</Button>
|
||||
<Button onClick={handleClear} className="bg-red-600 hover:bg-red-700">C</Button>
|
||||
|
||||
<Button onClick={() => handleFunction('log')} className="bg-zinc-700 hover:bg-zinc-600">log</Button>
|
||||
<Button onClick={() => handleFunction('ln')} className="bg-zinc-700 hover:bg-zinc-600">ln</Button>
|
||||
<Button onClick={() => handleFunction('√')} className="bg-zinc-700 hover:bg-zinc-600">√</Button>
|
||||
<Button onClick={handleBackspace} className="bg-zinc-700 hover:bg-zinc-600">⌫</Button>
|
||||
|
||||
<Button onClick={() => handleFunction('x²')} className="bg-zinc-700 hover:bg-zinc-600">x²</Button>
|
||||
<Button onClick={() => handleOperation('xⁿ')} className="bg-zinc-700 hover:bg-zinc-600">xⁿ</Button>
|
||||
<Button onClick={() => handleOperation('ⁿ√x')} className="bg-zinc-700 hover:bg-zinc-600">ⁿ√x</Button>
|
||||
<Button onClick={() => handleOperation('÷')} className="bg-blue-600 hover:bg-blue-700">÷</Button>
|
||||
|
||||
{/* Numbers and Basic Operations */}
|
||||
<Button onClick={() => handleNumber('7')} className="bg-zinc-600 hover:bg-zinc-500">7</Button>
|
||||
<Button onClick={() => handleNumber('8')} className="bg-zinc-600 hover:bg-zinc-500">8</Button>
|
||||
<Button onClick={() => handleNumber('9')} className="bg-zinc-600 hover:bg-zinc-500">9</Button>
|
||||
<Button onClick={() => handleOperation('×')} className="bg-blue-600 hover:bg-blue-700">×</Button>
|
||||
|
||||
<Button onClick={() => handleNumber('4')} className="bg-zinc-600 hover:bg-zinc-500">4</Button>
|
||||
<Button onClick={() => handleNumber('5')} className="bg-zinc-600 hover:bg-zinc-500">5</Button>
|
||||
<Button onClick={() => handleNumber('6')} className="bg-zinc-600 hover:bg-zinc-500">6</Button>
|
||||
<Button onClick={() => handleOperation('-')} className="bg-blue-600 hover:bg-blue-700">-</Button>
|
||||
|
||||
<Button onClick={() => handleNumber('1')} className="bg-zinc-600 hover:bg-zinc-500">1</Button>
|
||||
<Button onClick={() => handleNumber('2')} className="bg-zinc-600 hover:bg-zinc-500">2</Button>
|
||||
<Button onClick={() => handleNumber('3')} className="bg-zinc-600 hover:bg-zinc-500">3</Button>
|
||||
<Button onClick={() => handleOperation('+')} className="bg-blue-600 hover:bg-blue-700">+</Button>
|
||||
|
||||
<Button onClick={() => handlePlusMinus()} className="bg-zinc-600 hover:bg-zinc-500">±</Button>
|
||||
<Button onClick={() => handleNumber('0')} className="bg-zinc-600 hover:bg-zinc-500">0</Button>
|
||||
<Button onClick={() => handleDecimal()} className="bg-zinc-600 hover:bg-zinc-500">.</Button>
|
||||
<Button onClick={() => handleEquals()} className="bg-green-600 hover:bg-green-700">=</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
40
app/changelog/page.tsx
Normal file
40
app/changelog/page.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
export default function Changelog() {
|
||||
return (
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Changelog</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6 space-y-8">
|
||||
<div className="prose prose-invert max-w-none">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white mb-4">Version 0.2.0</h2>
|
||||
<ul className="list-disc pl-5 space-y-2 text-gray-300">
|
||||
<li>Added Settings and Changelog pages</li>
|
||||
<li>Color Converter added</li>
|
||||
<li>Enhanced QR code generator</li>
|
||||
<li>Currency converter added</li>
|
||||
<li>Number Converter added</li>
|
||||
<li>Scientific Calculator added</li>
|
||||
<li>Removed bookmarked websites feature</li>
|
||||
<li>Updated navigation structure</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white mb-4">Version 0.1.0</h2>
|
||||
<ul className="list-disc pl-5 space-y-2 text-gray-300">
|
||||
<li>Initial release</li>
|
||||
<li>Added basic tools: Password Generator, QR Code Generator</li>
|
||||
<li>Added converters: Number, Color, Currency</li>
|
||||
<li>Added Scientific Calculator</li>
|
||||
<li>Basic website layout and navigation</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
export default function ComingSoon() {
|
||||
return (
|
||||
<main className="min-h-screen p-8">
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Coming Soon</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6 space-y-8">
|
||||
<div className="prose prose-invert max-w-none">
|
||||
<p className="text-gray-300 mb-6">
|
||||
This project is maintained and developed by <a href="https://github.com/danielvici" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">me</a> as an individual developer.
|
||||
This project is maintained and developed by <a href="https://github.com/danielvici" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">me</a> as an individual developer.
|
||||
New functions and tools will be added as time and inspiration allow.
|
||||
</p>
|
||||
|
||||
@@ -16,10 +16,42 @@ export default function ComingSoon() {
|
||||
Instead, I add new tools and functions when I have a good idea and the time allows.
|
||||
</p>
|
||||
|
||||
<p className="text-gray-300 mt-8">
|
||||
If you have suggestions for new tools or features, you can submit them via GitHub
|
||||
or contact <a href="https://x.com/danielvici123" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">me</a> directly. I welcome any feedback and suggestions!
|
||||
</p>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white mb-4">Planned Features</h2>
|
||||
<ul className="list-disc pl-5 space-y-2 text-gray-300">
|
||||
<li>Text Tools
|
||||
<ul className="list-disc pl-5 mt-2 space-y-1">
|
||||
<li>Text Case Converter</li>
|
||||
<li>Lorem Ipsum Generator</li>
|
||||
<li>String Hash Generator</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Development Tools
|
||||
<ul className="list-disc pl-5 mt-2 space-y-1">
|
||||
<li>JSON Formatter</li>
|
||||
<li>Base64 Encoder/Decoder</li>
|
||||
<li>URL Encoder/Decoder</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Image Tools
|
||||
<ul className="list-disc pl-5 mt-2 space-y-1">
|
||||
<li>Image Resizer</li>
|
||||
<li>Image Format Converter</li>
|
||||
<li>Image Compression</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-900/20 rounded-lg p-6 border border-blue-800">
|
||||
<h3 className="text-xl font-bold text-blue-400 mb-2">Want to suggest a feature?</h3>
|
||||
<p className="text-gray-300">
|
||||
Feel free to suggest new tools or features through the GitHub repository or contact <a href="https://x.com/danielvici123" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-600">me</a> directly.
|
||||
I welcome any feedback and suggestions!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,12 +7,26 @@ export default function AddWebsite() {
|
||||
const [title, setTitle] = useState('')
|
||||
const [isAdding, setIsAdding] = useState(false)
|
||||
|
||||
const formatUrl = (inputUrl: string) => {
|
||||
// Remove leading/trailing whitespace
|
||||
let formattedUrl = inputUrl.trim()
|
||||
|
||||
// If URL doesn't start with a protocol, add https://
|
||||
if (!formattedUrl.match(/^https?:\/\//i)) {
|
||||
formattedUrl = 'https://' + formattedUrl
|
||||
}
|
||||
|
||||
return formattedUrl
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
// Validiere URL
|
||||
const formattedUrl = formatUrl(url)
|
||||
|
||||
// Validate URL
|
||||
try {
|
||||
new URL(url)
|
||||
new URL(formattedUrl)
|
||||
} catch {
|
||||
alert('Please enter a valid URL')
|
||||
return
|
||||
@@ -20,23 +34,23 @@ export default function AddWebsite() {
|
||||
|
||||
const newWebsite: PinnedWebsite = {
|
||||
id: Date.now().toString(),
|
||||
title: title || url,
|
||||
url: url.startsWith('http') ? url : `https://${url}`,
|
||||
title: title || formattedUrl,
|
||||
url: formattedUrl,
|
||||
addedAt: Date.now()
|
||||
}
|
||||
|
||||
// Lade bestehende Websites
|
||||
// Load existing websites
|
||||
const existingWebsites = JSON.parse(localStorage.getItem('pinnedWebsites') || '[]')
|
||||
|
||||
// Füge neue Website hinzu
|
||||
// Add new website
|
||||
localStorage.setItem('pinnedWebsites', JSON.stringify([...existingWebsites, newWebsite]))
|
||||
|
||||
// Setze Formular zurück
|
||||
// Reset form
|
||||
setUrl('')
|
||||
setTitle('')
|
||||
setIsAdding(false)
|
||||
|
||||
// Lade die Seite neu um die neue Website anzuzeigen
|
||||
// Reload page to show new website
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
@@ -59,10 +73,13 @@ export default function AddWebsite() {
|
||||
type="text"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
placeholder="https://example.com"
|
||||
placeholder="example.com"
|
||||
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
HTTPS will be added automatically if not provided
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-1">
|
||||
|
||||
@@ -4,15 +4,23 @@ import Link from 'next/link'
|
||||
|
||||
export default function Navbar() {
|
||||
return (
|
||||
<nav className="bg-blue-900 p-4 shadow-lg">
|
||||
<nav className="bg-blue-900 p-4 shadow-lg fixed w-full top-0 z-50">
|
||||
<div className="container mx-auto">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex-1" /> {/* Spacer */}
|
||||
<Link href="/" className="text-white font-bold text-xl hover:text-blue-100">
|
||||
<Link href="/" className="text-white font-bold text-xl hover:text-blue-200">
|
||||
HOME
|
||||
</Link>
|
||||
<div className="flex-1 flex justify-end">
|
||||
<Link href="/about" className="text-white hover:text-blue-100">
|
||||
<div className="flex gap-6">
|
||||
<Link href="/coming_soon" className="text-white hover:text-blue-200">
|
||||
Coming Soon
|
||||
</Link>
|
||||
<Link href="/changelog" className="text-white hover:text-blue-200">
|
||||
Changelog
|
||||
</Link>
|
||||
<Link href="/settings" className="text-white hover:text-blue-200">
|
||||
Settings
|
||||
</Link>
|
||||
<Link href="/about" className="text-white hover:text-blue-200">
|
||||
About
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
155
app/converter/color/page.tsx
Normal file
155
app/converter/color/page.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type ColorFormats = {
|
||||
hex: string
|
||||
rgb: string
|
||||
hsl: string
|
||||
}
|
||||
|
||||
export default function ColorConverter() {
|
||||
const [color, setColor] = useState('#000000')
|
||||
const [formats, setFormats] = useState<ColorFormats>({
|
||||
hex: '#000000',
|
||||
rgb: 'rgb(0, 0, 0)',
|
||||
hsl: 'hsl(0, 0%, 0%)'
|
||||
})
|
||||
|
||||
const hexToRgb = (hex: string): number[] => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
? [
|
||||
parseInt(result[1], 16),
|
||||
parseInt(result[2], 16),
|
||||
parseInt(result[3], 16)
|
||||
]
|
||||
: [0, 0, 0]
|
||||
}
|
||||
|
||||
const rgbToHsl = (r: number, g: number, b: number): number[] => {
|
||||
r /= 255
|
||||
g /= 255
|
||||
b /= 255
|
||||
|
||||
const max = Math.max(r, g, b)
|
||||
const min = Math.min(r, g, b)
|
||||
let h = 0
|
||||
let s = 0
|
||||
const l = (max + min) / 2
|
||||
|
||||
if (max !== min) {
|
||||
const d = max - min
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
||||
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0)
|
||||
break
|
||||
case g:
|
||||
h = (b - r) / d + 2
|
||||
break
|
||||
case b:
|
||||
h = (r - g) / d + 4
|
||||
break
|
||||
}
|
||||
|
||||
h /= 6
|
||||
}
|
||||
|
||||
return [
|
||||
Math.round(h * 360),
|
||||
Math.round(s * 100),
|
||||
Math.round(l * 100)
|
||||
]
|
||||
}
|
||||
|
||||
const updateFormats = (hex: string) => {
|
||||
const rgb = hexToRgb(hex)
|
||||
const hsl = rgbToHsl(rgb[0], rgb[1], rgb[2])
|
||||
|
||||
setFormats({
|
||||
hex: hex,
|
||||
rgb: `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`,
|
||||
hsl: `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateFormats(color)
|
||||
}, [color])
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Color Converter</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<div className="prose prose-invert max-w-none mb-6">
|
||||
<p className="text-gray-300">
|
||||
Convert colors between different formats: HEX, RGB, and HSL.
|
||||
Use the color picker or enter values manually to see the conversions.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col md:flex-row gap-6">
|
||||
<div className="w-full">
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Color Picker
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
value={color}
|
||||
onChange={(e) => setColor(e.target.value)}
|
||||
className="w-full h-12 rounded-md cursor-pointer bg-zinc-700 p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold text-white">Color Values</h2>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<div className="bg-zinc-900 p-4 rounded-lg">
|
||||
<label className="block text-sm font-medium text-gray-400 mb-1">
|
||||
HEX
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={formats.hex}
|
||||
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-900 p-4 rounded-lg">
|
||||
<label className="block text-sm font-medium text-gray-400 mb-1">
|
||||
RGB
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={formats.rgb}
|
||||
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-900 p-4 rounded-lg">
|
||||
<label className="block text-sm font-medium text-gray-400 mb-1">
|
||||
HSL
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={formats.hsl}
|
||||
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
173
app/converter/currency/page.tsx
Normal file
173
app/converter/currency/page.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type ExchangeRates = {
|
||||
[key: string]: number
|
||||
}
|
||||
|
||||
const POPULAR_CURRENCIES = [
|
||||
{ code: 'USD', name: 'US Dollar' },
|
||||
{ code: 'EUR', name: 'Euro' },
|
||||
{ code: 'GBP', name: 'British Pound' },
|
||||
{ code: 'JPY', name: 'Japanese Yen' },
|
||||
{ code: 'CHF', name: 'Swiss Franc' },
|
||||
{ code: 'CAD', name: 'Canadian Dollar' },
|
||||
{ code: 'AUD', name: 'Australian Dollar' },
|
||||
{ code: 'CNY', name: 'Chinese Yuan' },
|
||||
]
|
||||
|
||||
export default function CurrencyConverter() {
|
||||
const [amount, setAmount] = useState('1')
|
||||
const [fromCurrency, setFromCurrency] = useState('EUR')
|
||||
const [toCurrency, setToCurrency] = useState('USD')
|
||||
const [rates, setRates] = useState<ExchangeRates>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRates = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
// Using ExchangeRate-API (free tier)
|
||||
const response = await fetch('https://open.er-api.com/v6/latest/EUR')
|
||||
const data = await response.json()
|
||||
|
||||
if (data.rates) {
|
||||
setRates(data.rates)
|
||||
setLastUpdated(new Date())
|
||||
} else {
|
||||
throw new Error('Failed to fetch exchange rates')
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to load exchange rates. Please try again later.')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
fetchRates()
|
||||
}, [])
|
||||
|
||||
const convert = () => {
|
||||
if (!rates[fromCurrency] || !rates[toCurrency]) return '0.00'
|
||||
|
||||
const amountNum = parseFloat(amount)
|
||||
if (isNaN(amountNum)) return '0.00'
|
||||
|
||||
// Convert through EUR (base currency)
|
||||
const inEUR = amountNum / rates[fromCurrency]
|
||||
const result = inEUR * rates[toCurrency]
|
||||
|
||||
return result.toFixed(2)
|
||||
}
|
||||
|
||||
const handleSwap = () => {
|
||||
setFromCurrency(toCurrency)
|
||||
setToCurrency(fromCurrency)
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Currency Converter</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<div className="prose prose-invert max-w-none mb-6">
|
||||
<p className="text-gray-300">
|
||||
Convert between different currencies using real-time exchange rates.
|
||||
{lastUpdated && (
|
||||
<span className="block text-sm text-gray-400 mt-1">
|
||||
Last updated: {lastUpdated.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-blue-400">Loading exchange rates...</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="text-red-500 text-sm p-3 bg-red-500/10 rounded-md border border-red-500/20">
|
||||
{error}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Amount
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-end gap-2">
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
From
|
||||
</label>
|
||||
<select
|
||||
value={fromCurrency}
|
||||
onChange={(e) => setFromCurrency(e.target.value)}
|
||||
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2 text-sm"
|
||||
>
|
||||
{POPULAR_CURRENCIES.map(currency => (
|
||||
<option key={currency.code} value={currency.code} className="text-sm">
|
||||
{currency.code} - {currency.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleSwap}
|
||||
className="bg-zinc-700 text-white p-2 rounded-md hover:bg-zinc-600 transition-colors mb-0.5"
|
||||
>
|
||||
⇄
|
||||
</button>
|
||||
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
To
|
||||
</label>
|
||||
<select
|
||||
value={toCurrency}
|
||||
onChange={(e) => setToCurrency(e.target.value)}
|
||||
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2 text-sm"
|
||||
>
|
||||
{POPULAR_CURRENCIES.map(currency => (
|
||||
<option key={currency.code} value={currency.code} className="text-sm">
|
||||
{currency.code} - {currency.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-900 p-6 rounded-lg text-center">
|
||||
<div className="text-sm text-gray-400 mb-2">Result</div>
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{amount} {fromCurrency} = {convert()} {toCurrency}
|
||||
</div>
|
||||
<div className="text-sm text-gray-400 mt-2">
|
||||
1 {fromCurrency} = {(rates[toCurrency] / rates[fromCurrency]).toFixed(4)} {toCurrency}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export default function ImageConverter() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8">
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Image Converter</h1>
|
||||
|
||||
@@ -100,6 +100,7 @@ export default function ImageConverter() {
|
||||
<option value="jpg">JPG</option>
|
||||
<option value="webp">WebP</option>
|
||||
<option value="gif">GIF</option>
|
||||
<option value="ico">ICO</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
143
app/converter/number/page.tsx
Normal file
143
app/converter/number/page.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function NumberConverter() {
|
||||
const [value, setValue] = useState('')
|
||||
const [fromBase, setFromBase] = useState('10')
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const isValidNumber = (num: string, base: string) => {
|
||||
const validChars = {
|
||||
'2': /^[01]+$/,
|
||||
'10': /^[0-9]+$/,
|
||||
'16': /^[0-9A-Fa-f]+$/
|
||||
}
|
||||
return validChars[base as keyof typeof validChars].test(num)
|
||||
}
|
||||
|
||||
const convert = (input: string, from: string) => {
|
||||
if (!input) return { binary: '', decimal: '', hex: '' }
|
||||
|
||||
try {
|
||||
if (!isValidNumber(input, from)) {
|
||||
throw new Error('Invalid number for selected base')
|
||||
}
|
||||
|
||||
const decimal = parseInt(input, parseInt(from))
|
||||
if (isNaN(decimal)) throw new Error('Invalid number')
|
||||
|
||||
return {
|
||||
binary: decimal.toString(2),
|
||||
decimal: decimal.toString(10),
|
||||
hex: decimal.toString(16).toUpperCase()
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Invalid input')
|
||||
return { binary: '', decimal: '', hex: '' }
|
||||
}
|
||||
}
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value)
|
||||
setError('')
|
||||
}
|
||||
|
||||
const results = convert(value, fromBase)
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Number Converter</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<div className="prose prose-invert max-w-none mb-6">
|
||||
<p className="text-gray-300">
|
||||
Convert numbers between binary (base 2), decimal (base 10), and hexadecimal (base 16) formats.
|
||||
Enter a number in any base and see its equivalent representations.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Input Number
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter a number"
|
||||
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Input Base
|
||||
</label>
|
||||
<select
|
||||
value={fromBase}
|
||||
onChange={(e) => setFromBase(e.target.value)}
|
||||
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
|
||||
>
|
||||
<option value="2">Binary (Base 2)</option>
|
||||
<option value="10">Decimal (Base 10)</option>
|
||||
<option value="16">Hexadecimal (Base 16)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="text-red-500 text-sm p-3 bg-red-500/10 rounded-md border border-red-500/20">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-xl font-semibold text-white">Results</h2>
|
||||
|
||||
<div className="grid gap-4">
|
||||
<div className="bg-zinc-900 p-4 rounded-lg">
|
||||
<label className="block text-sm font-medium text-gray-400 mb-1">
|
||||
Binary (Base 2)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={results.binary}
|
||||
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-900 p-4 rounded-lg">
|
||||
<label className="block text-sm font-medium text-gray-400 mb-1">
|
||||
Decimal (Base 10)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={results.decimal}
|
||||
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-900 p-4 rounded-lg">
|
||||
<label className="block text-sm font-medium text-gray-400 mb-1">
|
||||
Hexadecimal (Base 16)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={results.hex}
|
||||
className="w-full bg-zinc-800 text-white rounded-md border border-gray-700 p-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -3,16 +3,32 @@ import { useState } from 'react'
|
||||
|
||||
export default function PasswordGenerator() {
|
||||
const [password, setPassword] = useState('')
|
||||
const [length, setLength] = useState(12)
|
||||
const [length, setLength] = useState(16)
|
||||
const [includeNumbers, setIncludeNumbers] = useState(true)
|
||||
const [includeSymbols, setIncludeSymbols] = useState(true)
|
||||
const [includeLetters, setIncludeLetters] = useState(true)
|
||||
|
||||
const getStrengthDescription = (len: number) => {
|
||||
if (len < 8) return { text: 'Very Weak - Not recommended for security', color: 'text-red-500' }
|
||||
if (len < 12) return { text: 'Weak - Only for low-security needs', color: 'text-orange-500' }
|
||||
if (len < 16) return { text: 'Moderate - Good for most purposes', color: 'text-yellow-500' }
|
||||
if (len < 20) return { text: 'Strong - Recommended for sensitive data', color: 'text-green-500' }
|
||||
return { text: 'Very Strong - Excellent for critical security', color: 'text-emerald-500' }
|
||||
}
|
||||
|
||||
const generatePassword = () => {
|
||||
const numbers = '0123456789'
|
||||
const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'
|
||||
const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
|
||||
let chars = letters
|
||||
// Ensure at least one type is selected
|
||||
if (!includeLetters && !includeNumbers && !includeSymbols) {
|
||||
setIncludeLetters(true)
|
||||
return
|
||||
}
|
||||
|
||||
let chars = ''
|
||||
if (includeLetters) chars += letters
|
||||
if (includeNumbers) chars += numbers
|
||||
if (includeSymbols) chars += symbols
|
||||
|
||||
@@ -24,12 +40,25 @@ export default function PasswordGenerator() {
|
||||
setPassword(newPassword)
|
||||
}
|
||||
|
||||
const strength = getStrengthDescription(length)
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8">
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Password Generator</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<div className="prose prose-invert max-w-none mb-6">
|
||||
<p className="text-gray-300">
|
||||
Generate secure passwords with customizable options. For maximum security, we recommend:
|
||||
</p>
|
||||
<ul className="list-disc pl-6 text-gray-300 space-y-1">
|
||||
<li>Use at least 16 characters for sensitive accounts</li>
|
||||
<li>Include a mix of letters, numbers, and special characters</li>
|
||||
<li>Use different passwords for each account</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
@@ -37,15 +66,28 @@ export default function PasswordGenerator() {
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="8"
|
||||
max="32"
|
||||
min="4"
|
||||
max="64"
|
||||
value={length}
|
||||
onChange={(e) => setLength(Number(e.target.value))}
|
||||
className="w-full mt-2"
|
||||
/>
|
||||
<p className={`text-sm mt-2 ${strength.color}`}>
|
||||
{strength.text}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-4">
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={includeLetters}
|
||||
onChange={(e) => setIncludeLetters(e.target.checked)}
|
||||
className="rounded border-gray-600 bg-zinc-700 text-blue-500 mr-2 focus:ring-blue-500 focus:ring-offset-zinc-800"
|
||||
/>
|
||||
<span className="text-gray-300">Letters</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
143
app/generator/qr/page.tsx
Normal file
143
app/generator/qr/page.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
'use client'
|
||||
import { useState, useRef } from 'react'
|
||||
import QRCode from 'qrcode'
|
||||
|
||||
export default function QRGenerator() {
|
||||
const [text, setText] = useState('')
|
||||
const [qrDataUrl, setQrDataUrl] = useState('')
|
||||
const [size, setSize] = useState(300)
|
||||
const [darkColor, setDarkColor] = useState('#000000')
|
||||
const [lightColor, setLightColor] = useState('#FFFFFF')
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
const generateQR = async () => {
|
||||
if (!text) return
|
||||
|
||||
try {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
|
||||
await QRCode.toCanvas(canvas, text, {
|
||||
width: size,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: darkColor,
|
||||
light: lightColor
|
||||
}
|
||||
})
|
||||
|
||||
setQrDataUrl(canvas.toDataURL('image/png'))
|
||||
} catch (err) {
|
||||
console.error('Error generating QR code:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const downloadQR = () => {
|
||||
if (!qrDataUrl) return
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.download = 'qrcode.png'
|
||||
link.href = qrDataUrl
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">QR Code Generator</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6">
|
||||
<div className="prose prose-invert max-w-none mb-6">
|
||||
<p className="text-gray-300">
|
||||
Generate QR codes for text, URLs, or any other content. Customize the size and colors,
|
||||
then download the QR code as a PNG image.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Text or URL
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
placeholder="Enter text or URL"
|
||||
className="w-full bg-zinc-700 text-white rounded-md border border-gray-600 p-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Size: {size}x{size} pixels
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="100"
|
||||
max="1000"
|
||||
step="50"
|
||||
value={size}
|
||||
onChange={(e) => setSize(Number(e.target.value))}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
QR Code Color
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
value={darkColor}
|
||||
onChange={(e) => setDarkColor(e.target.value)}
|
||||
className="w-full h-10 rounded-md cursor-pointer bg-zinc-700 p-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Background Color
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
value={lightColor}
|
||||
onChange={(e) => setLightColor(e.target.value)}
|
||||
className="w-full h-10 rounded-md cursor-pointer bg-zinc-700 p-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={generateQR}
|
||||
className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Generate QR Code
|
||||
</button>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<canvas ref={canvasRef} className="hidden" />
|
||||
{qrDataUrl && (
|
||||
<div className="space-y-4">
|
||||
<img
|
||||
src={qrDataUrl}
|
||||
alt="Generated QR Code"
|
||||
className="mx-auto border-4 border-white rounded-lg"
|
||||
/>
|
||||
<button
|
||||
onClick={downloadQR}
|
||||
className="w-full bg-green-500 text-white py-2 px-4 rounded-md hover:bg-green-600 transition-colors"
|
||||
>
|
||||
Download QR Code
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata = {
|
||||
title: 'Tool Website',
|
||||
description: 'Eine Sammlung nützlicher Tools',
|
||||
description: 'A collection of useful web-based tools. Secure and Open Source.',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -15,8 +15,11 @@ export default function RootLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="de">
|
||||
<body className={inter.className}>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" type="image/vnd.microsoft.icon" href="/logo.ico" />
|
||||
</head>
|
||||
<body className="bg-zinc-900">
|
||||
<Navbar />
|
||||
{children}
|
||||
</body>
|
||||
|
||||
140
app/page.tsx
140
app/page.tsx
@@ -1,95 +1,81 @@
|
||||
'use client'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { PinnedWebsite } from './types/types'
|
||||
import AddWebsite from './components/AddWebsite'
|
||||
|
||||
export default function Home() {
|
||||
const [pinnedWebsites, setPinnedWebsites] = useState<PinnedWebsite[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('pinnedWebsites')
|
||||
if (saved) {
|
||||
setPinnedWebsites(JSON.parse(saved))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
const newWebsites = pinnedWebsites.filter(site => site.id !== id)
|
||||
localStorage.setItem('pinnedWebsites', JSON.stringify(newWebsites))
|
||||
setPinnedWebsites(newWebsites)
|
||||
}
|
||||
|
||||
const tools = [
|
||||
{
|
||||
name: 'Password Generator',
|
||||
file: '/password',
|
||||
description: 'Create secure passwords with custom options',
|
||||
category: 'Generators',
|
||||
items: [
|
||||
{
|
||||
name: 'Password Generator',
|
||||
file: '/generator/password',
|
||||
description: 'Create secure passwords with custom options',
|
||||
},
|
||||
{
|
||||
name: 'QR Code Generator',
|
||||
file: '/generator/qr',
|
||||
description: 'Generate and download QR codes for any text or URL',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Image Converter',
|
||||
file: '/img_converter',
|
||||
description: 'Convert images between different formats',
|
||||
category: 'Converters',
|
||||
items: [
|
||||
{
|
||||
name: 'Image Converter',
|
||||
file: '/converter/image',
|
||||
description: 'Convert images between different formats',
|
||||
},
|
||||
{
|
||||
name: 'Number Converter',
|
||||
file: '/converter/number',
|
||||
description: 'Convert numbers between binary, decimal, and hexadecimal',
|
||||
},
|
||||
{
|
||||
name: 'Color Converter',
|
||||
file: '/converter/color',
|
||||
description: 'Convert colors between RGB, HEX, HSL',
|
||||
},
|
||||
{
|
||||
name: 'Currency Converter',
|
||||
file: '/converter/currency',
|
||||
description: 'Convert between different currencies with live rates',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'More Coming Soon',
|
||||
file: '/coming_soon',
|
||||
description: 'Learn more about future features',
|
||||
isComingSoon: true
|
||||
}
|
||||
category: 'Calculators',
|
||||
items: [
|
||||
{
|
||||
name: 'Scientific Calculator',
|
||||
file: '/calculator/scientific',
|
||||
description: 'Advanced calculator with scientific functions',
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8 text-white">
|
||||
<main className="min-h-screen p-8 pt-24 text-white">
|
||||
<div className="max-w-7xl mx-auto space-y-12">
|
||||
{/* Tools Section */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-6">Tools</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{tools.map((tool, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
href={tool.file}
|
||||
className={`p-6 rounded-lg border border-gray-700 bg-zinc-800 transition-colors duration-200 cursor-pointer
|
||||
${tool.isComingSoon
|
||||
? 'opacity-50 hover:opacity-75'
|
||||
: 'hover:border-blue-500'}`}
|
||||
>
|
||||
<h2 className="text-xl font-semibold mb-2">{tool.name}</h2>
|
||||
<p className="text-gray-400">{tool.description}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pinned Websites Section */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-6">Bookmarked Websites</h2>
|
||||
<AddWebsite />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{pinnedWebsites.map((website) => (
|
||||
<div
|
||||
key={website.id}
|
||||
className="group relative p-6 rounded-lg border border-gray-700 bg-zinc-800 hover:border-blue-500 transition-colors"
|
||||
>
|
||||
<a
|
||||
href={website.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block"
|
||||
>
|
||||
<h3 className="text-xl font-semibold mb-2">{website.title}</h3>
|
||||
<p className="text-gray-400 truncate">{website.url}</p>
|
||||
</a>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
handleDelete(website.id)
|
||||
}}
|
||||
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 text-gray-400 hover:text-red-500 transition-opacity"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<h2 className="text-2xl font-bold mb-6 text-white">Tools</h2>
|
||||
<div className="space-y-8">
|
||||
{tools.map((category, categoryIndex) => (
|
||||
<div key={categoryIndex}>
|
||||
<h3 className="text-xl font-semibold mb-4 text-blue-400">{category.category}</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{category.items.map((tool, toolIndex) => (
|
||||
<Link
|
||||
key={toolIndex}
|
||||
href={tool.file}
|
||||
className="p-6 rounded-lg border border-gray-700 bg-zinc-800 transition-colors duration-200 hover:border-blue-500"
|
||||
>
|
||||
<h2 className="text-xl font-semibold mb-2 text-white">{tool.name}</h2>
|
||||
<p className="text-gray-400">{tool.description}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
41
app/settings/page.tsx
Normal file
41
app/settings/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export default function Settings() {
|
||||
const [darkMode, setDarkMode] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const savedMode = localStorage.getItem('darkMode')
|
||||
if (savedMode !== null) {
|
||||
setDarkMode(JSON.parse(savedMode))
|
||||
}
|
||||
}, [])
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
const newMode = !darkMode
|
||||
setDarkMode(newMode)
|
||||
localStorage.setItem('darkMode', JSON.stringify(newMode))
|
||||
// Apply dark mode to document
|
||||
if (newMode) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8 pt-24">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h1 className="text-4xl font-bold mb-8 text-white">Settings</h1>
|
||||
|
||||
<div className="bg-zinc-800 rounded-lg shadow-md p-6 space-y-8">
|
||||
<div className="space-y-6">
|
||||
<p className="text-gray-300">
|
||||
Currently, there are no settings available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export type PinnedWebsite = {
|
||||
id: string;
|
||||
title: string;
|
||||
url: string;
|
||||
icon?: string;
|
||||
addedAt: number;
|
||||
}
|
||||
333
package-lock.json
generated
333
package-lock.json
generated
@@ -9,7 +9,9 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.7",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"next": "^14.0.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
@@ -1016,7 +1018,6 @@
|
||||
"version": "20.17.47",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.47.tgz",
|
||||
"integrity": "sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
@@ -1029,6 +1030,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/qrcode": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz",
|
||||
"integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.21.tgz",
|
||||
@@ -1238,6 +1248,15 @@
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
@@ -1321,6 +1340,87 @@
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cliui/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
@@ -1407,6 +1507,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
@@ -1423,6 +1532,12 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
@@ -1539,6 +1654,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
@@ -1595,6 +1723,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
@@ -1697,7 +1834,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -2013,6 +2149,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
@@ -2271,6 +2419,42 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
@@ -2278,6 +2462,15 @@
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -2351,6 +2544,15 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
@@ -2475,6 +2677,23 @@
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -2567,6 +2786,21 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
@@ -2644,6 +2878,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
|
||||
@@ -3076,7 +3316,6 @@
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
@@ -3133,6 +3372,12 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
@@ -3231,6 +3476,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||
@@ -3252,6 +3503,82 @@
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yargs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.7",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"next": "^14.0.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
|
||||
BIN
public/logo.ico
Normal file
BIN
public/logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Reference in New Issue
Block a user