"use client"; import { useState } from "react"; import { Plus, Pencil, Trash2, Search, Eye, EyeOff, Copy, ExternalLink, KeyRound, X, } from "lucide-react"; import { Button } from "@/shared/components/ui/button"; import { Input } from "@/shared/components/ui/input"; import { Label } from "@/shared/components/ui/label"; import { Textarea } from "@/shared/components/ui/textarea"; import { Badge } from "@/shared/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle, } from "@/shared/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/shared/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/shared/components/ui/dialog"; import { Switch } from "@/shared/components/ui/switch"; import type { CompanyId } from "@/core/auth/types"; import type { VaultEntry, VaultEntryCategory, CustomField } from "../types"; import { useVault } from "../hooks/use-vault"; const CATEGORY_LABELS: Record = { web: "Web", email: "Email", server: "Server", database: "Bază de date", api: "API", other: "Altele", }; const COMPANY_LABELS: Record = { beletage: "Beletage", "urban-switch": "Urban Switch", "studii-de-teren": "Studii de Teren", group: "Grup", }; /** Calculate password strength: 0-3 (weak, medium, strong, very strong) */ function getPasswordStrength(pwd: string): { level: 0 | 1 | 2 | 3; label: string; color: string; } { if (!pwd) return { level: 0, label: "Nicio parolă", color: "bg-gray-300" }; const len = pwd.length; const hasUpper = /[A-Z]/.test(pwd); const hasLower = /[a-z]/.test(pwd); const hasDigit = /\d/.test(pwd); const hasSymbol = /[!@#$%^&*()\-_=+\[\]{}|;:,.<>?]/.test(pwd); const varietyScore = (hasUpper ? 1 : 0) + (hasLower ? 1 : 0) + (hasDigit ? 1 : 0) + (hasSymbol ? 1 : 0); const score = len + varietyScore * 2; if (score < 8) return { level: 0, label: "Slabă", color: "bg-red-500" }; if (score < 16) return { level: 1, label: "Medie", color: "bg-yellow-500" }; if (score < 24) return { level: 2, label: "Puternică", color: "bg-green-500" }; return { level: 3, label: "Foarte puternică", color: "bg-emerald-600" }; } type ViewMode = "list" | "add" | "edit"; /** Generate a random password */ function generatePassword( length: number, options: { upper: boolean; lower: boolean; digits: boolean; symbols: boolean; }, ): string { let chars = ""; if (options.upper) chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; if (options.lower) chars += "abcdefghijklmnopqrstuvwxyz"; if (options.digits) chars += "0123456789"; if (options.symbols) chars += "!@#$%^&*()-_=+[]{}|;:,.<>?"; if (!chars) chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let result = ""; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } export function PasswordVaultModule() { const { entries, allEntries, loading, filters, updateFilter, addEntry, updateEntry, removeEntry, } = useVault(); const [viewMode, setViewMode] = useState("list"); const [editingEntry, setEditingEntry] = useState(null); const [visiblePasswords, setVisiblePasswords] = useState>( new Set(), ); const [copiedId, setCopiedId] = useState(null); const [deletingId, setDeletingId] = useState(null); const togglePassword = (id: string) => { setVisiblePasswords((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; const handleCopy = async (text: string, id: string) => { try { await navigator.clipboard.writeText(text); setCopiedId(id); setTimeout(() => setCopiedId(null), 2000); } catch { /* silent */ } }; const handleSubmit = async ( data: Omit, ) => { if (viewMode === "edit" && editingEntry) { await updateEntry(editingEntry.id, data); } else { await addEntry(data); } setViewMode("list"); setEditingEntry(null); }; const handleDeleteConfirm = async () => { if (deletingId) { await removeEntry(deletingId); setDeletingId(null); } }; return (
Atenție: Parolele sunt stocate local (localStorage). Nu sunt criptate. Folosiți un manager de parole dedicat pentru date sensibile.
{/* Stats */}

