Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
at ui-refactor 145 lines 4.3 kB view raw
1import { useState } from "react"; 2import { updateProfile } from "../api/client"; 3 4export default function EditProfileModal({ profile, onClose, onUpdate }) { 5 const [bio, setBio] = useState(profile?.bio || ""); 6 const [website, setWebsite] = useState(profile?.website || ""); 7 const [links, setLinks] = useState(profile?.links || []); 8 const [newLink, setNewLink] = useState(""); 9 const [saving, setSaving] = useState(false); 10 const [error, setError] = useState(null); 11 12 const handleSubmit = async (e) => { 13 e.preventDefault(); 14 setSaving(true); 15 setError(null); 16 17 try { 18 await updateProfile({ bio, website, links }); 19 onUpdate(); 20 onClose(); 21 } catch (err) { 22 setError(err.message); 23 } finally { 24 setSaving(false); 25 } 26 }; 27 28 const addLink = () => { 29 if (!newLink) return; 30 31 if (!links.includes(newLink)) { 32 setLinks([...links, newLink]); 33 setNewLink(""); 34 setError(null); 35 } 36 }; 37 38 const removeLink = (index) => { 39 setLinks(links.filter((_, i) => i !== index)); 40 }; 41 42 return ( 43 <div className="modal-overlay" onClick={onClose}> 44 <div className="modal-container" onClick={(e) => e.stopPropagation()}> 45 <div className="modal-header"> 46 <h2>Edit Profile</h2> 47 <button className="modal-close-btn" onClick={onClose}> 48 <svg 49 width="20" 50 height="20" 51 viewBox="0 0 24 24" 52 fill="none" 53 stroke="currentColor" 54 strokeWidth="2" 55 strokeLinecap="round" 56 strokeLinejoin="round" 57 > 58 <line x1="18" y1="6" x2="6" y2="18" /> 59 <line x1="6" y1="6" x2="18" y2="18" /> 60 </svg> 61 </button> 62 </div> 63 <form onSubmit={handleSubmit} className="modal-body"> 64 {error && <div className="error-message">{error}</div>} 65 66 <div className="form-group"> 67 <label>Bio</label> 68 <textarea 69 className="input" 70 value={bio} 71 onChange={(e) => setBio(e.target.value)} 72 placeholder="Tell us about yourself..." 73 rows={4} 74 maxLength={5000} 75 /> 76 <div className="char-count">{bio.length}/5000</div> 77 </div> 78 79 <div className="form-group"> 80 <label>Website</label> 81 <input 82 type="url" 83 className="input" 84 value={website} 85 onChange={(e) => setWebsite(e.target.value)} 86 placeholder="https://example.com" 87 maxLength={1000} 88 /> 89 </div> 90 91 <div className="form-group"> 92 <label>Links</label> 93 <div className="links-input-group"> 94 <input 95 type="url" 96 className="input" 97 value={newLink} 98 onChange={(e) => setNewLink(e.target.value)} 99 placeholder="Add a link (e.g. GitHub, LinkedIn)..." 100 onKeyDown={(e) => 101 e.key === "Enter" && (e.preventDefault(), addLink()) 102 } 103 /> 104 <button 105 type="button" 106 className="btn btn-secondary" 107 onClick={addLink} 108 > 109 Add 110 </button> 111 </div> 112 <ul className="links-list"> 113 {links.map((link, i) => ( 114 <li key={i} className="link-item"> 115 <span>{link}</span> 116 <button 117 type="button" 118 className="btn-icon-sm" 119 onClick={() => removeLink(i)} 120 > 121 × 122 </button> 123 </li> 124 ))} 125 </ul> 126 </div> 127 128 <div className="modal-actions"> 129 <button 130 type="button" 131 className="btn btn-secondary" 132 onClick={onClose} 133 disabled={saving} 134 > 135 Cancel 136 </button> 137 <button type="submit" className="btn btn-primary" disabled={saving}> 138 {saving ? "Saving..." : "Save Profile"} 139 </button> 140 </div> 141 </form> 142 </div> 143 </div> 144 ); 145}