diff --git a/web/source/settings/components/form/text.jsx b/web/source/settings/components/form/text.jsx index 3cd7e2d25..27c7e7d58 100644 --- a/web/source/settings/components/form/text.jsx +++ b/web/source/settings/components/form/text.jsx @@ -50,7 +50,14 @@ module.exports = function useTextInput({name, Name}, {validator, defaultValue="" [name]: text, [`${name}Ref`]: textRef, [`set${Name}`]: setText, - [`${name}Valid`]: valid + [`${name}Valid`]: valid, + + name, + value: text, + ref: textRef, + setter: setText, + valid, + hasChanged: () => text != defaultValue } ]; }; \ No newline at end of file diff --git a/web/source/settings/lib/query/custom-emoji.js b/web/source/settings/lib/query/custom-emoji.js index e62931cb2..0455fea7b 100644 --- a/web/source/settings/lib/query/custom-emoji.js +++ b/web/source/settings/lib/query/custom-emoji.js @@ -20,16 +20,9 @@ const Promise = require("bluebird"); +const { unwrapRes } = require("./lib"); const base = require("./base"); -function unwrap(res) { - if (res.error != undefined) { - throw res.error; - } else { - return res.data; - } -} - const endpoints = (build) => ({ getAllEmoji: build.query({ query: (params = {}) => ({ @@ -132,7 +125,7 @@ const endpoints = (build) => ({ filter: `domain:${domain},shortcode:${emoji.shortcode}`, limit: 1 } - }).then(unwrap); + }).then(unwrapRes); }).then(([lookup]) => { if (lookup == undefined) { throw "not found"; } @@ -152,7 +145,7 @@ const endpoints = (build) => ({ url: `/api/v1/admin/custom_emojis/${lookup.id}`, asForm: true, body: body - }).then(unwrap); + }).then(unwrapRes); }).then((res) => { data.push([emoji.shortcode, res]); }).catch((e) => { diff --git a/web/source/settings/lib/query/index.js b/web/source/settings/lib/query/index.js index 4bd0ff100..be98794dd 100644 --- a/web/source/settings/lib/query/index.js +++ b/web/source/settings/lib/query/index.js @@ -20,5 +20,6 @@ module.exports = { ...require("./base"), - ...require("./custom-emoji.js") + ...require("./custom-emoji.js"), + ...require("./user-settings") }; \ No newline at end of file diff --git a/web/source/settings/lib/query/lib.js b/web/source/settings/lib/query/lib.js new file mode 100644 index 000000000..7e63ad741 --- /dev/null +++ b/web/source/settings/lib/query/lib.js @@ -0,0 +1,29 @@ +/* + 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"; + +module.exports = { + unwrapRes(res) { + if (res.error != undefined) { + throw res.error; + } else { + return res.data; + } + } +}; \ No newline at end of file diff --git a/web/source/settings/lib/query/user-settings.js b/web/source/settings/lib/query/user-settings.js new file mode 100644 index 000000000..a374d049f --- /dev/null +++ b/web/source/settings/lib/query/user-settings.js @@ -0,0 +1,37 @@ +/* + 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 Promise = require("bluebird"); + +// const { unwrapRes } = require("./lib"); +const base = require("./base"); + +const endpoints = (build) => ({ + updateCredentials: build.mutation({ + query: (formData) => ({ + method: "PATCH", + url: `/api/v1/accounts/update_credentials`, + asForm: true, + body: formData + }) + }) +}); + +module.exports = base.injectEndpoints({endpoints}); \ No newline at end of file diff --git a/web/source/settings/user/profile.js b/web/source/settings/user/profile.js index 8e3b0b9e8..76162fe16 100644 --- a/web/source/settings/user/profile.js +++ b/web/source/settings/user/profile.js @@ -21,59 +21,70 @@ const React = require("react"); const Redux = require("react-redux"); -const Submit = require("../components/submit"); - -const api = require("../lib/api"); -const user = require("../redux/reducers/user").actions; -const submit = require("../lib/submit"); - -const FakeProfile = require("../components/fake-profile"); -const { formFields } = require("../components/form-fields"); +const query = require("../lib/query"); const { - TextInput, - TextArea, - Checkbox, - File -} = formFields(user.setProfileVal, (state) => state.user.profile); + useTextInput +} = require("../components/form"); + +const FakeProfile = require("../components/fake-profile"); +const syncpipe = require("syncpipe"); +const MutationButton = require("../components/mutation-button"); module.exports = function UserProfile() { - const dispatch = Redux.useDispatch(); - const instance = Redux.useSelector(state => state.instances.current); + const allowCustomCSS = Redux.useSelector(state => state.instances.current.configuration.accounts.allow_custom_css); + const profile = Redux.useSelector(state => state.user.profile); - const allowCustomCSS = instance.configuration.accounts.allow_custom_css; + /* + User profile update form keys + - bool bot + - bool locked + - string display_name + - string note + - file avatar + - file header + - string source[privacy] + - bool source[sensitive] + - string source[language] + - string source[status_format] + - bool enable_rss + - string custom_css (if enabled) + */ - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); + const form = { + display_name: useTextInput("displayName", {defaultValue: profile.display_name}) + }; - const saveProfile = submit( - () => dispatch(api.user.updateProfile()), - {setStatus, setError} - ); + const [result, submitForm] = useFormSubmit(form, query.useUpdateCredentialsMutation()); return ( -
+

Profile

- + {/* */}

Header

- + /> */}

Avatar

- + /> */}
- + {/* Learn more about custom profile CSS (opens in a new tab) } - + */} + + + ); +}; + +function FormTextInput({label, placeHolder, field}) { + let [onChange, _reset, {value, ref}] = field; + + return ( +
+
); -}; \ No newline at end of file +} + +function useFormSubmit(form, [mutationQuery, result]) { + return [ + result, + function submitForm(e) { + e.preventDefault(); + + // transform the field definitions into an object with just their values + let updatedFields = 0; + const mutationData = syncpipe(form, [ + (_) => Object.entries(_), + (_) => _.map(([key, field]) => { + let data = field[2]; // [onChange, reset, {}] + if (data.hasChanged()) { + return [key, data.value]; + } else { + return null; + } + }), + (_) => _.filter((value) => value != null), + (_) => { + updatedFields = _.length; + return _; + }, + (_) => Object.fromEntries(_) + ]); + + if (updatedFields > 0) { + return mutationQuery(mutationData); + } + }, + ]; +} + +// function useForm(formSpec) { +// const form = {}; + +// Object.entries(formSpec).forEach(([name, cfg]) => { +// const [useTypedInput, defaultValue] = cfg; + +// form[name] = useTypedInput(name, ); +// }); + +// form.submit = function submitForm() { + +// }; + +// return form; +// } \ No newline at end of file