Total

{allEntries.length}

Web

{allEntries.filter((e) => e.category === "web").length}

Server

{allEntries.filter((e) => e.category === "server").length}

API

{allEntries.filter((e) => e.category === "api").length}

{viewMode === "list" && ( <>
updateFilter("search", e.target.value)} className="pl-9" />
{loading ? (

Se încarcă...

) : entries.length === 0 ? (

Nicio intrare găsită.

) : (
{entries.map((entry) => (

{entry.label}

{CATEGORY_LABELS[entry.category]}

{entry.username}

{visiblePasswords.has(entry.id) ? entry.password : "••••••••••"} {copiedId === entry.id && ( Copiat! )}
{entry.url && (

{entry.url}

)} {entry.customFields && entry.customFields.length > 0 && (
{entry.customFields.map((cf, i) => ( {cf.key}: {cf.value} ))}
)}
))}
)} )} {(viewMode === "add" || viewMode === "edit") && ( {viewMode === "edit" ? "Editare" : "Intrare nouă"} { setViewMode("list"); setEditingEntry(null); }} /> )} {/* Delete confirmation */} { if (!open) setDeletingId(null); }} > Confirmare ștergere

Ești sigur că vrei să ștergi această intrare? Acțiunea este ireversibilă.

); } function VaultForm({ initial, onSubmit, onCancel, }: { initial?: VaultEntry; onSubmit: (data: Omit) => void; onCancel: () => void; }) { const [label, setLabel] = useState(initial?.label ?? ""); const [username, setUsername] = useState(initial?.username ?? ""); const [password, setPassword] = useState(initial?.password ?? ""); const [url, setUrl] = useState(initial?.url ?? ""); const [category, setCategory] = useState( initial?.category ?? "web", ); const [company, setCompany] = useState( initial?.company ?? "beletage", ); const [notes, setNotes] = useState(initial?.notes ?? ""); const [customFields, setCustomFields] = useState( initial?.customFields ?? [], ); // Password generator state const [genLength, setGenLength] = useState(16); const [genUpper, setGenUpper] = useState(true); const [genLower, setGenLower] = useState(true); const [genDigits, setGenDigits] = useState(true); const [genSymbols, setGenSymbols] = useState(true); const strength = getPasswordStrength(password); const handleGenerate = () => { setPassword( generatePassword(genLength, { upper: genUpper, lower: genLower, digits: genDigits, symbols: genSymbols, }), ); }; const addCustomField = () => { setCustomFields([...customFields, { key: "", value: "" }]); }; const updateCustomField = ( index: number, field: keyof CustomField, value: string, ) => { setCustomFields( customFields.map((cf, i) => i === index ? { ...cf, [field]: value } : cf, ), ); }; const removeCustomField = (index: number) => { setCustomFields(customFields.filter((_, i) => i !== index)); }; return (
{ e.preventDefault(); onSubmit({ label, username, password, url, category, company, notes, customFields: customFields.filter((cf) => cf.key.trim()), tags: initial?.tags ?? [], visibility: initial?.visibility ?? "admin", }); }} className="space-y-4" >
setLabel(e.target.value)} className="mt-1" required />
setUsername(e.target.value)} className="mt-1" />
setPassword(e.target.value)} className="flex-1 font-mono text-sm" />
{password && (
Forță: {strength.label}
)}
{/* Password generator options */}

Generator parolă

setGenLength(parseInt(e.target.value, 10) || 8)} className="w-16 text-sm" min={4} max={64} />
setUrl(e.target.value)} className="mt-1" placeholder="https://..." />
{/* Custom fields */}
{customFields.length > 0 && (
{customFields.map((cf, i) => (
updateCustomField(i, "key", e.target.value)} className="w-[140px] text-sm" /> updateCustomField(i, "value", e.target.value) } className="flex-1 text-sm" />
))}
)}