snow-editor

small markdown and org-mode editor
Log | Files | Refs | README

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 }