Refactor App component to load projects and create activity
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
@import "form";
|
||||
@import "spinner";
|
||||
|
||||
#moco-bx-root {
|
||||
html {
|
||||
height: 100%;
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
|
||||
#moco-bx-root {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <Spinner />
|
||||
}
|
||||
|
||||
const { service } = this.props;
|
||||
|
||||
return (
|
||||
<Form
|
||||
changeset={this.changesetWithDefaults}
|
||||
projects={projects}
|
||||
projects={this.projects}
|
||||
errors={this.formErrors}
|
||||
onChange={this.handleChange}
|
||||
onSubmit={this.handleSubmit}
|
||||
|
||||
@@ -40,12 +40,7 @@ class Bubble extends Component {
|
||||
|
||||
@observable isLoading = false;
|
||||
@observable isOpen = false;
|
||||
@observable projects = [];
|
||||
@observable lastProjectId;
|
||||
@observable lastTaskId;
|
||||
@observable bookedHours = 0;
|
||||
@observable changeset = {};
|
||||
@observable formErrors = {};
|
||||
@observable unauthorizedError = false;
|
||||
|
||||
constructor(props) {
|
||||
@@ -57,9 +52,9 @@ class Bubble extends Component {
|
||||
disposeOnUnmount(
|
||||
this,
|
||||
reaction(() => 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 <Spinner />
|
||||
}
|
||||
|
||||
const { service, browser } = this.props;
|
||||
const { service, settings, browser } = this.props;
|
||||
|
||||
return (
|
||||
<div className="moco-bx-bubble" onClick={this.open}>
|
||||
@@ -199,9 +149,7 @@ class Bubble extends Component {
|
||||
{this.isOpen && (
|
||||
<Popup
|
||||
service={service}
|
||||
projects={this.projects}
|
||||
lastProjectId={this.lastProjectId}
|
||||
lastTaskId={this.lastTaskId}
|
||||
settings={settings}
|
||||
browser={browser}
|
||||
unauthorizedError={this.unauthorizedError}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const Spinner = () => (
|
||||
<div className="moco-bx-spinner" role="status" />
|
||||
<div className='moco-bx-spinner__container'>
|
||||
<div className='moco-bx-spinner' role='status' />
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Spinner
|
||||
|
||||
@@ -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(
|
||||
<App
|
||||
|
||||
Reference in New Issue
Block a user