Spaces:
Running
Running
| import React, { useState, useEffect } from 'react'; | |
| const Base64Converter = ({ addToMessage, sharedInput, setSharedInput }) => { | |
| const [output, setOutput] = useState(''); | |
| const [copied, setCopied] = useState(false); | |
| const [toBase64, setToBase64] = useState(true); | |
| const [hasError, setHasError] = useState(false); | |
| const convertToBase64 = (text) => { | |
| try { | |
| // Handle unicode characters | |
| const encoded = btoa(unescape(encodeURIComponent(text))); | |
| setOutput(encoded); | |
| setHasError(false); | |
| } catch (error) { | |
| setOutput('Error: Invalid input for Base64 encoding.'); | |
| setHasError(true); | |
| } | |
| }; | |
| const convertFromBase64 = (base64) => { | |
| try { | |
| // Handle unicode characters | |
| const decoded = decodeURIComponent(escape(atob(base64))); | |
| setOutput(decoded); | |
| setHasError(false); | |
| } catch (error) { | |
| setOutput('Error: Invalid Base64 string.'); | |
| setHasError(true); | |
| } | |
| }; | |
| const handleInputChange = (e) => { | |
| const newText = e.target.value; | |
| // Limit input to 50,000 characters to prevent performance issues | |
| if (newText.length > 50000) { | |
| return; | |
| } | |
| setSharedInput(newText); | |
| if (toBase64) { | |
| convertToBase64(newText); | |
| } else { | |
| convertFromBase64(newText); | |
| } | |
| }; | |
| // Recalculate output when component mounts or sharedInput changes from another converter | |
| useEffect(() => { | |
| if (sharedInput) { | |
| if (toBase64) { | |
| convertToBase64(sharedInput); | |
| } else { | |
| convertFromBase64(sharedInput); | |
| } | |
| } else { | |
| setOutput(''); | |
| setHasError(false); | |
| } | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, [sharedInput, toBase64]); | |
| const copyToClipboard = () => { | |
| navigator.clipboard.writeText(output); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| }; | |
| const swapConversion = () => { | |
| const oldInput = sharedInput; | |
| setSharedInput(output); | |
| setOutput(oldInput); | |
| setToBase64(!toBase64); | |
| }; | |
| return ( | |
| <div className="p-6 bg-gradient-to-br from-gray-900 to-black rounded-lg border border-gray-800 hover-glow transition-all duration-300"> | |
| <h2 className="text-3xl font-bold mb-6 text-white flex items-center gap-2"> | |
| <span className="text-blue-500">🔐</span> | |
| Base64 Converter | |
| </h2> | |
| <div className="grid grid-cols-1 md:grid-cols-[1fr_auto_1fr] gap-6 items-center"> | |
| <div className="space-y-2"> | |
| <label htmlFor="base64-input" className="block text-sm font-medium text-gray-300 mb-2"> | |
| {toBase64 ? 'Text Input' : 'Base64 Input'} | |
| </label> | |
| <textarea | |
| id="base64-input" | |
| className="w-full h-40 p-4 bg-black border-2 border-blue-500/30 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-300 hover:border-blue-500/50 resize-none" | |
| placeholder={toBase64 ? 'Enter text...' : 'Enter Base64...'} | |
| value={sharedInput} | |
| onChange={handleInputChange} | |
| /> | |
| </div> | |
| <div className="flex justify-center"> | |
| <button | |
| onClick={swapConversion} | |
| className="p-3 bg-gradient-to-br from-blue-600 to-blue-800 text-white rounded-full hover:from-blue-500 hover:to-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-all duration-300 hover:scale-110 active:scale-95 shadow-lg hover:shadow-blue-500/50" | |
| title="Swap conversion direction" | |
| > | |
| <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m2 5H4m0 0l4 4m-4-4l4-4" /> | |
| </svg> | |
| </button> | |
| </div> | |
| <div className="space-y-2"> | |
| <label htmlFor="base64-output" className="block text-sm font-medium text-gray-300 mb-2"> | |
| {toBase64 ? 'Base64 Output' : 'Text Output'} | |
| </label> | |
| <div id="base64-output" className="w-full h-40 p-4 bg-black border-2 border-blue-500/30 rounded-lg text-white relative overflow-auto hover:border-blue-500/50 transition-all duration-300"> | |
| <div className={`break-words ${hasError ? 'text-red-400' : ''}`}> | |
| {output || <span className="text-gray-500">Output will appear here...</span>} | |
| </div> | |
| {output && !hasError && ( | |
| <div className="absolute top-2 right-2 flex flex-col gap-2"> | |
| <button | |
| onClick={copyToClipboard} | |
| className="px-4 py-2 bg-gradient-to-r from-invader-pink to-pink-600 text-black font-semibold rounded-md hover:from-pink-500 hover:to-pink-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-invader-pink transition-all duration-300 shadow-md hover:shadow-lg hover:scale-105 active:scale-95" | |
| > | |
| {copied ? '✓ Copied!' : '📋 Copy'} | |
| </button> | |
| <button | |
| onClick={() => addToMessage(output)} | |
| className="px-4 py-2 bg-gradient-to-r from-invader-green to-green-600 text-black font-semibold rounded-md hover:from-green-500 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-invader-green transition-all duration-300 shadow-md hover:shadow-lg hover:scale-105 active:scale-95" | |
| > | |
| ➕ Add | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default Base64Converter; | |