'use client'; import { useState, useCallback } from 'react'; import { RefreshCw, Copy, Check, Clock } from 'lucide-react'; import { toast } from 'sonner'; import { cn, actionBtn } from '@/lib/utils'; import { SliderRow } from '@/components/ui/slider-row'; import { MobileTabs } from '@/components/ui/mobile-tabs'; import { generatePassword, passwordEntropy, generateUUID, generateApiKey, generateHash, generateToken, type PasswordOpts, type ApiKeyOpts, type HashOpts, type TokenOpts, } from '@/lib/random/generators'; type GeneratorType = 'password' | 'uuid' | 'apikey' | 'hash' | 'token'; type MobileTab = 'configure' | 'output'; const GENERATOR_TABS: { value: GeneratorType; label: string }[] = [ { value: 'password', label: 'Password' }, { value: 'uuid', label: 'UUID' }, { value: 'apikey', label: 'API Key' }, { value: 'hash', label: 'Hash' }, { value: 'token', label: 'Token' }, ]; const selectCls = 'w-full bg-transparent border border-border/40 rounded-lg px-2.5 py-1.5 text-xs font-mono outline-none focus:border-primary/50 transition-colors text-foreground/80 cursor-pointer'; const strengthLabel = (bits: number) => { if (bits < 40) return { label: 'Weak', color: 'bg-red-500' }; if (bits < 60) return { label: 'Fair', color: 'bg-amber-400' }; if (bits < 80) return { label: 'Good', color: 'bg-yellow-400' }; if (bits < 100) return { label: 'Strong', color: 'bg-emerald-400' }; return { label: 'Very Strong', color: 'bg-primary' }; }; export function RandomGenerator() { const [type, setType] = useState('password'); const [mobileTab, setMobileTab] = useState('configure'); const [output, setOutput] = useState(''); const [copied, setCopied] = useState(false); const [generating, setGenerating] = useState(false); const [history, setHistory] = useState([]); // Options per type const [pwOpts, setPwOpts] = useState({ length: 24, uppercase: true, lowercase: true, numbers: true, symbols: true, }); const [apiOpts, setApiOpts] = useState({ length: 32, format: 'hex', prefix: '', }); const [hashOpts, setHashOpts] = useState({ algorithm: 'SHA-256', input: '', }); const [tokenOpts, setTokenOpts] = useState({ bytes: 32, format: 'hex', }); const pushHistory = (val: string) => setHistory((h) => [val, ...h].slice(0, 8)); const generate = useCallback(async () => { setGenerating(true); try { let result = ''; switch (type) { case 'password': result = generatePassword(pwOpts); break; case 'uuid': result = generateUUID(); break; case 'apikey': result = generateApiKey(apiOpts); break; case 'hash': result = await generateHash(hashOpts); break; case 'token': result = generateToken(tokenOpts); break; } setOutput(result); pushHistory(result); setMobileTab('output'); } catch { toast.error('Generation failed'); } finally { setGenerating(false); } }, [type, pwOpts, apiOpts, hashOpts, tokenOpts]); const copy = (val = output) => { if (!val) return; navigator.clipboard.writeText(val); setCopied(true); toast.success('Copied to clipboard'); setTimeout(() => setCopied(false), 2000); }; const entropy = type === 'password' ? passwordEntropy(pwOpts) : null; const strength = entropy !== null ? strengthLabel(entropy) : null; return (
setMobileTab(v as MobileTab)} />
{/* ── Left: type selector + options ───────────────────── */}
{/* Type selector */}
Generator
{GENERATOR_TABS.map(({ value, label }) => ( ))}
{/* Options */}
Options {/* ── Password ── */} {type === 'password' && (
setPwOpts((o) => ({ ...o, length: v }))} />
Character sets
{([ { key: 'uppercase', label: 'A–Z', hint: 'Uppercase' }, { key: 'lowercase', label: 'a–z', hint: 'Lowercase' }, { key: 'numbers', label: '0–9', hint: 'Numbers' }, { key: 'symbols', label: '!@#', hint: 'Symbols' }, ] as const).map(({ key, label, hint }) => ( ))}
{strength && (
Strength {entropy} bits
{strength.label}
)}
)} {/* ── UUID ── */} {type === 'uuid' && (

Generates a cryptographically random UUID v4 using the browser's built-in{' '} crypto.randomUUID().

Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

)} {/* ── API Key ── */} {type === 'apikey' && (
setApiOpts((o) => ({ ...o, length: v }))} />
Encoding
Prefix (optional) setApiOpts((o) => ({ ...o, prefix: e.target.value }))} placeholder="sk, pk, api..." className="w-full bg-transparent border border-border/40 rounded-lg px-2.5 py-1.5 text-xs font-mono outline-none focus:border-primary/50 transition-colors text-foreground/80 placeholder:text-muted-foreground/25" />
)} {/* ── Hash ── */} {type === 'hash' && (
Algorithm
Input (empty = random)