From b65fd3a5f03c6cbd517246adc224c26e8c1dc070 Mon Sep 17 00:00:00 2001 From: Manuel Bouza Date: Thu, 21 Feb 2019 08:05:56 +0100 Subject: [PATCH] Refactor App component to load projects and create activity --- src/css/_spinner.scss | 26 +++++++---- src/css/popup.scss | 11 ++++- src/js/components/App.js | 87 +++++++++++++++++++++++++++++++----- src/js/components/Bubble.js | 72 +++++------------------------ src/js/components/Popup.js | 7 +-- src/js/components/Spinner.js | 4 +- src/js/popup.js | 6 +-- 7 files changed, 119 insertions(+), 94 deletions(-) diff --git a/src/css/_spinner.scss b/src/css/_spinner.scss index c911442..fb75aab 100644 --- a/src/css/_spinner.scss +++ b/src/css/_spinner.scss @@ -5,14 +5,22 @@ } } - .moco-bx-spinner { - display: inline-block; - width: 2rem; - height: 2rem; - vertical-align: text-bottom; - border: 2px solid #999; - border-right-color: transparent; - border-radius: 50%; - animation: moco-bx-spinner .75s linear infinite; + .moco-bx-spinner__container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + + .moco-bx-spinner { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + border: 2px solid #999; + border-right-color: transparent; + border-radius: 50%; + animation: moco-bx-spinner .75s linear infinite; + } } } diff --git a/src/css/popup.scss b/src/css/popup.scss index a63683b..c77ce64 100644 --- a/src/css/popup.scss +++ b/src/css/popup.scss @@ -1,5 +1,14 @@ @import "form"; @import "spinner"; -#moco-bx-root { +html { + height: 100%; + + body { + height: 100%; + + #moco-bx-root { + height: 100%; + } + } } diff --git a/src/js/components/App.js b/src/js/components/App.js index 915be1e..3179303 100644 --- a/src/js/components/App.js +++ b/src/js/components/App.js @@ -3,7 +3,7 @@ import PropTypes from "prop-types" import ApiClient from "api/Client" import Form from "components/Form" import Spinner from "components/Spinner" -import { observable, computed, reaction } from "mobx" +import { observable, computed, toJS } from "mobx" import { observer, disposeOnUnmount } from "mobx-react" import { findLastProject, @@ -25,24 +25,30 @@ class App extends Component { projectId: PropTypes.string, taskId: PropTypes.string }).isRequired, - projects: PropTypes.arrayOf(PropTypes.object).isRequired, - lastProjectId: PropTypes.number, - lastTaskId: PropTypes.number, + settings: PropTypes.shape({ + subdomain: PropTypes.string, + apiKey: PropTypes.string, + version: PropTypes.string + }), browser: PropTypes.object.isRequired }; + @observable projects = [] + @observable lastProjectId + @observable lastTaskId @observable changeset = {}; @observable formErrors = {}; + @observable isLoading = true @computed get changesetWithDefaults() { - const { service, projects, lastProjectId, lastTaskId } = this.props + const { service } = this.props const project = - findLastProject(service.projectId || lastProjectId)(projects) || - head(projects) + findLastProject(service.projectId || this.lastProjectId)(this.projects) || + head(this.projects) const task = - findLastTask(service.taskId || lastTaskId)(project) || + findLastTask(service.taskId || this.lastTaskId)(project) || head(project?.tasks) const defaults = { @@ -64,14 +70,67 @@ class App extends Component { } } + #apiClient + + constructor(props) { + super(props) + this.initializeApiClient(props.settings) + } + componentDidMount() { + this.fetchProjects() window.addEventListener("keydown", this.handleKeyDown) } componentWillUnmount() { window.removeEventListener("keydown", this.handleKeyDown) } - + + initializeApiClient = settings => { + this.#apiClient = new ApiClient(settings) + } + + fetchProjects = () => { + this.isLoading = true + + return this.#apiClient + .projects() + .then(({ data }) => { + this.projects = groupedProjectOptions(data.projects) + this.lastProjectId = data.last_project_id + this.lastTaskId = data.lastTaskId + }) + .catch(error => { + console.log(error) + }) + .finally(() => { + this.isLoading = false + }) + }; + + createActivity = () => { + this.isLoading = true + + this.#apiClient + .createActivity(this.changesetWithDefaults) + .then(({ data }) => { + this.changeset = {} + this.formErrors = {} + this.sendMessage( + { type: 'activityCreated', payload: { hours: data.hours} } + ) + }) + .catch(error => { + if (error.response?.status === 422) { + this.formErrors = error.response.data + } + if (error.response?.status === 401) { + this.unauthorizedError = true + } + }) + .finally(() => this.isLoading = false) + } + handleKeyDown = event => { event.stopPropagation() if (event.keyCode === 27) { @@ -93,7 +152,7 @@ class App extends Component { handleSubmit = event => { event.preventDefault() - this.sendMessage({ type: 'submitForm', payload: this.changesetWithDefaults }) + this.createActivity() } handleCancel = () => { @@ -107,12 +166,16 @@ class App extends Component { ) render() { - const { service, projects } = this.props; + if (this.isLoading) { + return + } + const { service } = this.props; + return (
this.props.settings, settings => { + this.close() this.initializeApiClient(settings) this.fetchBookedHours() - this.close() }) ) @@ -78,12 +73,15 @@ class Bubble extends Component { window.removeEventListener("keydown", this.handleKeyDown) } + initializeApiClient = settings => { + this.#apiClient = new ApiClient(settings) + } + open = event => { if (event && event.target && event.target.classList.contains('moco-bx-popup')) { return this.close() } this.isOpen = true - this.fetchProjects().then(() => (this.isOpen = true)) }; close = _event => { @@ -92,8 +90,9 @@ class Bubble extends Component { receiveMessage = ({ type, payload }) => { switch(type) { - case 'submitForm': { - return this.createActivity(payload).then(() => this.close()) + case 'activityCreated': { + this.bookedHours += payload.hours + return this.close() } case 'closeForm': { return this.close() @@ -101,35 +100,6 @@ class Bubble extends Component { } } - initializeApiClient = settings => { - this.#apiClient = new ApiClient(settings) - } - - fetchProjects = () => { - if (this.projects.length > 0) { - return Promise.resolve(); - } - - this.isLoading = true - - return this.#apiClient - .projects() - .then(({ data }) => { - this.unauthorizedError = false - this.projects = groupedProjectOptions(data.projects) - this.lastProjectId = data.last_project_id - this.lastTaskId = data.lastTaskId - }) - .catch(error => { - if (error.response?.status === 401) { - this.unauthorizedError = true - } - }) - .finally(() => { - this.isLoading = false - }) - }; - fetchBookedHours = () => { const { service } = this.props this.isLoading = true @@ -148,26 +118,6 @@ class Bubble extends Component { .finally(() => (this.isLoading = false)) }; - createActivity = payload => - this.#apiClient - .createActivity(payload) - .then(({ data }) => { - this.bookedHours += data.hours - this.changeset = {} - this.formErrors = {} - this.unauthorizedError = false - }) - .catch(this.handleSubmitError) - - handleSubmitError = error => { - if (error.response?.status === 422) { - this.formErrors = error.response.data - } - if (error.response?.status === 401) { - this.unauthorizedError = true - } - }; - handleKeyDown = event => { if (event.key === 'm' && (event.metaKey || event.ctrlKey)) { event.preventDefault() @@ -187,7 +137,7 @@ class Bubble extends Component { return } - const { service, browser } = this.props; + const { service, settings, browser } = this.props; return (
@@ -199,9 +149,7 @@ class Bubble extends Component { {this.isOpen && ( diff --git a/src/js/components/Popup.js b/src/js/components/Popup.js index 9b9df03..fb1030e 100644 --- a/src/js/components/Popup.js +++ b/src/js/components/Popup.js @@ -5,9 +5,7 @@ import queryString from 'query-string' import { serializeProps } from 'utils' const Popup = props => { - const serializedProps = serializeProps( - ['service', 'projects', 'lastProjectId', 'lastTaskId'] - )(props) + const serializedProps = serializeProps(['service', 'settings'])(props) const styles = useMemo(() => ({ width: '536px', @@ -31,9 +29,6 @@ const Popup = props => { Popup.propTypes = { service: PropTypes.object.isRequired, - projects: PropTypes.arrayOf(PropTypes.object).isRequired, - lastProjectId: PropTypes.number, - lastTaskId: PropTypes.number, browser: PropTypes.object.isRequired, unauthorizedError: PropTypes.bool.isRequired } diff --git a/src/js/components/Spinner.js b/src/js/components/Spinner.js index 5fc215c..73d67ff 100644 --- a/src/js/components/Spinner.js +++ b/src/js/components/Spinner.js @@ -1,7 +1,9 @@ import React from 'react' const Spinner = () => ( -
+
+
+
) export default Spinner diff --git a/src/js/popup.js b/src/js/popup.js index 0d88de7..64a0d5b 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -5,9 +5,9 @@ import queryString from 'query-string' import { parseProps } from 'utils' import '../css/popup.scss' -const parsedProps = parseProps( - ['service', 'projects', 'lastProjectId', 'lastTaskId'] -)(queryString.parse(location.search)) +const parsedProps = parseProps(['service', 'settings'])( + queryString.parse(location.search) +) ReactDOM.render(