implement old/user/profile.js
This commit is contained in:
parent
10faf67b51
commit
43f2e988b9
|
@ -24,6 +24,7 @@
|
||||||
"factor-bundle": "^2.5.0",
|
"factor-bundle": "^2.5.0",
|
||||||
"from2-string": "^1.1.0",
|
"from2-string": "^1.1.0",
|
||||||
"icssify": "^2.0.0",
|
"icssify": "^2.0.0",
|
||||||
|
"is-plain-object": "^5.0.0",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"modern-normalize": "^1.1.0",
|
"modern-normalize": "^1.1.0",
|
||||||
"photoswipe": "^5.3.0",
|
"photoswipe": "^5.3.0",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Promise = require("bluebird");
|
const Promise = require("bluebird");
|
||||||
|
const { isPlainObject } = require("is-plain-object");
|
||||||
|
|
||||||
const { APIError } = require("../errors");
|
const { APIError } = require("../errors");
|
||||||
const { setInstanceInfo } = require("../../redux/reducers/instances").actions;
|
const { setInstanceInfo } = require("../../redux/reducers/instances").actions;
|
||||||
|
@ -47,7 +48,13 @@ function apiCall(method, route, payload, type="json") {
|
||||||
} else if (type == "form") {
|
} else if (type == "form") {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
Object.entries(payload).forEach(([key, val]) => {
|
Object.entries(payload).forEach(([key, val]) => {
|
||||||
|
if (isPlainObject(val)) {
|
||||||
|
Object.entries(val).forEach(([key2, val2]) => {
|
||||||
|
formData.set(`${key}[${key2}]`, val2);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
formData.set(key, val);
|
formData.set(key, val);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
body = formData;
|
body = formData;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,38 @@ const d = require("dotty");
|
||||||
const user = require("../../redux/reducers/user").actions;
|
const user = require("../../redux/reducers/user").actions;
|
||||||
|
|
||||||
module.exports = function ({ apiCall }) {
|
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 {
|
return {
|
||||||
fetchAccount: function fetchAccount() {
|
fetchAccount: function fetchAccount() {
|
||||||
return function (dispatch, _getState) {
|
return function (dispatch, _getState) {
|
||||||
|
@ -34,39 +66,17 @@ module.exports = function ({ apiCall }) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
updateAccount: function updateAccount() {
|
updateProfile: function updateProfile() {
|
||||||
const formKeys = ["display_name", "locked"];
|
const formKeys = ["display_name", "locked", "source"];
|
||||||
const renamedKeys = [["note", "source.note"]];
|
const renamedKeys = [["note", "source.note"]];
|
||||||
const fileKeys = ["header", "avatar"];
|
const fileKeys = ["header", "avatar"];
|
||||||
|
|
||||||
return function (dispatch, getState) {
|
return updateCredentials((state) => state.user.profile, {formKeys, renamedKeys, fileKeys});
|
||||||
return Promise.try(() => {
|
},
|
||||||
const { account } = getState().user;
|
updateSettings: function updateProfile() {
|
||||||
|
const formKeys = ["source"];
|
||||||
|
|
||||||
const update = {};
|
return updateCredentials((state) => state.user.settings, {formKeys});
|
||||||
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"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]));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
"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 (
|
|
||||||
<section className="posts">
|
|
||||||
<h1>Post Settings</h1>
|
|
||||||
<form>
|
|
||||||
<div className="labelselect">
|
|
||||||
<label htmlFor="language">Default post language</label>
|
|
||||||
<select id="language" autoComplete="language" value={language} onChange={(e) => setLanguage(e.target.value)}>
|
|
||||||
<Languages />
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="labelselect">
|
|
||||||
<label htmlFor="privacy">Default post privacy</label>
|
|
||||||
<select id="privacy" value={privacy} onChange={(e) => setPrivacy(e.target.value)}>
|
|
||||||
<option value="private">Private / followers-only)</option>
|
|
||||||
<option value="unlisted">Unlisted</option>
|
|
||||||
<option value="public">Public</option>
|
|
||||||
</select>
|
|
||||||
<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#privacy-settings" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post privacy settings (opens in a new tab)</a>
|
|
||||||
</div>
|
|
||||||
<div className="labelselect">
|
|
||||||
<label htmlFor="format">Default post format</label>
|
|
||||||
<select id="format" value={format} onChange={(e) => setFormat(e.target.value)}>
|
|
||||||
<option value="plain">Plain (default)</option>
|
|
||||||
<option value="markdown">Markdown</option>
|
|
||||||
</select>
|
|
||||||
<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#input-types" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post format settings (opens in a new tab)</a>
|
|
||||||
</div>
|
|
||||||
<div className="labelcheckbox">
|
|
||||||
<label htmlFor="sensitive">Mark my posts as sensitive by default</label>
|
|
||||||
<input id="sensitive" type="checkbox" checked={sensitive} onChange={(e) => setSensitive(e.target.checked)}/>
|
|
||||||
</div>
|
|
||||||
<Submit onClick={submit} label="Save post settings" errorMsg={errorMsg} statusMsg={statusMsg}/>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -24,13 +24,22 @@ const d = require("dotty");
|
||||||
module.exports = createSlice({
|
module.exports = createSlice({
|
||||||
name: "user",
|
name: "user",
|
||||||
initialState: {
|
initialState: {
|
||||||
|
profile: {},
|
||||||
|
settings: {}
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setAccount: (state, {payload}) => {
|
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]}) => {
|
setProfileVal: (state, {payload: [key, val]}) => {
|
||||||
d.put(state.account, key, val);
|
d.put(state.profile, key, val);
|
||||||
|
},
|
||||||
|
setSettingsVal: (state, {payload: [key, val]}) => {
|
||||||
|
d.put(state.settings, key, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -177,48 +177,11 @@ input, select, textarea {
|
||||||
) !important;
|
) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-profile {
|
section.with-sidebar > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
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 {
|
input, textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
|
@ -236,6 +199,7 @@ input, select, textarea {
|
||||||
input:invalid {
|
input:invalid {
|
||||||
border-color: red;
|
border-color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 8rem;
|
height: 8rem;
|
||||||
|
@ -245,29 +209,6 @@ input, select, textarea {
|
||||||
margin-bottom: 0.5rem;
|
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 {
|
.moreinfolink {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
@ -307,3 +248,60 @@ input, select, textarea {
|
||||||
gap: 0.4rem;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,59 +21,34 @@
|
||||||
const Promise = require("bluebird");
|
const Promise = require("bluebird");
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
const Redux = require("react-redux");
|
const Redux = require("react-redux");
|
||||||
const d = require("dotty");
|
|
||||||
|
|
||||||
const Submit = require("../components/submit");
|
const Submit = require("../components/submit");
|
||||||
|
|
||||||
const api = require("../lib/api");
|
const api = require("../lib/api");
|
||||||
|
const formFields = require("../lib/form-fields");
|
||||||
const user = require("../redux/reducers/user").actions;
|
const user = require("../redux/reducers/user").actions;
|
||||||
|
|
||||||
module.exports = function UserProfile() {
|
module.exports = function UserProfile() {
|
||||||
const dispatch = Redux.useDispatch();
|
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 [errorMsg, setError] = React.useState("");
|
||||||
const [statusMsg, setStatus] = React.useState("");
|
const [statusMsg, setStatus] = React.useState("");
|
||||||
|
|
||||||
function onTextChange(key) {
|
function submit() {
|
||||||
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();
|
|
||||||
|
|
||||||
setStatus("PATCHing");
|
setStatus("PATCHing");
|
||||||
setError("");
|
setError("");
|
||||||
return Promise.try(() => {
|
return Promise.try(() => {
|
||||||
return dispatch(api.user.updateAccount());
|
return dispatch(api.user.updateProfile());
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
setStatus("Saved!");
|
setStatus("Saved!");
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
setStatus("");
|
setStatus("");
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="user-profile">
|
<div className="user-profile">
|
||||||
|
@ -81,11 +56,11 @@ module.exports = function UserProfile() {
|
||||||
<div className="overview">
|
<div className="overview">
|
||||||
<div className="profile">
|
<div className="profile">
|
||||||
<div className="headerimage">
|
<div className="headerimage">
|
||||||
<img className="headerpreview" src={account.header} alt={account.header ? `header image for ${account.username}` : "None set"}/>
|
<img className="headerpreview" src={account.header} alt={account.header ? `header image for ${account.username}` : "None set"} />
|
||||||
</div>
|
</div>
|
||||||
<div className="basic">
|
<div className="basic">
|
||||||
<div id="profile-basic-filler2"></div>
|
<div id="profile-basic-filler2"></div>
|
||||||
<span className="avatar"><img className="avatarpreview" src={account.avatar} alt={account.avatar ? `avatar image for ${account.username}` : "None set"}/></span>
|
<span className="avatar"><img className="avatarpreview" src={account.avatar} alt={account.avatar ? `avatar image for ${account.username}` : "None set"} /></span>
|
||||||
<div className="displayname">{account.display_name.trim().length > 0 ? account.display_name : account.username}</div>
|
<div className="displayname">{account.display_name.trim().length > 0 ? account.display_name : account.username}</div>
|
||||||
<div className="username"><span>@{account.username}</span></div>
|
<div className="username"><span>@{account.username}</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,31 +68,35 @@ module.exports = function UserProfile() {
|
||||||
<div className="files">
|
<div className="files">
|
||||||
<div>
|
<div>
|
||||||
<h3>Header</h3>
|
<h3>Header</h3>
|
||||||
<label htmlFor="header" className="file-input button">Browse…</label>
|
<div className="picker">
|
||||||
|
<label htmlFor="header" className="file-input button">Browse</label>
|
||||||
<span>{account.headerFile ? account.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>
|
||||||
|
<input className="hidden" id="header" type="file" accept="image/*" onChange={onFileChange("header")} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3>Avatar</h3>
|
<h3>Avatar</h3>
|
||||||
<label htmlFor="avatar" className="file-input button">Browse…</label>
|
<div className="picker">
|
||||||
|
<label htmlFor="avatar" className="file-input button">Browse</label>
|
||||||
<span>{account.avatarFile ? account.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>
|
||||||
|
<input className="hidden" id="avatar" type="file" accept="image/*" onChange={onFileChange("avatar")} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="labelinput">
|
<div className="labelinput">
|
||||||
<label htmlFor="displayname">Name</label>
|
<label htmlFor="displayname">Name</label>
|
||||||
<input id="displayname" type="text" value={account.display_name} onChange={onTextChange("display_name")} placeholder="A GoToSocial user"/>
|
<input id="displayname" type="text" value={account.display_name} onChange={onTextChange("display_name")} placeholder="A GoToSocial user" />
|
||||||
</div>
|
</div>
|
||||||
<div className="labelinput">
|
<div className="labelinput">
|
||||||
<label htmlFor="bio">Bio</label>
|
<label htmlFor="bio">Bio</label>
|
||||||
<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."/>
|
<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>
|
||||||
<div className="labelcheckbox">
|
<div className="labelcheckbox">
|
||||||
<label htmlFor="locked">Manually approve follow requests?</label>
|
<label htmlFor="locked">Manually approve follow requests?</label>
|
||||||
<input id="locked" type="checkbox" checked={account.locked} onChange={onCheckChange("locked")}/>
|
<input id="locked" type="checkbox" checked={account.locked} onChange={onCheckChange("locked")} />
|
||||||
</div>
|
</div>
|
||||||
<Submit onClick={submit} label="Save profile info" errorMsg={errorMsg} statusMsg={statusMsg}/>
|
<Submit onClick={submit} label="Save profile info" errorMsg={errorMsg} statusMsg={statusMsg} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -18,6 +18,70 @@
|
||||||
|
|
||||||
"use strict";
|
"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() {
|
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 (
|
||||||
|
<div className="user-settings">
|
||||||
|
<h1>Post settings</h1>
|
||||||
|
<div className="labelselect">
|
||||||
|
<label htmlFor="language">Default post language</label>
|
||||||
|
<select id="language" autoComplete="language" value={account.source.language.toUpperCase()} onChange={onTextChange("source.language")}>
|
||||||
|
<Languages />
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="labelselect">
|
||||||
|
<label htmlFor="privacy">Default post privacy</label>
|
||||||
|
<select id="privacy" value={account.source.privacy} onChange={onTextChange("source.privacy")}>
|
||||||
|
<option value="private">Private / followers-only)</option>
|
||||||
|
<option value="unlisted">Unlisted</option>
|
||||||
|
<option value="public">Public</option>
|
||||||
|
</select>
|
||||||
|
<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#privacy-settings" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post privacy settings (opens in a new tab)</a>
|
||||||
|
</div>
|
||||||
|
<div className="labelselect">
|
||||||
|
<label htmlFor="format">Default post format</label>
|
||||||
|
<select id="format" value={account.source.format} onChange={onTextChange("source.format")}>
|
||||||
|
<option value="plain">Plain (default)</option>
|
||||||
|
<option value="markdown">Markdown</option>
|
||||||
|
</select>
|
||||||
|
<a href="https://docs.gotosocial.org/en/latest/user_guide/posts/#input-types" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about post format settings (opens in a new tab)</a>
|
||||||
|
</div>
|
||||||
|
<div className="labelcheckbox">
|
||||||
|
<label htmlFor="sensitive">Mark my posts as sensitive by default</label>
|
||||||
|
<input id="sensitive" type="checkbox" checked={account.source.sensitive} onChange={onCheckChange("source.sensitive")}/>
|
||||||
|
</div>
|
||||||
|
<Submit onClick={submit} label="Save post settings" errorMsg={errorMsg} statusMsg={statusMsg}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
|
@ -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"
|
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
|
||||||
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
|
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:
|
is-regex@^1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
||||||
|
|
Loading…
Reference in New Issue