diff --git a/public/index.html b/public/index.html index 5b61727..17dce82 100644 --- a/public/index.html +++ b/public/index.html @@ -18,13 +18,6 @@ - - - - diff --git a/src/actions/actionTypes.js b/src/actions/actionTypes.js index ef50a86..db743a3 100644 --- a/src/actions/actionTypes.js +++ b/src/actions/actionTypes.js @@ -41,5 +41,9 @@ export const REGISTRATION_SUCCESS = 'REGISTRATION_SUCCESS'; export const REQUEST_REGISTRATION = 'REQUEST_REGISTRATION'; export const REGISTRATION_FAILURE = 'REGISTRATION_FAILURE'; +export const REQUEST_LANGUAGES = 'REQUEST_LANGUAGES'; +export const GET_LANGUAGES_SUCCESS = 'GET_LANGUAGES_SUCCESS'; +export const GET_LANGUAGES_FAILURE = 'GET_LANGUAGES_FAILURE'; + export const CHECK_AUTH = 'CHECK_AUTH'; export const SET_AUTH_TO_FALSE = 'SET_AUTH_TO_FALSE'; diff --git a/src/actions/languageActions.js b/src/actions/languageActions.js new file mode 100644 index 0000000..24687bc --- /dev/null +++ b/src/actions/languageActions.js @@ -0,0 +1,61 @@ +import * as types from './actionTypes'; +import LanguageApi from '../api/languageApi'; + +const languageApi = new LanguageApi(); + +export function getLanguagesError(message) { + return { + type: types.GET_LANGUAGES_FAILURE, + isFetching: false, + isAuthenticated: false, + message + }; +} + +export function requestLanguages() { + return { + type: types.REQUEST_LANGUAGES, + isFetching: true, + languages: [] + }; +} + +export function getLanguagesSuccess(languages) { + return { + type: types.GET_LANGUAGES_SUCCESS, + isFetching: false, + languages + }; +} + +export function getLanguages() { + return async (dispatch, getState) => { + try { + dispatch(requestLanguages()); + + const languages = await languageApi.getAllLanguages(); + + dispatch(getLanguagesSuccess(languages)); + } catch (e) { + dispatch(getLanguagesError(e.message)); + } + }; +} + +export function getLanguageNameByCode(code) { + return async (dispatch, getState) => { + try { + const languages = getState().languages.languages; + for (let i = 0; i < languages.languages.length; i++) { + const lang = languages.languages[i]; + + if (lang.code === code) { + return lang.name; + } + } + + return null; + } catch (e) { + } + }; +} diff --git a/src/actions/userActions.js b/src/actions/userActions.js index 43cda3a..1e4f743 100644 --- a/src/actions/userActions.js +++ b/src/actions/userActions.js @@ -82,10 +82,9 @@ export function registeryError(message) { }; } -export function requestUpdateUser(creds) { +export function requestUpdateUser() { return { type: types.REQUEST_UPDATE_USER, - creds, isFetching: true, requiresAuth: true }; @@ -95,8 +94,7 @@ export function updateUserSuccess(user) { return { type: types.UPDATE_USER_SUCCESS, user, - isFetching: false, - requiresAuth: true + isFetching: false }; } diff --git a/src/api/languageApi.js b/src/api/languageApi.js new file mode 100644 index 0000000..6e661bf --- /dev/null +++ b/src/api/languageApi.js @@ -0,0 +1,9 @@ +import Api from './api'; + +class LanguageApi extends Api { + async getAllLanguages() { + return await this.request(null, 'GET', `${this.basicUrl}/language`, true); + } +} + +export default LanguageApi; diff --git a/src/containers/ChecklistItemBlock.js b/src/containers/ChecklistItemBlock.js index 6d8f4f5..70a76b5 100644 --- a/src/containers/ChecklistItemBlock.js +++ b/src/containers/ChecklistItemBlock.js @@ -47,7 +47,6 @@ class ChecklistItemBlock extends React.Component { handleShare(event) { event.stopPropagation(); - console.log('Share Checklist', this.props.checklist); } handleOkClick(event) { diff --git a/src/containers/LanguageDialog.js b/src/containers/LanguageDialog.js index 6a6a683..87bf302 100644 --- a/src/containers/LanguageDialog.js +++ b/src/containers/LanguageDialog.js @@ -9,7 +9,7 @@ import FormLabel from '@material-ui/core/FormLabel'; import FormGroup from '@material-ui/core/FormGroup'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import Checkbox from '../common/components/Checkbox'; -import { SupportedLanguages } from '../utils/enums'; +import Spinner from '../common/components/Spinner'; import { connect } from 'react-redux'; import * as userActions from '../actions/userActions'; import { bindActionCreators } from 'redux'; @@ -18,104 +18,54 @@ import { withStyles } from '@material-ui/core/styles'; const styles = theme => ({ dialogContent: { width: '400px', - height: '400px' + maxHeight: '400px' } }); class LanguageDialog extends React.Component { - constructor(props) { - super(); + constructor(props, context) { + super(props, context); this.state = { - languages: this.filterLanguages(new SupportedLanguages().languages, props.user.user.languages), - user: Object.assign({}, props.user.user) + user: Object.assign({}, this.props.user.user) }; - this.filterLanguages = this.filterLanguages.bind(this); + this.originalLanguages = [...this.props.user.user.languages]; + this.handleCancelClick = this.handleCancelClick.bind(this); this.handleCheckboxChange = this.handleCheckboxChange.bind(this); this.handleOkClick = this.handleOkClick.bind(this); this.handleClose = this.handleClose.bind(this); } - filterLanguages(languages, userLanguages) { - return ( - languages - .map(l => { - return { - name: l.name, - code: l.code, - checked: userLanguages.includes(l.code) - }; - }) - // Sort Alphabetically - .sort((a, b) => { - const nameA = a.name.toUpperCase(); - const nameB = b.name.toUpperCase(); - - if (nameA < nameB) { - return -1; - } - - if (nameA > nameB) { - return 1; - } - - return 0; - }) - // Checked at the top of the list - .sort((a, b) => { - if (a.checked && !b.checked) { - return -1; - } - if (!a.checked && b.checked) { - return 1; - } - - return 0; - }) - ); - } - handleCancelClick() { this.props.onClose(); } - handleCheckboxChange = languageName => event => { - const languages = [...this.state.languages]; + handleCheckboxChange(selectedLanguage) { + return event => { + const user = Object.assign({}, this.props.user.user); - for (let i = 0; i < languages.length; i++) { - const language = languages[i]; + if(user.languages.includes(selectedLanguage.code)) { + const index = user.languages.indexOf(selectedLanguage.code); - if (language.name === languageName) { - language.checked = event.target.checked; - - break; + user.languages.splice(index, 1); + } else { + user.languages.push(selectedLanguage.code); } - } - - this.setState({ - languages - }); - }; - - handleOkClick(event) { - const user = Object.assign({}, this.state.user); - const languages = [...this.state.languages]; - const langCodes = languages.filter(l => l.checked).map(l => l.code); - - if (langCodes.length !== user.languages.length || !langCodes.every(l => user.languages.includes(l))) { - user.languages = langCodes; - - this.props.actions.updateUser(user); this.setState({ - languages: this.filterLanguages(languages, user.languages), user }); + }; + } + handleOkClick(event) { + if (isArraysEqual(this.originalLanguages, this.state.user.languages)) { this.props.onClose(); } else { + this.props.userActions.updateUser(this.state.user); + this.props.onClose(); } } @@ -139,19 +89,22 @@ class LanguageDialog extends React.Component { Select language - {this.state.languages.map(language => ( - - } - /> - ))} + {this.props.languages.isFetching + ? () + : this.props.languages.languages.map(language => ( + + } + /> + )) + } @@ -169,16 +122,27 @@ class LanguageDialog extends React.Component { function mapStateToProps(state, ownProps) { return { - user: state.user + user: state.user, + languages: state.languages }; } function mapDispatchToProps(dispatch) { return { - actions: bindActionCreators(userActions, dispatch) + userActions: bindActionCreators(userActions, dispatch) }; } +function isArraysEqual(a, b) { + const arrayA = [...a]; + const arrayB = [...b]; + + arrayA.sort(); + arrayB.sort(); + + return arrayA.length === arrayB.length && arrayA.every((element, index) => element === arrayB[index]); +} + LanguageDialog.propTypes = { open: PropTypes.bool.isRequired, classes: PropTypes.object.isRequired, diff --git a/src/pages/ChecklistPage.js b/src/pages/ChecklistPage.js index 918f044..b1f1169 100644 --- a/src/pages/ChecklistPage.js +++ b/src/pages/ChecklistPage.js @@ -14,6 +14,7 @@ import ExpansionPanel from '../components/ExpansionPanel'; import ExpansionPanelSummary from '../components/ExpansionPanelSummary'; import * as checklistActions from '../actions/checklistActions'; import * as userActions from '../actions/userActions'; +import * as languageActions from '../actions/languageActions'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { PropTypes } from 'prop-types'; @@ -48,9 +49,11 @@ class ChecklistPage extends React.Component { constructor(props, context) { super(props, context); + const hasUserAnyLanguages = this.props.user.user.languages.length === 0; + this.state = { user: Object.assign({}, this.props.user.user), - openLanguageDialog: this.props.user.user.languages.length === 0, + openLanguageDialog: hasUserAnyLanguages, drawerIsOpened: false, openElementDialog: false, checklist: { @@ -75,6 +78,7 @@ class ChecklistPage extends React.Component { componentWillMount() { this.props.checklistActions.loadChecklists(); + this.props.languageActions.getLanguages(); } handleLogoutClick(event) { @@ -134,7 +138,7 @@ class ChecklistPage extends React.Component { } }); } - + handAddNewChecklistButtonClick(event) { this.setState({ openElementDialog: true @@ -157,7 +161,7 @@ class ChecklistPage extends React.Component { render() { const { classes } = this.props; - const isFetching = this.props.isFetching; + const isChecklistsFetching = this.props.isChecklistsFetching; const isApiAddChecklist = this.props.isApiAddChecklist; const drawerOptions = [ { @@ -185,21 +189,21 @@ class ChecklistPage extends React.Component { aria-label="Add checklist" size="medium" className={classes.addChecklistButton} - onClick={isFetching ? undefined : this.handAddNewChecklistButtonClick} + onClick={isChecklistsFetching ? undefined : this.handAddNewChecklistButtonClick} > - {isFetching ? ( + {isChecklistsFetching ? ( ) : ( this.props.checklists.map(checklist => ( @@ -252,7 +256,8 @@ function mapStateToProps(state, ownProps) { isAuthenticated: state.user.isAuthenticated, user: state.user, checklists: state.checklists.checklists, - isFetching: state.checklists.isFetching, + languages: state.languages.languages, + isChecklistsFetching: state.checklists.isFetching, isApiAddChecklist: state.checklists.isApiAddChecklist }; } @@ -260,16 +265,18 @@ function mapStateToProps(state, ownProps) { function mapDispatchToProps(dispatch) { return { checklistActions: bindActionCreators(checklistActions, dispatch), - userActions: bindActionCreators(userActions, dispatch) + userActions: bindActionCreators(userActions, dispatch), + languageActions: bindActionCreators(languageActions, dispatch) }; } ChecklistPage.propTypes = { classes: PropTypes.object.isRequired, - isFetching: PropTypes.bool.isRequired, + isChecklistsFetching: PropTypes.bool.isRequired, isAuthenticated: PropTypes.bool.isRequired, user: PropTypes.object.isRequired, - checklists: PropTypes.array.isRequired + checklists: PropTypes.array.isRequired, + languages: PropTypes.array.isRequired }; export default connect( diff --git a/src/reducers/index.js b/src/reducers/index.js index c568f69..45068ee 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,10 +1,12 @@ import { combineReducers } from 'redux'; import checklists from './checklistReducer'; import user from './userReducer'; +import languages from './languageReducer'; const rootReducer = combineReducers({ checklists, - user + user, + languages }); export default rootReducer; diff --git a/src/reducers/initialState.js b/src/reducers/initialState.js index 63e0d04..067dbad 100644 --- a/src/reducers/initialState.js +++ b/src/reducers/initialState.js @@ -9,5 +9,9 @@ export default { isFetching: false, user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user')) : null, isAuthenticated: isTokenValid() + }, + languages: { + languages: [], + isFetching: false } }; diff --git a/src/reducers/languageReducer.js b/src/reducers/languageReducer.js new file mode 100644 index 0000000..9a731c7 --- /dev/null +++ b/src/reducers/languageReducer.js @@ -0,0 +1,27 @@ +import * as types from '../actions/actionTypes'; +import initialState from './initialState'; + +export default function languageReducer(state = initialState.languages, action) { + switch (action.type) { + case types.REQUEST_LANGUAGES: + return Object.assign({}, state, { + isFetching: action.isFetching, + languages: action.languages, + user: action.creds + }); + case types.GET_LANGUAGES_SUCCESS: + return Object.assign({}, state, { + isFetching: action.isFetching, + languages: action.languages, + user: action.user + }); + case types.GET_LANGUAGES_FAILURE: + return Object.assign({}, state, { + isFetching: action.isFetching, + languages: action.languages, + errorMessage: action.message + }); + default: + return state; + } +} diff --git a/src/reducers/userReducer.js b/src/reducers/userReducer.js index 06f357e..78043c6 100644 --- a/src/reducers/userReducer.js +++ b/src/reducers/userReducer.js @@ -59,6 +59,20 @@ export default function userReducer(state = initialState.auth, action) { isAuthenticated: action.isAuthenticated, user: {} }); + case types.REQUEST_UPDATE_USER: + return Object.assign({}, state, { + isFetching: action.isFetching + }); + case types.UPDATE_USER_SUCCESS: + return Object.assign({}, state, { + isFetching: action.isFetching, + user: action.user + }); + case types.UPDATE_USER_FAILURE: + return Object.assign({}, state, { + isFetching: action.isFetching, + errorMessage: action.message + }); default: return state; }