profile.js (5201B)
1 /* 2 GoToSocial 3 Copyright (C) GoToSocial Authors admin@gotosocial.org 4 SPDX-License-Identifier: AGPL-3.0-or-later 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 "use strict"; 21 22 const React = require("react"); 23 24 const query = require("../lib/query"); 25 26 const { 27 useTextInput, 28 useFileInput, 29 useBoolInput, 30 useFieldArrayInput 31 } = require("../lib/form"); 32 33 const useFormSubmit = require("../lib/form/submit"); 34 const { useWithFormContext, FormContext } = require("../lib/form/context"); 35 36 const { 37 TextInput, 38 TextArea, 39 FileInput, 40 Checkbox 41 } = require("../components/form/inputs"); 42 43 const FormWithData = require("../lib/form/form-with-data"); 44 const FakeProfile = require("../components/fake-profile"); 45 const MutationButton = require("../components/form/mutation-button"); 46 47 module.exports = function UserProfile() { 48 return ( 49 <FormWithData 50 dataQuery={query.useVerifyCredentialsQuery} 51 DataForm={UserProfileForm} 52 /> 53 ); 54 }; 55 56 function UserProfileForm({ data: profile }) { 57 /* 58 User profile update form keys 59 - bool bot 60 - bool locked 61 - string display_name 62 - string note 63 - file avatar 64 - file header 65 - bool enable_rss 66 - string custom_css (if enabled) 67 */ 68 69 const { data: instance } = query.useInstanceQuery(); 70 const instanceConfig = React.useMemo(() => { 71 return { 72 allowCustomCSS: instance?.configuration?.accounts?.allow_custom_css === true, 73 maxPinnedFields: instance?.configuration?.accounts?.max_profile_fields ?? 6 74 }; 75 }, [instance]); 76 77 const form = { 78 avatar: useFileInput("avatar", { withPreview: true }), 79 header: useFileInput("header", { withPreview: true }), 80 displayName: useTextInput("display_name", { source: profile }), 81 note: useTextInput("note", { source: profile, valueSelector: (p) => p.source?.note }), 82 customCSS: useTextInput("custom_css", { source: profile }), 83 bot: useBoolInput("bot", { source: profile }), 84 locked: useBoolInput("locked", { source: profile }), 85 enableRSS: useBoolInput("enable_rss", { source: profile }), 86 fields: useFieldArrayInput("fields_attributes", { 87 defaultValue: profile?.source?.fields, 88 length: instanceConfig.maxPinnedFields 89 }), 90 }; 91 92 const [submitForm, result] = useFormSubmit(form, query.useUpdateCredentialsMutation(), { 93 onFinish: () => { 94 form.avatar.reset(); 95 form.header.reset(); 96 } 97 }); 98 99 return ( 100 <form className="user-profile" onSubmit={submitForm}> 101 <h1>Profile</h1> 102 <div className="overview"> 103 <FakeProfile 104 avatar={form.avatar.previewValue ?? profile.avatar} 105 header={form.header.previewValue ?? profile.header} 106 display_name={form.displayName.value ?? profile.username} 107 username={profile.username} 108 role={profile.role} 109 /> 110 <div className="files"> 111 <div> 112 <h3>Header</h3> 113 <FileInput 114 field={form.header} 115 accept="image/*" 116 /> 117 </div> 118 <div> 119 <h3>Avatar</h3> 120 <FileInput 121 field={form.avatar} 122 accept="image/*" 123 /> 124 </div> 125 </div> 126 </div> 127 <TextInput 128 field={form.displayName} 129 label="Name" 130 placeholder="A GoToSocial user" 131 /> 132 <TextArea 133 field={form.note} 134 label="Bio" 135 placeholder="Just trying out GoToSocial, my pronouns are they/them and I like sloths." 136 rows={8} 137 /> 138 <Checkbox 139 field={form.locked} 140 label="Manually approve follow requests" 141 /> 142 <Checkbox 143 field={form.enableRSS} 144 label="Enable RSS feed of Public posts" 145 /> 146 <b>Profile fields</b> 147 <ProfileFields 148 field={form.fields} 149 /> 150 {!instanceConfig.allowCustomCSS ? null : 151 <TextArea 152 field={form.customCSS} 153 label="Custom CSS" 154 className="monospace" 155 rows={8} 156 > 157 <a href="https://docs.gotosocial.org/en/latest/user_guide/custom_css" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about custom profile CSS (opens in a new tab)</a> 158 </TextArea> 159 } 160 <MutationButton label="Save profile info" result={result} /> 161 </form> 162 ); 163 } 164 165 function ProfileFields({ field: formField }) { 166 return ( 167 <div className="fields"> 168 <FormContext.Provider value={formField.ctx}> 169 {formField.value.map((data, i) => ( 170 <Field 171 key={i} 172 index={i} 173 data={data} 174 /> 175 ))} 176 </FormContext.Provider> 177 </div> 178 ); 179 } 180 181 function Field({ index, data }) { 182 const form = useWithFormContext(index, { 183 name: useTextInput("name", { defaultValue: data.name }), 184 value: useTextInput("value", { defaultValue: data.value }) 185 }); 186 187 return ( 188 <div className="entry"> 189 <TextInput 190 field={form.name} 191 placeholder="Name" 192 /> 193 <TextInput 194 field={form.value} 195 placeholder="Value" 196 /> 197 </div> 198 ); 199 }