SharedViewPage.jsx (2818B)
1 import { useEffect, useState } from 'react'; 2 import { useParams } from 'react-router-dom'; 3 import EditorLayout from '../components/EditorLayout.jsx'; 4 import StatusBadge from '../components/StatusBadge.jsx'; 5 import { ApiError, fetchViewDocument, friendlyErrorMessage } from '../lib/api.js'; 6 import { downloadDocument } from '../lib/download.js'; 7 import { STR } from '../lib/strings.js'; 8 import LinkErrorPage from './LinkErrorPage.jsx'; 9 10 export default function SharedViewPage() { 11 const { token } = useParams(); 12 const [doc, setDoc] = useState(null); 13 const [error, setError] = useState(null); 14 const [loading, setLoading] = useState(true); 15 16 useEffect(() => { 17 let cancelled = false; 18 19 (async () => { 20 setLoading(true); 21 setError(null); 22 try { 23 const data = await fetchViewDocument(token); 24 if (!cancelled) setDoc(data); 25 } catch (err) { 26 if (!cancelled) setError(err); 27 } finally { 28 if (!cancelled) setLoading(false); 29 } 30 })(); 31 32 return () => { 33 cancelled = true; 34 }; 35 }, [token]); 36 37 if (loading) { 38 return ( 39 <div className="app"> 40 <p className="page-loading">{STR.LOADING_DOCUMENT}</p> 41 </div> 42 ); 43 } 44 45 if (error instanceof ApiError) { 46 if (error.status === 410) { 47 return ( 48 <LinkErrorPage 49 title={STR.LINK_EXPIRED_TITLE} 50 message={STR.LINK_EXPIRED_VIEW} 51 /> 52 ); 53 } 54 if (error.status === 404) { 55 return ( 56 <LinkErrorPage 57 title={STR.DOCUMENT_NOT_FOUND_TITLE} 58 message={friendlyErrorMessage(error)} 59 /> 60 ); 61 } 62 } 63 64 if (error || !doc) { 65 return ( 66 <LinkErrorPage 67 title={STR.LOAD_ERROR_TITLE} 68 message={friendlyErrorMessage(error)} 69 /> 70 ); 71 } 72 73 const saveLabel = doc.mode === 'org' ? STR.DOWNLOAD_ORG : STR.DOWNLOAD_MD; 74 75 return ( 76 <div className="app"> 77 <header className="app-header"> 78 <div className="app-header-text"> 79 <div className="app-header-top"> 80 <h1 className="app-title">{doc.title}</h1> 81 <StatusBadge variant="shared">{STR.BADGE_SHARED}</StatusBadge> 82 <StatusBadge variant="readonly">{STR.BADGE_READONLY}</StatusBadge> 83 </div> 84 <p className="app-subtitle">{STR.SHARED_VIEW}</p> 85 </div> 86 <div className="toolbar"> 87 <button 88 type="button" 89 className="btn" 90 onClick={() => downloadDocument(doc.content, doc.mode, doc.title)} 91 > 92 {saveLabel} 93 </button> 94 </div> 95 </header> 96 97 <EditorLayout mode={doc.mode} content={doc.content} previewOnly showEditor={false} /> 98 99 <footer className="app-footer"> 100 <p className="app-meta">{STR.FOOTER_SHARED}</p> 101 </footer> 102 </div> 103 ); 104 }