diff options
Diffstat (limited to 'web/source/panels')
-rw-r--r-- | web/source/panels/admin/README.md | 21 | ||||
-rw-r--r-- | web/source/panels/admin/blocks.js | 318 | ||||
-rw-r--r-- | web/source/panels/admin/index.js | 64 | ||||
-rw-r--r-- | web/source/panels/admin/settings.js | 182 | ||||
-rw-r--r-- | web/source/panels/admin/style.css | 106 | ||||
-rw-r--r-- | web/source/panels/base.css | 67 | ||||
-rw-r--r-- | web/source/panels/lib/oauth.js | 227 | ||||
-rw-r--r-- | web/source/panels/lib/panel.js | 134 | ||||
-rw-r--r-- | web/source/panels/user/basic.js | 151 | ||||
-rw-r--r-- | web/source/panels/user/index.js | 76 | ||||
-rw-r--r-- | web/source/panels/user/languages.js | 98 | ||||
-rw-r--r-- | web/source/panels/user/posts.js | 107 | ||||
-rw-r--r-- | web/source/panels/user/security.js | 80 | ||||
-rw-r--r-- | web/source/panels/user/style.css | 118 |
14 files changed, 0 insertions, 1749 deletions
diff --git a/web/source/panels/admin/README.md b/web/source/panels/admin/README.md deleted file mode 100644 index 9a4572270..000000000 --- a/web/source/panels/admin/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# GoToSocial Admin Panel - -Standalone web admin panel for [GoToSocial](https://github.com/superseriousbusiness/gotosocial). - -A public hosted instance is also available at https://gts.superseriousbusiness.org/admin/, so you can fill your own instance URL in there. - -## Installation -Build requirements: some version of Node.js with npm, -``` -git clone https://github.com/superseriousbusiness/gotosocial-admin.git && cd gotosocial-admin -npm install -node index.js -``` -All processed build output will now be in `public/`, which you can copy over to a folder in your GoToSocial installation like `web/assets/admin`, or serve elsewhere. -No further configuration is required, authentication happens through normal OAUTH flow. - -## Development -Follow the installation steps, but run `NODE_ENV=development node index.js` to start the livereloading dev server instead. - -## License, donations -[AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html). If you want to support my work, you can: <a href="https://liberapay.com/f0x/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
\ No newline at end of file diff --git a/web/source/panels/admin/blocks.js b/web/source/panels/admin/blocks.js deleted file mode 100644 index b12eb50a9..000000000 --- a/web/source/panels/admin/blocks.js +++ /dev/null @@ -1,318 +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 Promise = require("bluebird"); -const React = require("react"); -const fileDownload = require("js-file-download"); - -function sortBlocks(blocks) { - return blocks.sort((a, b) => { // alphabetical sort - return a.domain.localeCompare(b.domain); - }); -} - -function deduplicateBlocks(blocks) { - let a = new Map(); - blocks.forEach((block) => { - a.set(block.id, block); - }); - return Array.from(a.values()); -} - -module.exports = function Blocks({oauth}) { - const [blocks, setBlocks] = React.useState([]); - const [info, setInfo] = React.useState("Fetching blocks"); - const [errorMsg, setError] = React.useState(""); - const [checked, setChecked] = React.useState(new Set()); - - React.useEffect(() => { - Promise.try(() => { - return oauth.apiRequest("/api/v1/admin/domain_blocks", undefined, undefined, "GET"); - }).then((json) => { - setInfo(""); - setError(""); - setBlocks(sortBlocks(json)); - }).catch((e) => { - setError(e.message); - setInfo(""); - }); - }, []); - - let blockList = blocks.map((block) => { - function update(e) { - let newChecked = new Set(checked.values()); - if (e.target.checked) { - newChecked.add(block.id); - } else { - newChecked.delete(block.id); - } - setChecked(newChecked); - } - - return ( - <React.Fragment key={block.id}> - <div><input type="checkbox" onChange={update} checked={checked.has(block.id)}></input></div> - <div>{block.domain}</div> - <div>{(new Date(block.created_at)).toLocaleString()}</div> - </React.Fragment> - ); - }); - - function clearChecked() { - setChecked(new Set()); - } - - function undoChecked() { - let amount = checked.size; - if(confirm(`Are you sure you want to remove ${amount} block(s)?`)) { - setInfo(""); - Promise.map(Array.from(checked.values()), (block) => { - console.log("deleting", block); - return oauth.apiRequest(`/api/v1/admin/domain_blocks/${block}`, "DELETE"); - }).then((res) => { - console.log(res); - setInfo(`Deleted ${amount} blocks: ${res.map((a) => a.domain).join(", ")}`); - }).catch((e) => { - setError(e); - }); - - let newBlocks = blocks.filter((block) => { - if (checked.size > 0 && checked.has(block.id)) { - checked.delete(block.id); - return false; - } else { - return true; - } - }); - setBlocks(newBlocks); - clearChecked(); - } - } - - return ( - <section className="blocks"> - <h1>Blocks</h1> - <div className="error accent">{errorMsg}</div> - <div>{info}</div> - <AddBlock oauth={oauth} blocks={blocks} setBlocks={setBlocks} /> - <h3>Blocks:</h3> - <div style={{display: "grid", gridTemplateColumns: "1fr auto"}}> - <span onClick={clearChecked} className="accent" style={{alignSelf: "end"}}>uncheck all</span> - <button onClick={undoChecked}>Unblock selected</button> - </div> - <div className="blocklist overflow"> - {blockList} - </div> - <BulkBlocking oauth={oauth} blocks={blocks} setBlocks={setBlocks}/> - </section> - ); -}; - -function BulkBlocking({oauth, blocks, setBlocks}) { - const [bulk, setBulk] = React.useState(""); - const [blockMap, setBlockMap] = React.useState(new Map()); - const [output, setOutput] = React.useState(); - - React.useEffect(() => { - let newBlockMap = new Map(); - blocks.forEach((block) => { - newBlockMap.set(block.domain, block); - }); - setBlockMap(newBlockMap); - }, [blocks]); - - const fileRef = React.useRef(); - - function error(e) { - setOutput(<div className="error accent">{e}</div>); - throw e; - } - - function fileUpload() { - let reader = new FileReader(); - reader.addEventListener("load", (e) => { - try { - // TODO: use validatem? - let json = JSON.parse(e.target.result); - json.forEach((block) => { - console.log("block:", block); - }); - } catch(e) { - error(e.message); - } - }); - reader.readAsText(fileRef.current.files[0]); - } - - React.useEffect(() => { - if (fileRef && fileRef.current) { - fileRef.current.addEventListener("change", fileUpload); - } - return function cleanup() { - fileRef.current.removeEventListener("change", fileUpload); - }; - }); - - function textImport() { - Promise.try(() => { - if (bulk[0] == "[") { - // assume it's json - return JSON.parse(bulk); - } else { - return bulk.split("\n").map((val) => { - return { - domain: val.trim() - }; - }); - } - }).then((domains) => { - console.log(domains); - let before = domains.length; - setOutput(`Importing ${before} domain(s)`); - domains = domains.filter(({domain}) => { - return (domain != "" && !blockMap.has(domain)); - }); - setOutput(<span>{output}<br/>{`Deduplicated ${before - domains.length}/${before} with existing blocks, adding ${domains.length} block(s)`}</span>); - if (domains.length > 0) { - let data = new FormData(); - data.append("domains", new Blob([JSON.stringify(domains)], {type: "application/json"}), "import.json"); - return oauth.apiRequest("/api/v1/admin/domain_blocks?import=true", "POST", data, "form"); - } - }).then((json) => { - console.log("bulk import result:", json); - setBlocks(sortBlocks(deduplicateBlocks([...json, ...blocks]))); - }).catch((e) => { - error(e.message); - }); - } - - function textExport() { - setBulk(blocks.reduce((str, val) => { - if (typeof str == "object") { - return str.domain; - } else { - return str + "\n" + val.domain; - } - })); - } - - function jsonExport() { - Promise.try(() => { - return oauth.apiRequest("/api/v1/admin/domain_blocks?export=true", "GET"); - }).then((json) => { - fileDownload(JSON.stringify(json), "block-export.json"); - }).catch((e) => { - error(e); - }); - } - - function textAreaUpdate(e) { - setBulk(e.target.value); - } - - return ( - <React.Fragment> - <h3>Bulk import/export</h3> - <label htmlFor="bulk">Domains, one per line:</label> - <textarea value={bulk} rows={20} onChange={textAreaUpdate}></textarea> - <div className="controls"> - <button onClick={textImport}>Import All From Field</button> - <button onClick={textExport}>Export To Field</button> - <label className="button" htmlFor="upload">Upload .json</label> - <button onClick={jsonExport}>Download .json</button> - </div> - {output} - <input type="file" id="upload" className="hidden" ref={fileRef}></input> - </React.Fragment> - ); -} - -function AddBlock({oauth, blocks, setBlocks}) { - const [domain, setDomain] = React.useState(""); - const [type, setType] = React.useState("suspend"); - const [obfuscated, setObfuscated] = React.useState(false); - const [privateDescription, setPrivateDescription] = React.useState(""); - const [publicDescription, setPublicDescription] = React.useState(""); - - function addBlock() { - console.log(`${type}ing`, domain); - Promise.try(() => { - return oauth.apiRequest("/api/v1/admin/domain_blocks", "POST", { - domain: domain, - obfuscate: obfuscated, - private_comment: privateDescription, - public_comment: publicDescription - }, "json"); - }).then((json) => { - setDomain(""); - setPrivateDescription(""); - setPublicDescription(""); - setBlocks([json, ...blocks]); - }); - } - - function onDomainChange(e) { - setDomain(e.target.value); - } - - function onTypeChange(e) { - setType(e.target.value); - } - - function onKeyDown(e) { - if (e.key == "Enter") { - addBlock(); - } - } - - return ( - <React.Fragment> - <h3>Add Block:</h3> - <div className="addblock"> - <input id="domain" placeholder="instance" onChange={onDomainChange} value={domain} onKeyDown={onKeyDown} /> - <select value={type} onChange={onTypeChange}> - <option id="suspend">Suspend</option> - <option id="silence">Silence</option> - </select> - <button onClick={addBlock}>Add</button> - <div> - <label htmlFor="private">Private description:</label><br/> - <textarea id="private" value={privateDescription} onChange={(e) => setPrivateDescription(e.target.value)}></textarea> - </div> - <div> - <label htmlFor="public">Public description:</label><br/> - <textarea id="public" value={publicDescription} onChange={(e) => setPublicDescription(e.target.value)}></textarea> - </div> - <div className="single"> - <label htmlFor="obfuscate">Obfuscate:</label> - <input id="obfuscate" type="checkbox" value={obfuscated} onChange={(e) => setObfuscated(e.target.checked)}/> - </div> - </div> - </React.Fragment> - ); -} - -// function Blocklist() { -// return ( -// <section className="blocklists"> -// <h1>Blocklists</h1> -// </section> -// ); -// }
\ No newline at end of file diff --git a/web/source/panels/admin/index.js b/web/source/panels/admin/index.js deleted file mode 100644 index 0fc1601eb..000000000 --- a/web/source/panels/admin/index.js +++ /dev/null @@ -1,64 +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 Promise = require("bluebird"); -const React = require("react"); -const ReactDom = require("react-dom"); - -const createPanel = require("../lib/panel"); - -const Settings = require("./settings"); -const Blocks = require("./blocks"); - -require("../base.css"); -require("./style.css"); - -function AdminPanel({oauth}) { - /* - Features: (issue #78) - - [ ] Instance information updating - GET /api/v1/instance PATCH /api/v1/instance - - [ ] Domain block creation, viewing, and deletion - GET /api/v1/admin/domain_blocks - POST /api/v1/admin/domain_blocks - GET /api/v1/admin/domain_blocks/DOMAIN_BLOCK_ID, DELETE /api/v1/admin/domain_blocks/DOMAIN_BLOCK_ID - - [ ] Blocklist import/export - GET /api/v1/admin/domain_blocks?export=true - POST json file as form field domains to /api/v1/admin/domain_blocks - */ - - return ( - <React.Fragment> - <Logout oauth={oauth}/> - <Settings oauth={oauth} /> - <Blocks oauth={oauth}/> - </React.Fragment> - ); -} - -function Logout({oauth}) { - return ( - <div> - <button onClick={oauth.logout}>Logout</button> - </div> - ); -} - -createPanel("GoToSocial Admin Panel", ["admin"], AdminPanel);
\ No newline at end of file diff --git a/web/source/panels/admin/settings.js b/web/source/panels/admin/settings.js deleted file mode 100644 index c9f470464..000000000 --- a/web/source/panels/admin/settings.js +++ /dev/null @@ -1,182 +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 Promise = require("bluebird"); -const React = require("react"); - -module.exports = function Settings({oauth}) { - const [info, setInfo] = React.useState({}); - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState("Fetching instance info"); - - React.useEffect(() => { - Promise.try(() => { - return oauth.apiRequest("/api/v1/instance", "GET"); - }).then((json) => { - setInfo(json); - }).catch((e) => { - setError(e.message); - setStatus(""); - }); - }, []); - - function submit() { - setStatus("PATCHing"); - setError(""); - return Promise.try(() => { - let formDataInfo = new FormData(); - Object.entries(info).forEach(([key, val]) => { - if (key == "contact_account") { - key = "contact_username"; - val = val.username; - } - if (key == "email") { - key = "contact_email"; - } - if (typeof val != "object") { - formDataInfo.append(key, val); - } - }); - return oauth.apiRequest("/api/v1/instance", "PATCH", formDataInfo, "form"); - }).then((json) => { - setStatus("Config saved"); - console.log(json); - }).catch((e) => { - setError(e.message); - setStatus(""); - }); - } - - return ( - <section className="info login"> - <h1>Instance Information <button onClick={submit}>Save</button></h1> - <div className="error accent"> - {errorMsg} - </div> - <div> - {statusMsg} - </div> - <form onSubmit={(e) => e.preventDefault()}> - {editableObject(info)} - </form> - </section> - ); -}; - -function editableObject(obj, path=[]) { - const readOnlyKeys = ["uri", "version", "urls_streaming_api", "stats"]; - const hiddenKeys = ["contact_account_", "urls"]; - const explicitShownKeys = ["contact_account_username"]; - const implementedKeys = "title, contact_account_username, email, short_description, description, terms, avatar, header".split(", "); - const textareaKeys = ["short_description", "description"] - - let listing = Object.entries(obj).map(([key, val]) => { - let fullkey = [...path, key].join("_"); - - if ( - hiddenKeys.includes(fullkey) || - hiddenKeys.includes(path.join("_")+"_") // also match just parent path - ) { - if (!explicitShownKeys.includes(fullkey)) { - return null; - } - } - - if (Array.isArray(val)) { - // FIXME: handle this - } else if (typeof val == "object") { - return (<React.Fragment key={fullkey}> - {editableObject(val, [...path, key])} - </React.Fragment>); - } - - let isImplemented = ""; - if (!implementedKeys.includes(fullkey)) { - isImplemented = " notImplemented"; - } - - let isReadOnly = ( - readOnlyKeys.includes(fullkey) || - readOnlyKeys.includes(path.join("_")) || - isImplemented != "" - ); - - let label = key.replace(/_/g, " "); - if (path.length > 0) { - label = `\u00A0`.repeat(4 * path.length) + label; - } - - let inputProps; - let changeFunc; - if (val === true || val === false) { - inputProps = { - type: "checkbox", - defaultChecked: val, - disabled: isReadOnly - }; - changeFunc = (e) => e.target.checked; - } else if (val.length != 0 && !isNaN(val)) { - inputProps = { - type: "number", - defaultValue: val, - readOnly: isReadOnly - }; - changeFunc = (e) => e.target.value; - } else { - inputProps = { - type: "text", - defaultValue: val, - readOnly: isReadOnly - }; - changeFunc = (e) => e.target.value; - } - - function setRef(element) { - if (element != null) { - element.addEventListener("change", (e) => { - obj[key] = changeFunc(e); - }); - } - } - - let field; - if (textareaKeys.includes(fullkey)) { - field = <textarea className={isImplemented} ref={setRef} {...inputProps}></textarea> - } else { - field = <input className={isImplemented} ref={setRef} {...inputProps} /> - } - return ( - <React.Fragment key={fullkey}> - <label htmlFor={key} className="capitalize">{label}</label> - <div className={isImplemented}> - {field} - </div> - </React.Fragment> - ); - }); - return ( - <React.Fragment> - {path != "" && - <><b>{path}:</b> <span id="filler"></span></> - } - {listing} - </React.Fragment> - ); -}
\ No newline at end of file diff --git a/web/source/panels/admin/style.css b/web/source/panels/admin/style.css deleted file mode 100644 index 01195437f..000000000 --- a/web/source/panels/admin/style.css +++ /dev/null @@ -1,106 +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/>. -*/ - -section.info { - form { - grid-template-columns: auto 1fr; - width: calc(100% - 0.35rem); - - input { - width: 100%; - line-height: 1.5rem; - } - - label, input { - padding: 0.2rem 0.5rem; - } - - input[type=checkbox] { - justify-self: start; - width: initial; - } - - input:read-only { - border: none; - } - - input:invalid { - border-color: red; - } - } - - textarea { - width: 100%; - height: 8rem; - } - - h1 { - display: flex; - justify-content: space-between; - margin-bottom: 0.5rem; - } -} - -section.blocks { - .overflow { - max-height: 80vh; - overflow-y: auto; - } - - .blocklist { - display: grid; - grid-template-columns: auto 1fr auto; - grid-gap: 0.35rem 0; - - div { - background: rgb(70, 79, 88); - padding: 0.2rem 0.4rem; - } - } - - .addblock { - display: grid; - grid-template-columns: 1fr auto auto; - grid-gap: 0.35rem; - - input, select { - font-size: 1.2rem; - } - - input, select, textarea { - padding: 0.5rem; - } - - div { - grid-column: 1/4; - } - - div.single input { - width: initial; - } - } - - h3 { - margin-bottom: 0; - } - - .controls { - display: flex; - gap: 0.5rem; - } -} diff --git a/web/source/panels/base.css b/web/source/panels/base.css deleted file mode 100644 index 2d76ed080..000000000 --- a/web/source/panels/base.css +++ /dev/null @@ -1,67 +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/>. -*/ - -body { - grid-template-rows: auto 1fr; -} - -.capitalize { - text-transform: capitalize; -} - -section { - margin-bottom: 1rem; -} - -input, select, textarea { - box-sizing: border-box; -} - -.error { - font-weight: bold; -} - -.hidden { - display: none; -} - -.messagebutton { - margin-top: 1rem; - display: flex; - gap: 1rem; - align-items: center; - - button { - white-space: nowrap; - } -} - -.notImplemented { - border: 2px solid rgb(70, 79, 88); - background: repeating-linear-gradient( - -45deg, - #525c66, - #525c66 10px, - rgb(70, 79, 88) 10px, - rgb(70, 79, 88) 20px - ) !important; -} - -.mono { - font-family: monospace; -} diff --git a/web/source/panels/lib/oauth.js b/web/source/panels/lib/oauth.js deleted file mode 100644 index 3619dfa01..000000000 --- a/web/source/panels/lib/oauth.js +++ /dev/null @@ -1,227 +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 Promise = require("bluebird"); - -function getCurrentUrl() { - return window.location.origin + window.location.pathname; // strips ?query=string and #hash -} - -module.exports = function oauthClient(config, initState) { - /* config: - instance: instance domain (https://testingtesting123.xyz) - client_name: "GoToSocial Admin Panel" - scope: [] - website: - */ - - let state = initState; - if (initState == undefined) { - state = localStorage.getItem("oauth"); - if (state == undefined) { - state = { - config - }; - storeState(); - } else { - state = JSON.parse(state); - } - } - - function storeState() { - localStorage.setItem("oauth", JSON.stringify(state)); - } - - /* register app - /api/v1/apps - */ - function register() { - if (state.client_id != undefined) { - return true; // we already have a registration - } - let url = new URL(config.instance); - url.pathname = "/api/v1/apps"; - - return fetch(url.href, { - method: "POST", - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - client_name: config.client_name, - redirect_uris: getCurrentUrl(), - scopes: config.scope.join(" "), - website: getCurrentUrl() - }) - }).then((res) => { - if (res.status != 200) { - throw res; - } - return res.json(); - }).then((json) => { - state.client_id = json.client_id; - state.client_secret = json.client_secret; - storeState(); - }); - } - - /* authorize: - /oauth/authorize - ?client_id=CLIENT_ID - &redirect_uri=window.location.href - &response_type=code - &scope=admin - */ - function authorize() { - let url = new URL(config.instance); - url.pathname = "/oauth/authorize"; - url.searchParams.set("client_id", state.client_id); - url.searchParams.set("redirect_uri", getCurrentUrl()); - url.searchParams.set("response_type", "code"); - url.searchParams.set("scope", config.scope.join(" ")); - - window.location.assign(url.href); - } - - function callback() { - if (state.access_token != undefined) { - return; // we're already done :) - } - let params = (new URL(window.location)).searchParams; - - let token = params.get("code"); - if (token != null) { - console.log("got token callback:", token); - } - - return authorizeToken(token) - .catch((e) => { - console.log("Error processing oauth callback:", e); - logout(); // just to be sure - }); - } - - function authorizeToken(token) { - let url = new URL(config.instance); - url.pathname = "/oauth/token"; - return fetch(url.href, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - client_id: state.client_id, - client_secret: state.client_secret, - redirect_uri: getCurrentUrl(), - grant_type: "authorization_code", - code: token - }) - }).then((res) => { - if (res.status != 200) { - throw res; - } - return res.json(); - }).then((json) => { - state.access_token = json.access_token; - storeState(); - window.location = getCurrentUrl(); // clear ?token= - }); - } - - function isAuthorized() { - return (state.access_token != undefined); - } - - function apiRequest(path, method, data, type="json", accept="json") { - if (!isAuthorized()) { - throw new Error("Not Authenticated"); - } - let url = new URL(config.instance); - let [p, s] = path.split("?"); - url.pathname = p; - if (s != undefined) { - url.search = s; - } - let headers = { - "Authorization": `Bearer ${state.access_token}`, - "Accept": accept == "json" ? "application/json" : "*/*" - }; - let body = data; - if (type == "json" && body != undefined) { - headers["Content-Type"] = "application/json"; - body = JSON.stringify(data); - } - return fetch(url.href, { - method, - headers, - body - }).then((res) => { - return Promise.all([res.json(), res]); - }).then(([json, res]) => { - if (res.status != 200) { - if (json.error) { - throw new Error(json.error); - } else { - throw new Error(`${res.status}: ${res.statusText}`); - } - } else { - return json; - } - }).catch(e => { - if (e instanceof SyntaxError) { - throw new Error("Error: The GtS API returned a non-json error. This usually means a network problem, or an issue with your instance's reverse proxy configuration.", {cause: e}); - } else { - throw e; - } - }); - } - - function logout() { - let url = new URL(config.instance); - url.pathname = "/oauth/revoke"; - return fetch(url.href, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - client_id: state.client_id, - client_secret: state.client_secret, - token: state.access_token, - }) - }).then((res) => { - if (res.status != 200) { - // GoToSocial doesn't actually implement this route yet, - // so error is to be expected - return; - } - return res.json(); - }).catch(() => { - // see above - }).then(() => { - localStorage.removeItem("oauth"); - window.location = getCurrentUrl(); - }); - } - - return { - register, authorize, callback, isAuthorized, apiRequest, logout - }; -}; diff --git a/web/source/panels/lib/panel.js b/web/source/panels/lib/panel.js deleted file mode 100644 index 168eac7a0..000000000 --- a/web/source/panels/lib/panel.js +++ /dev/null @@ -1,134 +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 Promise = require("bluebird"); -const React = require("react"); -const ReactDom = require("react-dom"); - -const oauthLib = require("./oauth"); - -module.exports = function createPanel(clientName, scope, Component) { - ReactDom.render(<Panel/>, document.getElementById("root")); - - function Panel() { - const [oauth, setOauth] = React.useState(); - const [hasAuth, setAuth] = React.useState(false); - const [oauthState, setOauthState] = React.useState(localStorage.getItem("oauth")); - - React.useEffect(() => { - let state = localStorage.getItem("oauth"); - if (state != undefined) { - state = JSON.parse(state); - let restoredOauth = oauthLib(state.config, state); - Promise.try(() => { - return restoredOauth.callback(); - }).then(() => { - setAuth(true); - }); - setOauth(restoredOauth); - } - }, [setAuth, setOauth]); - - if (!hasAuth && oauth && oauth.isAuthorized()) { - setAuth(true); - } - - if (oauth && oauth.isAuthorized()) { - return <Component oauth={oauth} />; - } else if (oauthState != undefined) { - return "processing oauth..."; - } else { - return <Auth setOauth={setOauth} />; - } - } - - function Auth({setOauth}) { - const [ instance, setInstance ] = React.useState(""); - - React.useEffect(() => { - let isStillMounted = true; - // check if current domain runs an instance - let thisUrl = new URL(window.location.origin); - thisUrl.pathname = "/api/v1/instance"; - Promise.try(() => { - return fetch(thisUrl.href); - }).then((res) => { - if (res.status == 200) { - return res.json(); - } - }).then((json) => { - if (json && json.uri && isStillMounted) { - setInstance(json.uri); - } - }).catch((e) => { - console.log("error checking instance response:", e); - }); - - return () => { - // cleanup function - isStillMounted = false; - }; - }, []); - - function doAuth() { - return Promise.try(() => { - return new URL(instance); - }).catch(TypeError, () => { - return new URL(`https://${instance}`); - }).then((parsedURL) => { - let url = parsedURL.toString(); - let oauth = oauthLib({ - instance: url, - client_name: clientName, - scope: scope, - website: window.location.href - }); - setOauth(oauth); - setInstance(url); - return oauth.register().then(() => { - return oauth; - }); - }).then((oauth) => { - return oauth.authorize(); - }).catch((e) => { - console.log("error authenticating:", e); - }); - } - - function updateInstance(e) { - if (e.key == "Enter") { - doAuth(); - } else { - setInstance(e.target.value); - } - } - - return ( - <section className="login"> - <h1>OAUTH Login:</h1> - <form onSubmit={(e) => e.preventDefault()}> - <label htmlFor="instance">Instance: </label> - <input value={instance} onChange={updateInstance} id="instance"/> - <button onClick={doAuth}>Authenticate</button> - </form> - </section> - ); - } -};
\ No newline at end of file diff --git a/web/source/panels/user/basic.js b/web/source/panels/user/basic.js deleted file mode 100644 index f507b782b..000000000 --- a/web/source/panels/user/basic.js +++ /dev/null @@ -1,151 +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 Submit = require("../../lib/submit"); - -module.exports = function Basic({oauth, account, allowCustomCSS}) { - 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); - const [customCSS, setCustomCSS] = React.useState(""); - - React.useEffect(() => { - setHeaderSrc(account.header); - setAvatarSrc(account.avatar); - - setDisplayName(account.display_name); - setBio(account.source ? account.source.note : ""); - setLocked(account.locked); - setCustomCSS((allowCustomCSS && account.custom_css) ? account.custom_css : ""); - }, [account, setHeaderSrc, setAvatarSrc, setDisplayName, setBio, setLocked, setCustomCSS]); - - 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); - - if (allowCustomCSS) { - formDataInfo.set("custom_css", customCSS); - } - - 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); - setCustomCSS(allowCustomCSS && json.custom_css ? json.custom_css : ""); - }).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> - { !allowCustomCSS ? null : - <div className="labelinput"> - <label htmlFor="customcss">Custom CSS</label> - <textarea className="mono" id="customcss" value={customCSS} onChange={(e) => setCustomCSS(e.target.value)}/> - <a href="https://docs.gotosocial.org/en/latest/user_guide/custom_css" target="_blank" className="moreinfolink" rel="noreferrer">Learn more about custom CSS (opens in a new tab)</a> - </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> - ); -}; diff --git a/web/source/panels/user/index.js b/web/source/panels/user/index.js deleted file mode 100644 index aeecac415..000000000 --- a/web/source/panels/user/index.js +++ /dev/null @@ -1,76 +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 Promise = require("bluebird"); -const React = require("react"); -const ReactDom = require("react-dom"); - -const createPanel = require("../lib/panel"); - -const Basic = require("./basic"); -const Posts = require("./posts"); -const Security = require("./security"); - -require("../base.css"); -require("./style.css"); - -function UserPanel({oauth}) { - const [account, setAccount] = React.useState({}); - const [allowCustomCSS, setAllowCustomCSS] = React.useState(false); - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState("Fetching user info"); - - React.useEffect(() => { - - }, [oauth, setAllowCustomCSS, setError, setStatus]); - - React.useEffect(() => { - Promise.try(() => { - return oauth.apiRequest("/api/v1/instance", "GET"); - }).then((json) => { - setAllowCustomCSS(json.configuration.accounts.allow_custom_css); - Promise.try(() => { - return oauth.apiRequest("/api/v1/accounts/verify_credentials", "GET"); - }).then((json) => { - setAccount(json); - }).catch((e) => { - setError(e.message); - setStatus(""); - }); - }).catch((e) => { - setError(e.message); - setStatus(""); - }); - - }, [oauth, setAllowCustomCSS, setAccount, setError, setStatus]); - - return ( - <React.Fragment> - <div> - <button className="logout" onClick={oauth.logout}>Log out of settings panel</button> - </div> - <Basic oauth={oauth} account={account} allowCustomCSS={allowCustomCSS}/> - <Posts oauth={oauth} account={account}/> - <Security oauth={oauth}/> - </React.Fragment> - ); -} - -createPanel("GoToSocial User Panel", ["read write"], UserPanel);
\ No newline at end of file diff --git a/web/source/panels/user/languages.js b/web/source/panels/user/languages.js deleted file mode 100644 index c20e08426..000000000 --- a/web/source/panels/user/languages.js +++ /dev/null @@ -1,98 +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"); - -module.exports = function Languages() { - return <React.Fragment> - <option value="AF">Afrikaans</option> - <option value="SQ">Albanian</option> - <option value="AR">Arabic</option> - <option value="HY">Armenian</option> - <option value="EU">Basque</option> - <option value="BN">Bengali</option> - <option value="BG">Bulgarian</option> - <option value="CA">Catalan</option> - <option value="KM">Cambodian</option> - <option value="ZH">Chinese (Mandarin)</option> - <option value="HR">Croatian</option> - <option value="CS">Czech</option> - <option value="DA">Danish</option> - <option value="NL">Dutch</option> - <option value="EN">English</option> - <option value="ET">Estonian</option> - <option value="FJ">Fiji</option> - <option value="FI">Finnish</option> - <option value="FR">French</option> - <option value="KA">Georgian</option> - <option value="DE">German</option> - <option value="EL">Greek</option> - <option value="GU">Gujarati</option> - <option value="HE">Hebrew</option> - <option value="HI">Hindi</option> - <option value="HU">Hungarian</option> - <option value="IS">Icelandic</option> - <option value="ID">Indonesian</option> - <option value="GA">Irish</option> - <option value="IT">Italian</option> - <option value="JA">Japanese</option> - <option value="JW">Javanese</option> - <option value="KO">Korean</option> - <option value="LA">Latin</option> - <option value="LV">Latvian</option> - <option value="LT">Lithuanian</option> - <option value="MK">Macedonian</option> - <option value="MS">Malay</option> - <option value="ML">Malayalam</option> - <option value="MT">Maltese</option> - <option value="MI">Maori</option> - <option value="MR">Marathi</option> - <option value="MN">Mongolian</option> - <option value="NE">Nepali</option> - <option value="NO">Norwegian</option> - <option value="FA">Persian</option> - <option value="PL">Polish</option> - <option value="PT">Portuguese</option> - <option value="PA">Punjabi</option> - <option value="QU">Quechua</option> - <option value="RO">Romanian</option> - <option value="RU">Russian</option> - <option value="SM">Samoan</option> - <option value="SR">Serbian</option> - <option value="SK">Slovak</option> - <option value="SL">Slovenian</option> - <option value="ES">Spanish</option> - <option value="SW">Swahili</option> - <option value="SV">Swedish </option> - <option value="TA">Tamil</option> - <option value="TT">Tatar</option> - <option value="TE">Telugu</option> - <option value="TH">Thai</option> - <option value="BO">Tibetan</option> - <option value="TO">Tonga</option> - <option value="TR">Turkish</option> - <option value="UK">Ukrainian</option> - <option value="UR">Urdu</option> - <option value="UZ">Uzbek</option> - <option value="VI">Vietnamese</option> - <option value="CY">Welsh</option> - <option value="XH">Xhosa</option> - </React.Fragment>; -}; diff --git a/web/source/panels/user/posts.js b/web/source/panels/user/posts.js deleted file mode 100644 index e4ceae617..000000000 --- a/web/source/panels/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 <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> - ); -}; diff --git a/web/source/panels/user/security.js b/web/source/panels/user/security.js deleted file mode 100644 index f5925083d..000000000 --- a/web/source/panels/user/security.js +++ /dev/null @@ -1,80 +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 Submit = require("../../lib/submit"); - -module.exports = function Security({oauth}) { - const [errorMsg, setError] = React.useState(""); - const [statusMsg, setStatus] = React.useState(""); - - const [oldPassword, setOldPassword] = React.useState(""); - const [newPassword, setNewPassword] = React.useState(""); - const [newPasswordConfirm, setNewPasswordConfirm] = React.useState(""); - - const submit = (e) => { - e.preventDefault(); - - if (newPassword !== newPasswordConfirm) { - setError("New password and confirm new password did not match!"); - return; - } - - setStatus("PATCHing"); - setError(""); - return Promise.try(() => { - let formDataInfo = new FormData(); - formDataInfo.set("old_password", oldPassword); - formDataInfo.set("new_password", newPassword); - return oauth.apiRequest("/api/v1/user/password_change", "POST", formDataInfo, "form"); - }).then((json) => { - setStatus("Saved!"); - setOldPassword(""); - setNewPassword(""); - setNewPasswordConfirm(""); - }).catch((e) => { - setError(e.message); - setStatus(""); - }); - }; - - return ( - <section className="security"> - <h1>Password Change</h1> - <form> - <div className="labelinput"> - <label htmlFor="password">Current password</label> - <input name="password" id="password" type="password" autoComplete="current-password" value={oldPassword} onChange={(e) => setOldPassword(e.target.value)} /> - </div> - <div className="labelinput"> - <label htmlFor="new-password">New password</label> - <input name="new-password" id="new-password" type="password" autoComplete="new-password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} /> - </div> - <div className="labelinput"> - <label htmlFor="confirm-new-password">Confirm new password</label> - <input name="confirm-new-password" id="confirm-new-password" type="password" autoComplete="new-password" value={newPasswordConfirm} onChange={(e) => setNewPasswordConfirm(e.target.value)} /> - </div> - <Submit onClick={submit} label="Save new password" errorMsg={errorMsg} statusMsg={statusMsg}/> - </form> - </section> - ); -}; diff --git a/web/source/panels/user/style.css b/web/source/panels/user/style.css deleted file mode 100644 index 021b1816e..000000000 --- a/web/source/panels/user/style.css +++ /dev/null @@ -1,118 +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/>. -*/ - -section.basic, section.posts, section.security { - form { - display: flex; - flex-direction: column; - gap: 1rem; - - input, textarea { - width: 100%; - line-height: 1.5rem; - } - - input[type=checkbox] { - justify-self: start; - width: initial; - } - - input:read-only { - border: none; - } - - input:invalid { - border-color: red; - } - } - - textarea { - width: 100%; - height: 8rem; - } - - h1 { - 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; - } -} - -.labelinput .border { - border-radius: 0.2rem; - border: 0.15rem solid $border_accent; - padding: 0.3rem; - display: flex; - flex-direction: column; -} - -.file-input.button { - display: inline-block; - font-size: 1rem; - font-weight: normal; - padding: 0.3rem 0.3rem; - align-self: flex-start; - /* background: $border_accent; */ - margin-right: 0.2rem; -} - -.labelinput, .labelselect { - display: flex; - flex-direction: column; - gap: 0.4rem; -} - -.labelcheckbox { - display: flex; - gap: 0.4rem; -} - -.titlesave { - display: flex; - flex-wrap: wrap; - gap: 0.4rem; -} - -.logout { - margin-bottom: 2rem; -} |