Write on the margins of the internet. Powered by the AT Protocol.
margin.at
extension
web
atproto
comments
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}