(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

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}