import { useState } from 'preact/hooks'; import { Archive, Clipboard, Download, Eye, EyeOff, ExternalLink, Paperclip, Pencil, RotateCcw, Trash2 } from 'lucide-preact'; import type { Cipher } from '@/lib/types'; import { t } from '@/lib/i18n'; import { TOTP_PERIOD_SECONDS, TOTP_RING_CIRCUMFERENCE, copyToClipboard, formatAttachmentSize, formatHistoryTime, formatTotp, maskSecret, openUri, parseFieldType, toBooleanFieldValue, } from '@/components/vault/vault-page-helpers'; interface VaultDetailViewProps { selectedCipher: Cipher; repromptApprovedCipherId: string | null; showPassword: boolean; totpLive: { code: string; remain: number } | null; passkeyCreatedAt: string | null; hiddenFieldVisibleMap: Record; folderName: (id: string | null | undefined) => string; downloadingAttachmentKey: string; attachmentDownloadPercent: number | null; onOpenReprompt: () => void; onToggleShowPassword: () => void; onToggleHiddenField: (index: number) => void; onDownloadAttachment: (cipher: Cipher, attachmentId: string) => void; onStartEdit: () => void; onDelete: (cipher: Cipher) => void; onArchive: (cipher: Cipher) => void | Promise; onUnarchive: (cipher: Cipher) => void | Promise; } export default function VaultDetailView(props: VaultDetailViewProps) { const selectedAttachments = Array.isArray(props.selectedCipher.attachments) ? props.selectedCipher.attachments : []; const [showSshPrivateKey, setShowSshPrivateKey] = useState(false); const isArchived = !!(props.selectedCipher.archivedDate || (props.selectedCipher as { archivedAt?: string | null }).archivedAt); const formatDownloadLabel = (attachmentId: string) => { const downloadKey = `${props.selectedCipher.id}:${attachmentId}`; if (props.downloadingAttachmentKey !== downloadKey) return t('txt_download'); return props.attachmentDownloadPercent == null ? t('txt_downloading') : t('txt_downloading_percent', { percent: props.attachmentDownloadPercent }); }; return ( <> {Number(props.selectedCipher.reprompt || 0) === 1 && props.repromptApprovedCipherId !== props.selectedCipher.id && (

{t('txt_master_password_reprompt_2')}

{t('txt_this_item_requires_master_password_every_time_before_viewing_details')}
)} {(Number(props.selectedCipher.reprompt || 0) !== 1 || props.repromptApprovedCipherId === props.selectedCipher.id) && ( <>

{props.selectedCipher.decName || t('txt_no_name')}

{props.folderName(props.selectedCipher.folderId)}
{isArchived &&
{t('txt_archived')}
}
{props.selectedCipher.login && (

{t('txt_login_credentials')}

{t('txt_username')}
{props.selectedCipher.login.decUsername || ''}
{t('txt_password')}
{props.showPassword ? props.selectedCipher.login.decPassword || '' : maskSecret(props.selectedCipher.login.decPassword || '')}
{!!props.selectedCipher.login.decTotp && (
{t('txt_totp')}
{props.totpLive ? formatTotp(props.totpLive.code) : t('txt_text_3')}
{props.totpLive ? props.totpLive.remain : 0}
)} {!!props.passkeyCreatedAt && (
{t('txt_passkey')}
{t('txt_passkey_created_at_value', { value: formatHistoryTime(props.passkeyCreatedAt) })}
)}
)} {(props.selectedCipher.login?.uris || []).length > 0 && (

{t('txt_autofill_options')}

{(props.selectedCipher.login?.uris || []).map((uri, index) => { const value = uri.decUri || uri.uri || ''; if (!value.trim()) return null; return (
{t('txt_website')}
{value}
); })}
)} {props.selectedCipher.card && (

{t('txt_card_details')}

{t('txt_cardholder_name')}{props.selectedCipher.card.decCardholderName || ''}
{t('txt_number')}{props.selectedCipher.card.decNumber || ''}
{t('txt_brand')}{props.selectedCipher.card.decBrand || ''}
{t('txt_expiry')}{`${props.selectedCipher.card.decExpMonth || ''}/${props.selectedCipher.card.decExpYear || ''}`}
{t('txt_security_code')}{props.selectedCipher.card.decCode || ''}
)} {props.selectedCipher.identity && (

{t('txt_identity_details')}

{t('txt_name')}{`${props.selectedCipher.identity.decFirstName || ''} ${props.selectedCipher.identity.decLastName || ''}`.trim()}
{t('txt_username')}{props.selectedCipher.identity.decUsername || ''}
{t('txt_email')}{props.selectedCipher.identity.decEmail || ''}
{t('txt_phone')}{props.selectedCipher.identity.decPhone || ''}
{t('txt_company')}{props.selectedCipher.identity.decCompany || ''}
{t('txt_address')}{[props.selectedCipher.identity.decAddress1, props.selectedCipher.identity.decAddress2, props.selectedCipher.identity.decAddress3, props.selectedCipher.identity.decCity, props.selectedCipher.identity.decState, props.selectedCipher.identity.decPostalCode, props.selectedCipher.identity.decCountry].filter(Boolean).join(', ')}
)} {props.selectedCipher.sshKey && (

{t('txt_ssh_key')}

{t('txt_private_key')}
{showSshPrivateKey ? props.selectedCipher.sshKey.decPrivateKey || '' : maskSecret(props.selectedCipher.sshKey.decPrivateKey || '')}
{t('txt_public_key')}
{props.selectedCipher.sshKey.decPublicKey || ''}
{t('txt_fingerprint')}
{props.selectedCipher.sshKey.decFingerprint || ''}
)} {!!(props.selectedCipher.decNotes || '').trim() && (

{t('txt_notes')}

{props.selectedCipher.decNotes || ''}
)} {(props.selectedCipher.fields || []).some((x) => parseFieldType(x.type) !== 3) && (

{t('txt_custom_fields')}

{(props.selectedCipher.fields || []) .filter((x) => parseFieldType(x.type) !== 3) .map((field, index) => { const fieldType = parseFieldType(field.type); const fieldName = field.decName || t('txt_field'); const rawValue = field.decValue || ''; const isHiddenVisible = !!props.hiddenFieldVisibleMap[index]; if (fieldType === 2) { const checked = toBooleanFieldValue(rawValue); return (
{fieldName}
); } return (
{fieldName}
{fieldType === 1 && !isHiddenVisible ? maskSecret(rawValue) : rawValue}
{fieldType === 1 && ( )}
); })}
)} {selectedAttachments.some((attachment) => String(attachment?.id || '').trim()) && (

{t('txt_attachments')}

{selectedAttachments.map((attachment) => { const attachmentId = String(attachment?.id || '').trim(); if (!attachmentId) return null; const fileName = String(attachment.decFileName || attachment.fileName || attachmentId).trim() || attachmentId; return (
{fileName} {formatAttachmentSize(attachment)}
); })}
)} {(props.selectedCipher.creationDate || props.selectedCipher.revisionDate) && (

{t('txt_item_history')}

{t('txt_last_edited_value', { value: formatHistoryTime(props.selectedCipher.revisionDate) })}
{t('txt_created_value', { value: formatHistoryTime(props.selectedCipher.creationDate) })}
)}
{isArchived ? ( ) : ( )}
)} ); }