diff --git a/web/source/css/_colors.css b/web/source/css/_colors.css index f8c266539..055572ed9 100644 --- a/web/source/css/_colors.css +++ b/web/source/css/_colors.css @@ -44,6 +44,9 @@ $blue1: #3a9fde; /* darker blue for smaller elements (borders), can only be used $blue2: #66befe; /* all-round accent color, can be used with $gray1 (6.8), $gray2 (5.5), $gray3 (4.9), $gray4 (4.5) */ $blue3: #89caff; /* hover/selected accent to $blue2, can be used with $gray1 (7.9), $gray2 (6.3), $gray3 (5.6), $gray4 (5.2), $gray5 (4.7) */ +$error1: #860000; /* Error border/foreground text, can be used with $error2 (5.0), $white1 (10), $white2 (5.1) */ +$error2: #ff9796; /* Error background text, can be used with $error1 (5.0), $gray1 (6.6), $gray2 (5.3), $gray3 (4.8) */ + $fg: $white1; $bg: $gray1; diff --git a/web/source/package.json b/web/source/package.json index 41c7260e7..51a49a478 100644 --- a/web/source/package.json +++ b/web/source/package.json @@ -38,7 +38,9 @@ "react-error-boundary": "^3.1.4", "react-redux": "^8.0.2", "reactify": "^1.1.1", + "redux-devtools-extension": "^2.13.9", "redux-persist": "^6.0.0", + "redux-thunk": "^2.4.1", "uglifyify": "^5.0.2", "wouter": "^2.8.0-alpha.2" }, diff --git a/web/source/settings-panel/components/login.jsx b/web/source/settings-panel/components/login.jsx index 180ab738f..636a7f326 100644 --- a/web/source/settings-panel/components/login.jsx +++ b/web/source/settings-panel/components/login.jsx @@ -18,6 +18,76 @@ "use strict"; +const Promise = require("bluebird"); +const React = require("react"); +const Redux = require("react-redux"); + +const { setInstance } = require("../redux/reducers/instances").actions; +const { updateInstance, updateRegistration } = require("../lib/api"); + module.exports = function Login() { - return (null); + const dispatch = Redux.useDispatch(); + const [ instanceField, setInstanceField ] = React.useState(""); + const [ errorMsg, setErrorMsg ] = React.useState(); + const instanceFieldRef = React.useRef(""); + + React.useEffect(() => { + // check if current domain runs an instance + Promise.try(() => { + console.log("trying", window.location.origin); + return dispatch(updateInstance(window.location.origin)); + }).then((json) => { + if (instanceFieldRef.current.length == 0) { // user hasn't started typing yet + dispatch(setInstance(json.uri)); + instanceFieldRef.current = json.uri; + setInstanceField(json.uri); + } + }).catch((e) => { + console.log("Current domain does not host a valid instance: ", e); + }); + }, []); + + function tryInstance() { + Promise.try(() => { + return dispatch(updateInstance(instanceFieldRef.current)).catch((e) => { + // TODO: clearer error messages for common errors + console.log(e); + throw e; + }); + }).then((instance) => { + // return dispatch(updateRegistration); + }).catch((e) => { + setErrorMsg( + <> + {e.type} + {e.message} + + ); + }); + } + + function updateInstanceField(e) { + if (e.key == "Enter") { + tryInstance(instanceField); + } else { + setInstanceField(e.target.value); + instanceFieldRef.current = e.target.value; + } + } + + return ( +
+

OAUTH Login:

