diff --git a/src/modules/password-vault/components/password-vault-module.tsx b/src/modules/password-vault/components/password-vault-module.tsx index 4c30e87..18490d0 100644 --- a/src/modules/password-vault/components/password-vault-module.tsx +++ b/src/modules/password-vault/components/password-vault-module.tsx @@ -1,7 +1,10 @@ 'use client'; import { useState } from 'react'; -import { Plus, Pencil, Trash2, Search, Eye, EyeOff, Copy, ExternalLink } from 'lucide-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'; @@ -9,7 +12,9 @@ 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 type { VaultEntry, VaultEntryCategory } from '../types'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/shared/components/ui/dialog'; +import { Switch } from '@/shared/components/ui/switch'; +import type { VaultEntry, VaultEntryCategory, CustomField } from '../types'; import { useVault } from '../hooks/use-vault'; const CATEGORY_LABELS: Record = { @@ -18,12 +23,28 @@ const CATEGORY_LABELS: Record = { 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) => { @@ -51,6 +72,13 @@ export function PasswordVaultModule() { setEditingEntry(null); }; + const handleDeleteConfirm = async () => { + if (deletingId) { + await removeEntry(deletingId); + setDeletingId(null); + } + }; + return (
@@ -118,12 +146,21 @@ export function PasswordVaultModule() { {entry.url}

)} + {entry.customFields && entry.customFields.length > 0 && ( +
+ {entry.customFields.map((cf, i) => ( + + {cf.key}: {cf.value} + + ))} +
+ )}
-
@@ -143,6 +180,18 @@ export function PasswordVaultModule() { )} + + {/* Delete confirmation */} + { if (!open) setDeletingId(null); }}> + + Confirmare ștergere +

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

+ + + + +
+
); } @@ -158,11 +207,42 @@ function VaultForm({ initial, onSubmit, onCancel }: { const [url, setUrl] = useState(initial?.url ?? ''); const [category, setCategory] = useState(initial?.category ?? 'web'); 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 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, encryptedPassword: password, url, category, notes, tags: initial?.tags ?? [], visibility: initial?.visibility ?? 'admin' }); }} className="space-y-4"> + { + e.preventDefault(); + onSubmit({ + label, username, encryptedPassword: password, url, category, 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 />
+
setLabel(e.target.value)} className="mt-1" required />
setUsername(e.target.value)} className="mt-1" />
-
setPassword(e.target.value)} className="mt-1" />
+
+ +
+ setPassword(e.target.value)} className="flex-1 font-mono text-sm" /> + +
+
+ + {/* 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" /> + +
+ ))} +
+ )} +
+