use redux state for profile form

This commit is contained in:
f0x 2022-09-11 23:02:23 +02:00
parent 3ce9dfe7d6
commit 2bd18790cf
5 changed files with 71 additions and 45 deletions

View file

@ -18,6 +18,7 @@
"browserlist": "^1.0.1",
"create-error": "^0.3.1",
"css-extract": "^2.0.0",
"dotty": "^0.1.2",
"eslint-plugin-react": "^7.24.0",
"express": "^4.18.1",
"factor-bundle": "^2.5.0",

View file

@ -19,10 +19,11 @@
"use strict";
const Promise = require("bluebird");
const d = require("dotty");
const user = require("../../redux/reducers/user").actions;
module.exports = function({apiCall}) {
module.exports = function ({ apiCall }) {
return {
fetchAccount: function fetchAccount() {
return function (dispatch, _getState) {
@ -33,10 +34,34 @@ module.exports = function({apiCall}) {
});
};
},
updateAccount: function updateAccount(newAccount) {
return function (dispatch, _getSate) {
updateAccount: function updateAccount() {
const formKeys = ["display_name", "locked"];
const renamedKeys = [["note", "source.note"]];
const fileKeys = ["header", "avatar"];
return function (dispatch, getState) {
return Promise.try(() => {
return dispatch(apiCall("PATCH", "/api/v1/accounts/update_credentials", newAccount, "form"));
const { account } = getState().user;
const update = {};
formKeys.forEach((key) => {
d.put(update, key, d.get(account, key));
update[key] = account[key];
});
renamedKeys.forEach(([sendKey, intKey]) => {
d.put(update, sendKey, d.get(account, intKey));
});
fileKeys.forEach((key) => {
let file = d.get(account, `${key}File`);
if (file != undefined) {
d.put(update, key, file);
}
});
return dispatch(apiCall("PATCH", "/api/v1/accounts/update_credentials", update, "form"));
}).then((account) => {
console.log(account);
return dispatch(user.setAccount(account));

View file

@ -19,6 +19,7 @@
"use strict";
const {createSlice} = require("@reduxjs/toolkit");
const d = require("dotty");
module.exports = createSlice({
name: "user",
@ -27,6 +28,9 @@ module.exports = createSlice({
reducers: {
setAccount: (state, {payload}) => {
state.account = payload;
},
setAccountVal: (state, {payload: [key, val]}) => {
d.put(state.account, key, val);
}
}
});

View file

@ -21,10 +21,12 @@
const Promise = require("bluebird");
const React = require("react");
const Redux = require("react-redux");
const d = require("dotty");
const Submit = require("../components/submit");
const api = require("../lib/api");
const user = require("../redux/reducers/user").actions;
module.exports = function UserProfile() {
const dispatch = Redux.useDispatch();
@ -33,29 +35,30 @@ module.exports = function UserProfile() {
const [errorMsg, setError] = React.useState("");
const [statusMsg, setStatus] = React.useState("");
const [headerFile, setHeaderFile] = React.useState(undefined);
const [avatarFile, setAvatarFile] = React.useState(undefined);
const [displayName, setDisplayName] = React.useState("");
const [bio, setBio] = React.useState("");
const [locked, setLocked] = React.useState(false);
React.useEffect(() => {
setDisplayName(account.display_name);
setBio(account.source ? account.source.note : "");
setLocked(account.locked);
}, []);
const headerOnChange = (e) => {
setHeaderFile(e.target.files[0]);
// setHeaderSrc(URL.createObjectURL(e.target.files[0]));
function onTextChange(key) {
return function (e) {
dispatch(user.setAccountVal([key, e.target.value]));
};
}
const avatarOnChange = (e) => {
setAvatarFile(e.target.files[0]);
// setAvatarSrc(URL.createObjectURL(e.target.files[0]));
function onCheckChange(key) {
return function (e) {
dispatch(user.setAccountVal([key, e.target.checked]));
};
}
function onFileChange(key) {
return function (e) {
let old = d.get(account, key);
if (old != undefined) {
URL.revokeObjectURL(old); // no error revoking a non-Object URL as provided by instance
}
let file = e.target.files[0];
let objectURL = URL.createObjectURL(file);
dispatch(user.setAccountVal([key, objectURL]));
dispatch(user.setAccountVal([`${key}File`, file]));
};
}
const submit = (e) => {
e.preventDefault();
@ -63,21 +66,7 @@ module.exports = function UserProfile() {
setStatus("PATCHing");
setError("");
return Promise.try(() => {
let payload = {
display_name: displayName,
note: bio,
locked: locked
};
if (headerFile) {
payload.header = headerFile;
}
if (avatarFile) {
payload.avatar = avatarFile;
}
return dispatch(api.user.updateAccount(payload));
return dispatch(api.user.updateAccount());
}).then(() => {
setStatus("Saved!");
}).catch((e) => {
@ -105,26 +94,28 @@ module.exports = function UserProfile() {
<div>
<h3>Header</h3>
<label htmlFor="header" className="file-input button">Browse</label>
<span>{headerFile ? headerFile.name : "no file selected"}</span>
<span>{account.headerFile ? account.headerFile.name : "no file selected"}</span>
<input className="hidden" id="header" type="file" accept="image/*" onChange={onFileChange("header")}/>
</div>
<div>
<h3>Avatar</h3>
<label htmlFor="avatar" className="file-input button">Browse</label>
<span>{avatarFile ? avatarFile.name : "no file selected"}</span>
<span>{account.avatarFile ? account.avatarFile.name : "no file selected"}</span>
<input className="hidden" id="avatar" type="file" accept="image/*" onChange={onFileChange("avatar")}/>
</div>
</div>
</div>
<div className="labelinput">
<label htmlFor="displayname">Name</label>
<input id="displayname" type="text" value={displayName} onChange={(e) => setDisplayName(e.target.value)} placeholder="A GoToSocial user"/>
<input id="displayname" type="text" value={account.display_name} onChange={onTextChange("display_name")} 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."/>
<textarea id="bio" value={account.source.note} onChange={onTextChange("source.note")} 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)}/>
<input id="locked" type="checkbox" checked={account.locked} onChange={onCheckChange("locked")}/>
</div>
<Submit onClick={submit} label="Save profile info" errorMsg={errorMsg} statusMsg={statusMsg}/>
</div>

View file

@ -2528,6 +2528,11 @@ domain-browser@^1.2.0:
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
dotty@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/dotty/-/dotty-0.1.2.tgz#512d44cc4111a724931226259297f235e8484f6f"
integrity sha512-V0EWmKeH3DEhMwAZ+8ZB2Ao4OK6p++Z0hsDtZq3N0+0ZMVqkzrcEGROvOnZpLnvBg5PTNG23JEDLAm64gPaotQ==
duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2, duplexer2@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"