+
e.preventDefault()}> + + + {errorMsg && +
+ {errorMsg} +
+ } + +
+
+ ); }; \ No newline at end of file diff --git a/web/source/settings-panel/index.js b/web/source/settings-panel/index.js index 01b8eeb03..bca2ead4e 100644 --- a/web/source/settings-panel/index.js +++ b/web/source/settings-panel/index.js @@ -33,7 +33,7 @@ const ErrorFallback = require("./components/error"); const oauthLib = require("./lib/oauth"); -// require("./style.css"); +require("./style.css"); // TODO: nested categories? const nav = { @@ -90,7 +90,7 @@ function App() { {sidebar} {/* */} -
+
{panelRouter} @@ -98,7 +98,9 @@ function App() { ); } else { - return ; + return ( + + ); } } diff --git a/web/source/settings-panel/lib/api.js b/web/source/settings-panel/lib/api.js new file mode 100644 index 000000000..e330ed64b --- /dev/null +++ b/web/source/settings-panel/lib/api.js @@ -0,0 +1,94 @@ +/* + 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 { setRegistration } = require("../redux/reducers/oauth").actions; +const { setInstanceInfo } = require("../redux/reducers/instances").actions; + +function apiCall(base, method, route, {payload, headers={}}) { + return Promise.try(() => { + let url = new URL(base); + url.pathname = route; + let body; + + if (payload != undefined) { + body = JSON.stringify(payload); + } + + let fetchHeaders = { + "Content-Type": "application/json", + ...headers + }; + + return fetch(url.toString(), { + method: method, + headers: fetchHeaders, + body: body + }); + }).then((res) => { + if (res.status == 200) { + return res.json(); + } else { + throw res; + } + }); +} + +function getCurrentUrl() { + return `${window.location.origin}${window.location.pathname}`; +} + +function updateInstance(domain) { + return function(dispatch, getState) { + /* check if domain is valid instance, then register client if needed */ + + return Promise.try(() => { + return apiCall(domain, "GET", "/api/v1/instance", { + headers: { + "Content-Type": "text/plain" + } + }); + }).then((json) => { + if (json && json.uri) { // TODO: validate instance json more? + dispatch(setInstanceInfo(json.uri, json)); + return json; + } + }); + }; +} + +function updateRegistration() { + return function(dispatch, getState) { + let base = getState().oauth.instance; + return Promise.try(() => { + return apiCall(base, "POST", "/api/v1/apps", { + client_name: "GoToSocial Settings", + scopes: "write admin", + redirect_uris: getCurrentUrl(), + website: getCurrentUrl() + }); + }).then((json) => { + console.log(json); + dispatch(setRegistration(base, json)); + }); + }; +} + +module.exports = { updateInstance, updateRegistration }; \ No newline at end of file diff --git a/web/source/settings-panel/redux/index.js b/web/source/settings-panel/redux/index.js index 12ad9d9b8..422bfab11 100644 --- a/web/source/settings-panel/redux/index.js +++ b/web/source/settings-panel/redux/index.js @@ -18,22 +18,27 @@ "use strict"; -const { createStore, combineReducers } = require("redux"); +const { createStore, combineReducers, applyMiddleware } = require("redux"); const { persistStore, persistReducer } = require("redux-persist"); +const thunk = require("redux-thunk").default; +const { composeWithDevTools } = require("redux-devtools-extension"); const persistConfig = { key: "gotosocial-settings", storage: require("redux-persist/lib/storage").default, - stateReconciler: require("redux-persist/lib/stateReconciler/autoMergeLevel2").default + stateReconciler: require("redux-persist/lib/stateReconciler/autoMergeLevel2").default, + whitelist: ['oauth'] }; const combinedReducers = combineReducers({ - oauth: require("./reducers/oauth").reducer + oauth: require("./reducers/oauth").reducer, + instances: require("./reducers/instances").reducer, }); const persistedReducer = persistReducer(persistConfig, combinedReducers); +const composedEnhancer = composeWithDevTools(applyMiddleware(thunk)); -const store = createStore(persistedReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); +const store = createStore(persistedReducer, composedEnhancer); const persistor = persistStore(store); module.exports = { store, persistor }; \ No newline at end of file diff --git a/web/source/settings-panel/redux/reducers/instances.js b/web/source/settings-panel/redux/reducers/instances.js new file mode 100644 index 000000000..92c7e982c --- /dev/null +++ b/web/source/settings-panel/redux/reducers/instances.js @@ -0,0 +1,38 @@ +/* + 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 {createSlice} = require("@reduxjs/toolkit"); + +module.exports = createSlice({ + name: "instances", + initialState: { + info: {}, + current: undefined + }, + reducers: { + setInstance: (state, {payload}) => { + state.current = payload; + }, + setInstanceInfo: (state, {payload}) => { + let [key, info] = payload; + state.info[key] = info; + }, + } +}); \ No newline at end of file diff --git a/web/source/settings-panel/redux/reducers/oauth.js b/web/source/settings-panel/redux/reducers/oauth.js index d2186392c..a3202703e 100644 --- a/web/source/settings-panel/redux/reducers/oauth.js +++ b/web/source/settings-panel/redux/reducers/oauth.js @@ -23,20 +23,13 @@ const {createSlice} = require("@reduxjs/toolkit"); module.exports = createSlice({ name: "oauth", initialState: { - loggedIn: false + loggedIn: false, + registrations: {} }, reducers: { - setInstance: (state, {payload}) => { - return { - ...state, - instance: payload - }; - }, setRegistration: (state, {payload}) => { - return { - ...state, - - } + let [key, info] = payload; + state.instanceRegistration[key] = info; } } }); \ No newline at end of file diff --git a/web/source/settings-panel/style.css b/web/source/settings-panel/style.css index 051038c58..a44e5d547 100644 --- a/web/source/settings-panel/style.css +++ b/web/source/settings-panel/style.css @@ -32,7 +32,7 @@ section { display: grid; grid-template-columns: 1fr min(92%, 90ch) 1fr; - section { + section.with-sidebar { border-left: none; border-top-left-radius: 0; border-bottom-left-radius: 0; @@ -128,10 +128,16 @@ input, select, textarea { } .error { + background: $error2; + border: 1px solid $error1; + border-radius: $br; + color: $error1; font-weight: bold; + padding: 0.5rem; pre { background: $bg; + color: $fg; padding: 1rem; overflow: auto; margin: 0; diff --git a/web/source/yarn.lock b/web/source/yarn.lock index 4f3cfd647..dd8056b87 100644 --- a/web/source/yarn.lock +++ b/web/source/yarn.lock @@ -4854,6 +4854,11 @@ recast@^0.11.17: private "~0.1.5" source-map "~0.5.0" +redux-devtools-extension@^2.13.9: + version "2.13.9" + resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz#6b764e8028b507adcb75a1cae790f71e6be08ae7" + integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A== + redux-persist@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"