import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { cycleAppLanguage, getNextLanguage, isGermanLocale } from '../utils/i18nLanguages.js' import { decryptJson } from '../services/crypto.js' import { PlausibleEvents, trackPlausibleEvent } from '../services/analytics.js' import VesselForm from './VesselForm.tsx' import CrewForm from './CrewForm.tsx' import LogEntriesList from './LogEntriesList.tsx' import { Ship, Users, FileText, Lock, AlertCircle, Globe } from 'lucide-react' interface ReadOnlyViewerProps { token: string hexKey: string } // Convert Hex String back to ArrayBuffer const hexToBuffer = (hex: string): ArrayBuffer => { const bytes = new Uint8Array(hex.length / 2) for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16) } return bytes.buffer } export default function ReadOnlyViewer({ token, hexKey }: ReadOnlyViewerProps) { const { t, i18n } = useTranslation() const [activeTab, setActiveTab] = useState<'vessel' | 'crew' | 'logs'>('logs') const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Logbook data states const [logbookTitle, setLogbookTitle] = useState('Logbook') const [yacht, setYacht] = useState(null) const [crews, setCrews] = useState([]) const [entries, setEntries] = useState([]) const [photos, setPhotos] = useState([]) const [gpsTracks, setGpsTracks] = useState([]) useEffect(() => { loadData() }, [token, hexKey]) const loadData = async () => { setLoading(true) setError(null) try { const keyBuffer = hexToBuffer(hexKey) const res = await fetch(`/api/collaboration/share-pull?token=${token}`) if (!res.ok) { if (res.status === 410) { throw new Error(isGermanLocale(i18n.language) ? 'Dieser Freigabelink ist abgelaufen.' : 'This share link has expired.') } throw new Error(isGermanLocale(i18n.language) ? 'Fehler beim Laden des freigegebenen Logbuchs.' : 'Failed to fetch shared logbook data.') } const data = await res.json() // Decrypt Title let decryptedTitle = 'Shared Logbook' if (data.title) { const parsed = JSON.parse(data.title) decryptedTitle = await decryptJson(parsed.ciphertext, parsed.iv, parsed.tag, keyBuffer) } setLogbookTitle(decryptedTitle) // Decrypt Yacht let decYacht = null if (data.yacht) { decYacht = await decryptJson(data.yacht.encryptedData, data.yacht.iv, data.yacht.tag, keyBuffer) } setYacht(decYacht) // Decrypt Crews const decCrews = [] if (data.crews) { for (const c of data.crews) { const dec = await decryptJson(c.encryptedData, c.iv, c.tag, keyBuffer) decCrews.push({ payloadId: c.payloadId, data: dec }) } } setCrews(decCrews) // Decrypt Entries const decEntries = [] if (data.entries) { for (const e of data.entries) { const dec = await decryptJson(e.encryptedData, e.iv, e.tag, keyBuffer) decEntries.push({ payloadId: e.payloadId, ...dec }) } } setEntries(decEntries) // Decrypt Photos const decPhotos = [] if (data.photos) { for (const p of data.photos) { const dec = await decryptJson(p.encryptedData, p.iv, p.tag, keyBuffer) decPhotos.push({ payloadId: p.payloadId, entryId: p.entryId, image: dec.image, caption: dec.caption || '', updatedAt: p.updatedAt }) } } setPhotos(decPhotos) // Decrypt GPS Tracks const decGpsTracks = [] if (data.gpsTracks) { for (const tr of data.gpsTracks) { const dec = await decryptJson(tr.encryptedData, tr.iv, tr.tag, keyBuffer) decGpsTracks.push({ entryId: tr.entryId, waypoints: dec.waypoints || dec || [], filename: dec.filename || 'track.gpx' }) } } setGpsTracks(decGpsTracks) trackPlausibleEvent(PlausibleEvents.PUBLIC_LINK_OPENED) } catch (err: any) { console.error(err) setError(err.message || 'Failed to decrypt logbook details.') } finally { setLoading(false) } } const toggleLanguage = () => { cycleAppLanguage(i18n) } if (loading) { return (

{isGermanLocale(i18n.language) ? 'Lade freigegebenes Logbuch...' : 'Loading shared logbook...'}

) } if (error) { return (

{isGermanLocale(i18n.language) ? 'Verbindungsfehler' : 'Access Error'}

{error}

) } return (
{/* Top Banner indicating read-only shared view */}

{logbookTitle}

{isGermanLocale(i18n.language) ? 'Schreibgeschützte Ansicht (Ende-zu-Ende verschlüsselt)' : 'Read-Only View (End-to-End Encrypted)'}

{activeTab === 'logs' && ( )} {activeTab === 'vessel' && ( )} {activeTab === 'crew' && ( )}
) }