import React, { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { db } from '../services/db.js' import { getActiveMasterKey } from '../services/auth.js' import { encryptJson, decryptJson } from '../services/crypto.js' import { syncLogbook } from '../services/sync.js' import { Ship, Save, Check, Plus, X, Camera, Trash2 } from 'lucide-react' interface VesselFormProps { logbookId: string } export default function VesselForm({ logbookId }: VesselFormProps) { const { t } = useTranslation() const [name, setName] = useState('') const [homePort, setHomePort] = useState('') const [charterCompany, setCharterCompany] = useState('') const [owner, setOwner] = useState('') const [registrationNumber, setRegistrationNumber] = useState('') const [callSign, setCallSign] = useState('') const [atis, setAtis] = useState('') const [mmsi, setMmsi] = useState('') const [sails, setSails] = useState([]) const [newSailName, setNewSailName] = useState('') const fileInputRef = React.useRef(null) const [photo, setPhoto] = useState(null) const [photoError, setPhotoError] = useState(null) const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [success, setSuccess] = useState(false) const [error, setError] = useState(null) // Load E2E encrypted vessel profile on mount useEffect(() => { async function loadVessel() { setLoading(true) setError(null) try { const masterKey = getActiveMasterKey() if (!masterKey) throw new Error('Master key not found. Please log in.') const local = await db.yachts.get(logbookId) if (local) { // Decrypt fields const decrypted = await decryptJson(local.encryptedData, local.iv, local.tag, masterKey) if (decrypted) { setName(decrypted.name || '') setHomePort(decrypted.homePort || '') setCharterCompany(decrypted.charterCompany || '') setOwner(decrypted.owner || '') setRegistrationNumber(decrypted.registrationNumber || '') setCallSign(decrypted.callSign || '') setAtis(decrypted.atis || '') setMmsi(decrypted.mmsi || '') setSails(decrypted.sails || []) setPhoto(decrypted.photo || null) } } } catch (err: any) { console.error('Failed to load vessel data:', err) setError(err.message || 'Decryption failed. Could not load vessel data.') } finally { setLoading(false) } } loadVessel() }, [logbookId]) const handleAddSail = () => { const trimmed = newSailName.trim() if (trimmed && !sails.includes(trimmed)) { setSails([...sails, trimmed]) } setNewSailName('') } const handleRemoveSail = (indexToRemove: number) => { setSails(sails.filter((_, idx) => idx !== indexToRemove)) } const triggerFileInput = () => { fileInputRef.current?.click() } const handleRemovePhoto = () => { setPhoto(null) if (fileInputRef.current) fileInputRef.current.value = '' } const handlePhotoChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return setPhotoError(null) const reader = new FileReader() reader.onload = (event) => { const img = new Image() img.onload = () => { try { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') if (!ctx) throw new Error('Could not get canvas context') let width = img.width let height = img.height const MAX_WIDTH = 800 const MAX_HEIGHT = 600 // Calculate resizing conserving aspect ratio if (width > MAX_WIDTH || height > MAX_HEIGHT) { const ratio = Math.min(MAX_WIDTH / width, MAX_HEIGHT / height) width = Math.round(width * ratio) height = Math.round(height * ratio) } canvas.width = width canvas.height = height ctx.drawImage(img, 0, 0, width, height) // Compress to JPEG, 70% quality const compressedBase64 = canvas.toDataURL('image/jpeg', 0.7) setPhoto(compressedBase64) } catch (err: any) { console.error('Failed to resize yacht photo:', err) setPhotoError(err.message || 'Failed to process image') } } img.onerror = () => { setPhotoError('Invalid image file') } img.src = event.target?.result as string } reader.onerror = () => { setPhotoError('Failed to read file') } reader.readAsDataURL(file) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setSaving(true) setError(null) setSuccess(false) try { const masterKey = getActiveMasterKey() if (!masterKey) throw new Error('Master key not found. Please log in.') const yachtData = { name: name.trim(), homePort: homePort.trim(), charterCompany: charterCompany.trim(), owner: owner.trim(), registrationNumber: registrationNumber.trim(), callSign: callSign.trim(), atis: atis.trim(), mmsi: mmsi.trim(), sails: sails, photo: photo } // E2E encrypt const encrypted = await encryptJson(yachtData, masterKey) const now = new Date().toISOString() // Save locally await db.yachts.put({ logbookId, encryptedData: encrypted.ciphertext, iv: encrypted.iv, tag: encrypted.tag, updatedAt: now }) // Queue for background synchronization await db.syncQueue.put({ action: 'update', type: 'yacht', payloadId: logbookId, logbookId, data: JSON.stringify(encrypted), updatedAt: now }) setSuccess(true) setTimeout(() => setSuccess(false), 3000) // Trigger background sync task syncLogbook(logbookId).catch((err) => console.warn('Background sync failed:', err)) } catch (err: any) { console.error('Failed to save vessel data:', err) setError(err.message || 'Failed to save vessel data.') } finally { setSaving(false) } } if (loading) { return (

{t('vessel.loading')}

) } return (

{t('vessel.title')}

{error &&
{error}
}
{photo ? ( {name ) : (
)}
{photo ? t('vessel.photo_change') : t('vessel.photo_add')}
{photo && ( )}
{photoError &&
{photoError}
}
setName(e.target.value)} disabled={saving} required />
setHomePort(e.target.value)} disabled={saving} />
setOwner(e.target.value)} disabled={saving} />
setCharterCompany(e.target.value)} disabled={saving} />
setRegistrationNumber(e.target.value)} disabled={saving} />
setCallSign(e.target.value)} disabled={saving} />
setAtis(e.target.value)} disabled={saving} />
setMmsi(e.target.value)} disabled={saving} />

{t('vessel.sails_list')}

{t('vessel.sails_help')}

{sails.length === 0 ? ( {t('vessel.no_sails')} ) : ( sails.map((sail, idx) => ( {sail} )) )}
setNewSailName(e.target.value)} disabled={saving} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAddSail(); } }} />
{success && (
{t('vessel.saved')}
)}
) }