diff --git a/web/source/package.json b/web/source/package.json
index 4467265f2..412c92329 100644
--- a/web/source/package.json
+++ b/web/source/package.json
@@ -24,6 +24,7 @@
"factor-bundle": "^2.5.0",
"from2-string": "^1.1.0",
"icssify": "^2.0.0",
+ "is-plain-object": "^5.0.0",
"js-file-download": "^0.4.12",
"modern-normalize": "^1.1.0",
"photoswipe": "^5.3.0",
diff --git a/web/source/settings-panel/lib/languages.js b/web/source/settings-panel/components/languages.jsx
similarity index 100%
rename from web/source/settings-panel/lib/languages.js
rename to web/source/settings-panel/components/languages.jsx
diff --git a/web/source/settings-panel/lib/api/index.js b/web/source/settings-panel/lib/api/index.js
index 23aa07a96..6240f9455 100644
--- a/web/source/settings-panel/lib/api/index.js
+++ b/web/source/settings-panel/lib/api/index.js
@@ -19,6 +19,7 @@
"use strict";
const Promise = require("bluebird");
+const { isPlainObject } = require("is-plain-object");
const { APIError } = require("../errors");
const { setInstanceInfo } = require("../../redux/reducers/instances").actions;
@@ -47,7 +48,13 @@ function apiCall(method, route, payload, type="json") {
} else if (type == "form") {
const formData = new FormData();
Object.entries(payload).forEach(([key, val]) => {
- formData.set(key, val);
+ if (isPlainObject(val)) {
+ Object.entries(val).forEach(([key2, val2]) => {
+ formData.set(`${key}[${key2}]`, val2);
+ });
+ } else {
+ formData.set(key, val);
+ }
});
body = formData;
}
diff --git a/web/source/settings-panel/lib/api/user.js b/web/source/settings-panel/lib/api/user.js
index 2af800d41..32309a2f8 100644
--- a/web/source/settings-panel/lib/api/user.js
+++ b/web/source/settings-panel/lib/api/user.js
@@ -24,6 +24,38 @@ const d = require("dotty");
const user = require("../../redux/reducers/user").actions;
module.exports = function ({ apiCall }) {
+ function updateCredentials(selector, {formKeys=[], renamedKeys=[], fileKeys=[]}) {
+ return function (dispatch, getState) {
+ return Promise.try(() => {
+ const state = selector(getState());
+
+ const update = {};
+
+ formKeys.forEach((key) => {
+ d.put(update, key, d.get(state, key));
+ });
+
+ renamedKeys.forEach(([sendKey, intKey]) => {
+ d.put(update, sendKey, d.get(state, intKey));
+ });
+
+ fileKeys.forEach((key) => {
+ let file = d.get(state, `${key}File`);
+ if (file != undefined) {
+ d.put(update, key, file);
+ }
+ });
+
+ console.log(update);
+
+ return dispatch(apiCall("PATCH", "/api/v1/accounts/update_credentials", update, "form"));
+ }).then((account) => {
+ console.log(account);
+ return dispatch(user.setAccount(account));
+ });
+ };
+ }
+
return {
fetchAccount: function fetchAccount() {
return function (dispatch, _getState) {
@@ -34,39 +66,17 @@ module.exports = function ({ apiCall }) {
});
};
},
- updateAccount: function updateAccount() {
- const formKeys = ["display_name", "locked"];
+ updateProfile: function updateProfile() {
+ const formKeys = ["display_name", "locked", "source"];
const renamedKeys = [["note", "source.note"]];
const fileKeys = ["header", "avatar"];
- return function (dispatch, getState) {
- return Promise.try(() => {
- const { account } = getState().user;
+ return updateCredentials((state) => state.user.profile, {formKeys, renamedKeys, fileKeys});
+ },
+ updateSettings: function updateProfile() {
+ const formKeys = ["source"];
- 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));
- });
- };
+ return updateCredentials((state) => state.user.settings, {formKeys});
}
};
};
\ No newline at end of file
diff --git a/web/source/settings-panel/lib/form-fields.js b/web/source/settings-panel/lib/form-fields.js
new file mode 100644
index 000000000..dfdd93086
--- /dev/null
+++ b/web/source/settings-panel/lib/form-fields.js
@@ -0,0 +1,50 @@
+/*
+ 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 .
+*/
+
+"use strict";
+
+const d = require("dotty");
+
+module.exports = function(dispatch, setter, obj) {
+ return {
+ onTextChange: function (key) {
+ return function (e) {
+ dispatch(setter([key, e.target.value]));
+ };
+ },
+
+ onCheckChange: function (key) {
+ return function (e) {
+ dispatch(setter([key, e.target.checked]));
+ };
+ },
+
+ onFileChange: function (key) {
+ return function (e) {
+ let old = d.get(obj, 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(setter([key, objectURL]));
+ dispatch(setter([`${key}File`, file]));
+ };
+ }
+ };
+};
diff --git a/web/source/settings-panel/old/user/posts.js b/web/source/settings-panel/old/user/posts.js
deleted file mode 100644
index 333a8ae24..000000000
--- a/web/source/settings-panel/old/user/posts.js
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- 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 .
-*/
-
-"use strict";
-
-const React = require("react");
-const Promise = require("bluebird");
-
-const Languages = require("./languages");
-const Submit = require("../../lib/submit");
-
-module.exports = function Posts({oauth, account}) {
- const [errorMsg, setError] = React.useState("");
- const [statusMsg, setStatus] = React.useState("");
-
- const [language, setLanguage] = React.useState("");
- const [privacy, setPrivacy] = React.useState("");
- const [format, setFormat] = React.useState("");
- const [sensitive, setSensitive] = React.useState(false);
-
- React.useEffect(() => {
- if (account.source) {
- setLanguage(account.source.language.toUpperCase());
- setPrivacy(account.source.privacy);
- setSensitive(account.source.sensitive ? account.source.sensitive : false);
- setFormat(account.source.status_format ? account.source.status_format : "plain");
- }
-
- }, [account, setSensitive, setPrivacy]);
-
- const submit = (e) => {
- e.preventDefault();
-
- setStatus("PATCHing");
- setError("");
- return Promise.try(() => {
- let formDataInfo = new FormData();
-
- formDataInfo.set("source[language]", language);
- formDataInfo.set("source[privacy]", privacy);
- formDataInfo.set("source[sensitive]", sensitive);
- formDataInfo.set("source[status_format]", format);
-
- return oauth.apiRequest("/api/v1/accounts/update_credentials", "PATCH", formDataInfo, "form");
- }).then((json) => {
- setStatus("Saved!");
- setLanguage(json.source.language.toUpperCase());
- setPrivacy(json.source.privacy);
- setSensitive(json.source.sensitive ? json.source.sensitive : false);
- setFormat(json.source.status_format ? json.source.status_format : "plain");
- }).catch((e) => {
- setError(e.message);
- setStatus("");
- });
- };
-
- return (
-
- );
-};
diff --git a/web/source/settings-panel/redux/reducers/user.js b/web/source/settings-panel/redux/reducers/user.js
index 1cc4e894e..673ed33d9 100644
--- a/web/source/settings-panel/redux/reducers/user.js
+++ b/web/source/settings-panel/redux/reducers/user.js
@@ -24,13 +24,22 @@ const d = require("dotty");
module.exports = createSlice({
name: "user",
initialState: {
+ profile: {},
+ settings: {}
},
reducers: {
setAccount: (state, {payload}) => {
- state.account = payload;
+ state.profile = payload;
+ // /user/settings only needs a copy of the 'source' obj
+ state.settings = {
+ source: payload.source
+ };
},
- setAccountVal: (state, {payload: [key, val]}) => {
- d.put(state.account, key, val);
+ setProfileVal: (state, {payload: [key, val]}) => {
+ d.put(state.profile, key, val);
+ },
+ setSettingsVal: (state, {payload: [key, val]}) => {
+ d.put(state.settings, key, val);
}
}
});
\ No newline at end of file
diff --git a/web/source/settings-panel/style.css b/web/source/settings-panel/style.css
index e2d6f20c6..68350239c 100644
--- a/web/source/settings-panel/style.css
+++ b/web/source/settings-panel/style.css
@@ -177,48 +177,11 @@ input, select, textarea {
) !important;
}
-.user-profile {
+section.with-sidebar > div {
display: flex;
flex-direction: column;
gap: 1rem;
- .overview {
- display: grid;
- grid-template-columns: 1fr auto;
-
- .basic {
- margin-top: -4.5rem;
-
- .avatar {
- height: 5rem;
- width: 5rem;
- }
-
- .displayname {
- font-size: 1.3rem;
- padding-top: 0;
- padding-bottom: 0;
- margin-top: 0.7rem;
- }
- }
-
- .files {
- padding: 1rem;
-
- h3 {
- margin-top: 0;
- }
-
- div:first-child {
- margin-bottom: 1rem;
- }
-
- span {
- font-style: italic;
- }
- }
- }
-
input, textarea {
width: 100%;
line-height: 1.5rem;
@@ -236,6 +199,7 @@ input, select, textarea {
input:invalid {
border-color: red;
}
+
textarea {
width: 100%;
height: 8rem;
@@ -245,29 +209,6 @@ input, select, textarea {
margin-bottom: 0.5rem;
}
- img {
- display: flex;
- justify-content: center;
- align-items: center;
- border: $boxshadow_border;
- box-shadow: $box-shadow;
- object-fit: cover;
- border-radius: 0.2rem;
- box-sizing: border-box;
- margin-bottom: 0.5rem;
- }
-
- .avatarpreview {
- height: 8.5rem;
- width: 8.5rem;
- }
-
- .headerpreview {
- width: 100%;
- aspect-ratio: 3 / 1;
- overflow: hidden;
- }
-
.moreinfolink {
font-size: 0.9em;
}
@@ -307,3 +248,60 @@ input, select, textarea {
gap: 0.4rem;
}
}
+
+.user-profile {
+ .overview {
+ display: grid;
+ grid-template-columns: 70% 30%;
+
+ .basic {
+ margin-top: -4.5rem;
+
+ .avatar {
+ height: 5rem;
+ width: 5rem;
+ }
+
+ .displayname {
+ font-size: 1.3rem;
+ padding-top: 0;
+ padding-bottom: 0;
+ margin-top: 0.7rem;
+ }
+ }
+
+ .files {
+ margin: 1rem;
+ margin-right: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+
+ div.picker {
+ width: 100%;
+ display: flex;
+
+ span {
+ flex: 1 1 auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding: 0.3rem 0;
+ }
+ }
+
+ h3 {
+ margin-top: 0;
+ margin-bottom: 0.5rem;
+ }
+
+ div:first-child {
+ margin-bottom: 1rem;
+ }
+
+ span {
+ font-style: italic;
+ }
+ }
+ }
+}
diff --git a/web/source/settings-panel/user/profile.js b/web/source/settings-panel/user/profile.js
index b3ac44dc3..a74623be7 100644
--- a/web/source/settings-panel/user/profile.js
+++ b/web/source/settings-panel/user/profile.js
@@ -21,103 +21,82 @@
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 formFields = require("../lib/form-fields");
const user = require("../redux/reducers/user").actions;
module.exports = function UserProfile() {
const dispatch = Redux.useDispatch();
- const account = Redux.useSelector(state => state.user.account);
+ const account = Redux.useSelector(state => state.user.profile);
+
+ const { onTextChange, onCheckChange, onFileChange } = formFields(dispatch, user.setProfileVal, account);
const [errorMsg, setError] = React.useState("");
const [statusMsg, setStatus] = React.useState("");
- function onTextChange(key) {
- return function (e) {
- dispatch(user.setAccountVal([key, e.target.value]));
- };
- }
-
- 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();
-
+ function submit() {
setStatus("PATCHing");
setError("");
return Promise.try(() => {
- return dispatch(api.user.updateAccount());
+ return dispatch(api.user.updateProfile());
}).then(() => {
setStatus("Saved!");
}).catch((e) => {
setError(e.message);
setStatus("");
});
- };
+ }
return (
Profile
-
-
-
-
-
-
-
{account.display_name.trim().length > 0 ? account.display_name : account.username}
-
@{account.username}
-
+
+
+
+
+
+
+
{account.display_name.trim().length > 0 ? account.display_name : account.username}
+
@{account.username}
+
Header
-
-
{account.headerFile ? account.headerFile.name : "no file selected"}
-
+
+
+ {account.headerFile ? account.headerFile.name : "no file selected"}
+
+
Avatar
-
-
{account.avatarFile ? account.avatarFile.name : "no file selected"}
-
+
+
+ {account.avatarFile ? account.avatarFile.name : "no file selected"}
+
+
-
+
-
+
-
+
-
+
);
};
\ No newline at end of file
diff --git a/web/source/settings-panel/user/settings.js b/web/source/settings-panel/user/settings.js
index d72690124..c6ad08ffc 100644
--- a/web/source/settings-panel/user/settings.js
+++ b/web/source/settings-panel/user/settings.js
@@ -18,6 +18,70 @@
"use strict";
+const Promise = require("bluebird");
+const React = require("react");
+const Redux = require("react-redux");
+
+const api = require("../lib/api");
+const formFields = require("../lib/form-fields");
+const user = require("../redux/reducers/user").actions;
+
+const Languages = require("../components/languages");
+const Submit = require("../components/submit");
+
module.exports = function UserSettings() {
- return "user settings";
+ const dispatch = Redux.useDispatch();
+ const account = Redux.useSelector(state => state.user.settings);
+
+ const { onTextChange, onCheckChange } = formFields(dispatch, user.setSettingsVal, account);
+
+ const [errorMsg, setError] = React.useState("");
+ const [statusMsg, setStatus] = React.useState("");
+
+ function submit() {
+ setStatus("PATCHing");
+ setError("");
+ return Promise.try(() => {
+ return dispatch(api.user.updateSettings());
+ }).then(() => {
+ setStatus("Saved!");
+ }).catch((e) => {
+ setError(e.message);
+ setStatus("");
+ });
+ }
+
+ return (
+
+
Post settings
+
+
+
+
+
+
+
+
+
+
+
+
+ );
};
\ No newline at end of file
diff --git a/web/source/yarn.lock b/web/source/yarn.lock
index ee744b63a..e5e1122d0 100644
--- a/web/source/yarn.lock
+++ b/web/source/yarn.lock
@@ -3670,6 +3670,11 @@ is-plain-obj@^2.0.0, is-plain-obj@^2.1.0:
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+is-plain-object@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+ integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+
is-regex@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"