From 0e3d27a91e74fbccf4c1511173b1288ad5d871b5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 2 Nov 2021 11:11:33 -0500 Subject: [PATCH] Developers: create an app through a form --- .../features/developers/apps/create.js | 224 ++++++++++++++++-- 1 file changed, 210 insertions(+), 14 deletions(-) diff --git a/app/soapbox/features/developers/apps/create.js b/app/soapbox/features/developers/apps/create.js index e9aa77d8f..7d68b7a79 100644 --- a/app/soapbox/features/developers/apps/create.js +++ b/app/soapbox/features/developers/apps/create.js @@ -1,38 +1,234 @@ import React from 'react'; import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import { injectIntl, FormattedMessage, defineMessages } from 'react-intl'; import Column from 'soapbox/features/ui/components/column'; -import { SimpleForm, TextInput } from 'soapbox/features/forms'; +import { + SimpleForm, + TextInput, + SimpleTextarea, + FieldsGroup, +} from 'soapbox/features/forms'; +import { createApp } from 'soapbox/actions/apps'; +import { obtainOAuthToken } from 'soapbox/actions/oauth'; +import { Map as ImmutableMap } from 'immutable'; +import { getBaseURL } from 'soapbox/utils/accounts'; +import { getFeatures } from 'soapbox/utils/features'; +import Accordion from 'soapbox/features/ui/components/accordion'; const messages = defineMessages({ heading: { id: 'column.app_create', defaultMessage: 'Create app' }, namePlaceholder: { id: 'app_create.name_placeholder', defaultMessage: 'e.g. \'Soapbox\'' }, + scopesPlaceholder: { id: 'app_create.scopes_placeholder', defaultMessage: 'e.g. \'read write follow\'' }, }); -export default @injectIntl -class CreateApp extends React.Component { +const mapStateToProps = state => { + const me = state.get('me'); + const account = state.getIn(['accounts', me]); + + const instance = state.get('instance'); + const features = getFeatures(instance); + + return { + account, + defaultScopes: features.scopes, + }; +}; + +export default @connect(mapStateToProps) +@injectIntl +class CreateApp extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + account: ImmutablePropTypes.map.isRequired, + defaultScopes: PropTypes.string, } - render() { + initialState = () => { + return { + params: ImmutableMap({ + client_name: '', + redirect_uris: 'urn:ietf:wg:oauth:2.0:oob', + scopes: '', + website: '', + }), + app: null, + token: null, + isLoading: false, + explanationExpanded: true, + }; + } + + state = this.initialState() + + handleCreateApp = () => { + const { dispatch, account } = this.props; + const { params } = this.state; + const baseURL = getBaseURL(account); + + return dispatch(createApp(params.toJS(), baseURL)) + .then(app => this.setState({ app })); + } + + handleCreateToken = () => { + const { dispatch, account } = this.props; + const { app, params: appParams } = this.state; + const baseURL = getBaseURL(account); + + const tokenParams = { + client_id: app.client_id, + client_secret: app.client_secret, + redirect_uri: appParams.get('redirect_uri'), + grant_type: 'client_credentials', + scope: appParams.get('scopes'), + }; + + return dispatch(obtainOAuthToken(tokenParams, baseURL)) + .then(token => this.setState({ token })); + } + + handleSubmit = e => { + this.setState({ isLoading: true }); + + this.handleCreateApp() + .then(this.handleCreateToken) + .then(() => { + this.scrollToTop(); + this.setState({ isLoading: false }); + }).catch(error => { + console.error(error); + this.setState({ isLoading: false }); + }); + } + + setParam = (key, value) => { + const { params } = this.state; + const newParams = params.set(key, value); + + this.setState({ params: newParams }); + } + + handleParamChange = key => { + return e => { + this.setParam(key, e.target.value); + }; + } + + resetState = () => { + this.setState(this.initialState()); + } + + handleReset = e => { + this.resetState(); + this.scrollToTop(); + } + + toggleExplanation = expanded => { + this.setState({ explanationExpanded: expanded }); + } + + scrollToTop = () => { + window.scrollTo({ top: 0, behavior: 'smooth' }); + } + + renderResults = () => { const { intl } = this.props; + const { app, token, explanationExpanded } = this.state; return ( - TODO: This page is incomplete + + } + expanded={explanationExpanded} + onToggle={this.toggleExplanation} + > + + + + +
+ } + value={JSON.stringify(app, null, 2)} + rows={10} + readOnly + /> +
+
+ +
+ } + value={JSON.stringify(token, null, 2)} + rows={10} + readOnly + /> +
+
+
+ +
+
+
+ ); + } - } - placeholder={intl.formatMessage(messages.namePlaceholder)} - required - /> - } - placeholder='https://soapbox.pub' - /> + render() { + const { intl } = this.props; + const { params, app, token, isLoading } = this.state; + + if (app && token) { + return this.renderResults(); + } + + return ( + + +
+ } + placeholder={intl.formatMessage(messages.namePlaceholder)} + onChange={this.handleParamChange('client_name')} + value={params.get('client_name')} + required + /> + } + placeholder='https://soapbox.pub' + onChange={this.handleParamChange('website')} + value={params.get('website')} + /> + } + placeholder='https://example.com' + onChange={this.handleParamChange('redirect_uris')} + value={params.get('redirect_uris')} + required + /> + } + placeholder={intl.formatMessage(messages.scopesPlaceholder)} + onChange={this.handleParamChange('scopes')} + value={params.get('scopes')} + required + /> +
+ +
+
);