mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 23:02:24 -06:00 
			
		
		
		
	[feature] Add first iteration of a user panel at /user (#736)
		
	* start work on user panel * parse source first before checking if empty form * newline * set avi + header nicely * add posts settings * render signin a bit nicer on mobile * return OK json on successful change * return unauthorized on bad password * clarify message on insecure password * make login a bit prettier * add alt text + border round image previews * add logout button * add password change * styling updates * redirect /auth/edit to /user * update tests * fix validation tests * better labels, link to more info * make submit button generic component * move submit button inside forms * add autocomplete labels to password fields * fix indentation (thx eslint) * update eslintrc * eslint: no-unescaped-entities * initial deduplication between user and admin panel * add default status/post format setting * user panel styling for inputs * update user panel styling, include normalize css * add placeholder text * input padding Co-authored-by: f0x <f0x@cthu.lu>
This commit is contained in:
		
					parent
					
						
							
								4722970a5b
							
						
					
				
			
			
				commit
				
					
						117888cf59
					
				
			
		
					 29 changed files with 931 additions and 202 deletions
				
			
		
							
								
								
									
										137
									
								
								web/source/panels/user/basic.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								web/source/panels/user/basic.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,137 @@
 | 
			
		|||
/*
 | 
			
		||||
   GoToSocial
 | 
			
		||||
   Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
 | 
			
		||||
 | 
			
		||||
   This program is free software: you can redistribute it and/or modify
 | 
			
		||||
   it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
   the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
   (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
   This program is distributed in the hope that it will be useful,
 | 
			
		||||
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
   GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
   You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
const React = require("react");
 | 
			
		||||
const Promise = require("bluebird");
 | 
			
		||||
 | 
			
		||||
const Submit = require("../../lib/submit");
 | 
			
		||||
 | 
			
		||||
module.exports = function Basic({oauth, account}) {
 | 
			
		||||
	const [errorMsg, setError] = React.useState("");
 | 
			
		||||
	const [statusMsg, setStatus] = React.useState("");
 | 
			
		||||
 | 
			
		||||
	const [headerFile, setHeaderFile] = React.useState(undefined);
 | 
			
		||||
	const [headerSrc, setHeaderSrc] = React.useState("");
 | 
			
		||||
 | 
			
		||||
	const [avatarFile, setAvatarFile] = React.useState(undefined);
 | 
			
		||||
	const [avatarSrc, setAvatarSrc] = React.useState("");
 | 
			
		||||
 | 
			
		||||
	const [displayName, setDisplayName] = React.useState("");
 | 
			
		||||
	const [bio, setBio] = React.useState("");
 | 
			
		||||
	const [locked, setLocked] = React.useState(false);
 | 
			
		||||
 | 
			
		||||
	React.useEffect(() => {
 | 
			
		||||
		setHeaderSrc(account.header);
 | 
			
		||||
		setAvatarSrc(account.avatar);
 | 
			
		||||
 | 
			
		||||
		setDisplayName(account.display_name);
 | 
			
		||||
		setBio(account.source ? account.source.note : "");
 | 
			
		||||
		setLocked(account.locked);
 | 
			
		||||
	}, [account, setHeaderSrc, setAvatarSrc, setDisplayName, setBio, setLocked]);
 | 
			
		||||
 | 
			
		||||
	const headerOnChange = (e) => {
 | 
			
		||||
		setHeaderFile(e.target.files[0]);
 | 
			
		||||
		setHeaderSrc(URL.createObjectURL(e.target.files[0]));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const avatarOnChange = (e) => {
 | 
			
		||||
		setAvatarFile(e.target.files[0]);
 | 
			
		||||
		setAvatarSrc(URL.createObjectURL(e.target.files[0]));
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const submit = (e) => {
 | 
			
		||||
		e.preventDefault();
 | 
			
		||||
 | 
			
		||||
		setStatus("PATCHing");
 | 
			
		||||
		setError("");
 | 
			
		||||
		return Promise.try(() => {
 | 
			
		||||
			let formDataInfo = new FormData();
 | 
			
		||||
 | 
			
		||||
			if (headerFile) {
 | 
			
		||||
				formDataInfo.set("header", headerFile);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (avatarFile) {
 | 
			
		||||
				formDataInfo.set("avatar", avatarFile);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			formDataInfo.set("display_name", displayName);
 | 
			
		||||
			formDataInfo.set("note", bio);
 | 
			
		||||
			formDataInfo.set("locked", locked);
 | 
			
		||||
 | 
			
		||||
			return oauth.apiRequest("/api/v1/accounts/update_credentials", "PATCH", formDataInfo, "form");
 | 
			
		||||
		}).then((json) => {
 | 
			
		||||
			setStatus("Saved!");
 | 
			
		||||
 | 
			
		||||
			setHeaderSrc(json.header);
 | 
			
		||||
			setAvatarSrc(json.avatar);
 | 
			
		||||
 | 
			
		||||
			setDisplayName(json.display_name);
 | 
			
		||||
			setBio(json.source.note);
 | 
			
		||||
			setLocked(json.locked);
 | 
			
		||||
		}).catch((e) => {
 | 
			
		||||
			setError(e.message);
 | 
			
		||||
			setStatus("");
 | 
			
		||||
		});
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<section className="basic">
 | 
			
		||||
			<h1>@{account.username}'s Profile Info</h1>
 | 
			
		||||
			<form>
 | 
			
		||||
				<div className="labelinput">
 | 
			
		||||
					<label htmlFor="header">Header</label>
 | 
			
		||||
					<div className="border">
 | 
			
		||||
						<img className="headerpreview" src={headerSrc} alt={headerSrc ? `header image for ${account.username}` : "None set"}/>
 | 
			
		||||
						<div>
 | 
			
		||||
							<label htmlFor="header" className="file-input button">Browse…</label>
 | 
			
		||||
							<span>{headerFile ? headerFile.name : ""}</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<input className="hidden" id="header" type="file" accept="image/*" onChange={headerOnChange}/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div className="labelinput">
 | 
			
		||||
					<label htmlFor="avatar">Avatar</label>
 | 
			
		||||
					<div className="border">
 | 
			
		||||
						<img className="avatarpreview" src={avatarSrc} alt={headerSrc ? `avatar image for ${account.username}` : "None set"}/>
 | 
			
		||||
						<div>
 | 
			
		||||
							<label htmlFor="avatar" className="file-input button">Browse…</label>
 | 
			
		||||
							<span>{avatarFile ? avatarFile.name : ""}</span>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<input className="hidden" id="avatar" type="file" accept="image/*" onChange={avatarOnChange}/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div className="labelinput">
 | 
			
		||||
					<label htmlFor="displayname">Display Name</label>
 | 
			
		||||
					<input id="displayname" type="text" value={displayName} onChange={(e) => setDisplayName(e.target.value)} placeHolder="A GoToSocial user"/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div className="labelinput">
 | 
			
		||||
					<label htmlFor="bio">Bio</label>
 | 
			
		||||
					<textarea id="bio" value={bio} onChange={(e) => setBio(e.target.value)} placeHolder="Just trying out GoToSocial, my pronouns are they/them and I like sloths."/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div className="labelcheckbox">
 | 
			
		||||
					<label htmlFor="locked">Manually approve follow requests</label>
 | 
			
		||||
					<input id="locked" type="checkbox" checked={locked} onChange={(e) => setLocked(e.target.checked)}/>
 | 
			
		||||
				</div>
 | 
			
		||||
				<Submit onClick={submit} label="Save profile info" errorMsg={errorMsg} statusMsg={statusMsg}/>
 | 
			
		||||
			</form>
 | 
			
		||||
		</section>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue