ShareModal.jsx (4587B)
1 import { useState } from 'react'; 2 import { createDocument, friendlyErrorMessage, toAbsoluteUrl } from '../lib/api.js'; 3 import { EXPIRY_OPTIONS, formatExpiryDate, STR } from '../lib/strings.js'; 4 5 export default function ShareModal({ open, onClose, title, mode, content }) { 6 const [docTitle, setDocTitle] = useState(title); 7 const [expiresIn, setExpiresIn] = useState('7d'); 8 const [loading, setLoading] = useState(false); 9 const [error, setError] = useState(''); 10 const [result, setResult] = useState(null); 11 const [copied, setCopied] = useState(''); 12 13 if (!open) return null; 14 15 const handleSubmit = async (event) => { 16 event.preventDefault(); 17 setLoading(true); 18 setError(''); 19 setResult(null); 20 21 try { 22 const data = await createDocument({ 23 title: docTitle, 24 mode, 25 content, 26 expiresIn, 27 }); 28 setResult({ 29 ...data, 30 viewUrlAbs: toAbsoluteUrl(data.viewUrl), 31 editUrlAbs: toAbsoluteUrl(data.editUrl), 32 }); 33 } catch (err) { 34 setError(friendlyErrorMessage(err)); 35 } finally { 36 setLoading(false); 37 } 38 }; 39 40 const copyLink = async (url, key) => { 41 try { 42 await navigator.clipboard.writeText(url); 43 setCopied(key); 44 window.setTimeout(() => setCopied(''), 2000); 45 } catch { 46 setError(STR.COPY_FAILED); 47 } 48 }; 49 50 return ( 51 <div className="modal-backdrop" role="presentation" onClick={onClose}> 52 <div 53 className="modal share-panel" 54 role="dialog" 55 aria-labelledby="share-title" 56 aria-modal="true" 57 onClick={(e) => e.stopPropagation()} 58 > 59 <h2 id="share-title" className="modal__title"> 60 {STR.SHARE_DOCUMENT} 61 </h2> 62 63 {!result ? ( 64 <form onSubmit={handleSubmit}> 65 <label className="share-field"> 66 <span>{STR.TITLE}</span> 67 <input 68 type="text" 69 value={docTitle} 70 onChange={(e) => setDocTitle(e.target.value)} 71 maxLength={200} 72 /> 73 </label> 74 75 <fieldset className="share-field"> 76 <legend>{STR.LINK_VALIDITY}</legend> 77 {EXPIRY_OPTIONS.map((opt) => ( 78 <label key={opt.value} className="share-radio"> 79 <input 80 type="radio" 81 name="expiresIn" 82 value={opt.value} 83 checked={expiresIn === opt.value} 84 onChange={() => setExpiresIn(opt.value)} 85 /> 86 {opt.label} 87 </label> 88 ))} 89 </fieldset> 90 91 {error && ( 92 <p className="share-error" role="alert"> 93 {error} 94 </p> 95 )} 96 97 <div className="modal__actions"> 98 <button type="button" className="btn btn-ghost" onClick={onClose}> 99 {STR.CANCEL} 100 </button> 101 <button type="submit" className="btn" disabled={loading}> 102 {loading ? STR.CREATING : STR.CREATE_LINKS} 103 </button> 104 </div> 105 </form> 106 ) : ( 107 <div className="share-result"> 108 <p className="share-result__expiry">{formatExpiryDate(result.expiresAt)}</p> 109 110 <label className="share-field"> 111 <span>{STR.VIEW_LINK}</span> 112 <div className="share-copy-row"> 113 <input type="text" readOnly value={result.viewUrlAbs} /> 114 <button 115 type="button" 116 className="btn" 117 onClick={() => copyLink(result.viewUrlAbs, 'view')} 118 > 119 {copied === 'view' ? STR.COPIED : STR.COPY} 120 </button> 121 </div> 122 </label> 123 124 <label className="share-field"> 125 <span>{STR.EDIT_LINK}</span> 126 <div className="share-copy-row"> 127 <input type="text" readOnly value={result.editUrlAbs} /> 128 <button 129 type="button" 130 className="btn" 131 onClick={() => copyLink(result.editUrlAbs, 'edit')} 132 > 133 {copied === 'edit' ? STR.COPIED : STR.COPY} 134 </button> 135 </div> 136 </label> 137 138 <div className="modal__actions"> 139 <button type="button" className="btn" onClick={onClose}> 140 {STR.CLOSE} 141 </button> 142 </div> 143 </div> 144 )} 145 </div> 146 </div> 147 ); 148 }