adds getter and setters for user settings

This commit is contained in:
zmagod 2017-09-27 14:38:52 +02:00
parent 66269d68ff
commit 26b526d61b
9 changed files with 130 additions and 120 deletions

View file

@ -3,11 +3,15 @@ module ClientApi
class UsersController < ApplicationController class UsersController < ApplicationController
def preferences_info def preferences_info
settings = current_user.settings
respond_to do |format| respond_to do |format|
format.json do format.json do
render template: 'client_api/users/preferences', render template: 'client_api/users/preferences',
status: :ok, status: :ok,
locals: { user: current_user} locals: {
timeZone: settings['time_zone'],
notifications: settings['notifications']
}
end end
end end
end end
@ -67,7 +71,9 @@ module ClientApi
:full_name, :full_name,
:password_confirmation, :password_confirmation,
:current_password, :current_password,
:avatar) :avatar,
:assignments,
:time_zone)
end end
def change_notification(dinamic_param, params) def change_notification(dinamic_param, params)

View file

@ -54,6 +54,7 @@ export default {
time_zone: "Time zone", time_zone: "Time zone",
time_zone_warning: time_zone_warning:
"Time zone setting affects all time & date fields throughout application.", "Time zone setting affects all time & date fields throughout application.",
repeat_tutorial: "Repeat tutorial",
profile: "Profile", profile: "Profile",
preferences: "Preferences", preferences: "Preferences",
assignement: "Assignement", assignement: "Assignement",

View file

@ -1,11 +1,13 @@
import React, { Component } from "react"; import React, { Component } from "react";
import PropType from "prop-types"; import { string, func } from "prop-types";
import { Button } from "react-bootstrap"; import { Button } from "react-bootstrap";
import styled from "styled-components"; import styled from "styled-components";
import TimezonePicker from "react-bootstrap-timezone-picker"; import TimezonePicker from "react-bootstrap-timezone-picker";
import "react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css"; import "react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { updateUser } from "../../../../../services/api/users_api";
import InputDisabled from "../../../components/InputDisabled";
import { BORDER_LIGHT_COLOR } from "../../../../../config/constants/colors"; import { BORDER_LIGHT_COLOR } from "../../../../../config/constants/colors";
const Wrapper = styled.div` const Wrapper = styled.div`
@ -22,16 +24,29 @@ const Wrapper = styled.div`
} }
`; `;
const WrapperInputDisabled = styled.div`
margin: 20px 0;
padding-bottom: 15px;
border-bottom: 1px solid ${BORDER_LIGHT_COLOR};
.settings-warning {
margin-top: -5px;
}
`;
class InputTimezone extends Component { class InputTimezone extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
value: props.inputValue value: "",
disabled: true
}; };
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
this.handleUpdate = this.handleUpdate.bind(this); this.handleUpdate = this.handleUpdate.bind(this);
this.enableEdit = this.enableEdit.bind(this);
this.disableEdit = this.disableEdit.bind(this);
} }
handleChange(timezone) { handleChange(timezone) {
@ -40,20 +55,49 @@ class InputTimezone extends Component {
handleUpdate() { handleUpdate() {
if (this.state.value !== "") { if (this.state.value !== "") {
this.props.saveData(this.state.value); updateUser({ time_zone: this.state.value }).then(() => {
this.disableEdit();
});
} }
this.props.disableEdit();
} }
enableEdit() {
this.setState({ disabled: false, value: this.props.value });
}
disableEdit() {
this.setState({ disabled: true });
this.props.loadPreferences();
}
render() { render() {
if (this.state.disabled) {
return (
<WrapperInputDisabled>
<InputDisabled
labelTitle="settings_page.time_zone"
inputValue={this.props.value}
inputType="text"
enableEdit={this.enableEdit}
/>
<div className="settings-warning">
<small>
<FormattedMessage id="settings_page.time_zone_warning" />
</small>
</div>
</WrapperInputDisabled>
);
}
return ( return (
<Wrapper> <Wrapper>
<h4> <h4>
{this.props.labelValue} <FormattedMessage id="settings_page.time_zone" />
</h4> </h4>
<TimezonePicker <TimezonePicker
absolute absolute
defaultValue="Europe/London" defaultValue="Europe/London"
value={this.props.inputValue} value={this.state.value}
placeholder="Select timezone..." placeholder="Select timezone..."
onChange={this.handleChange} onChange={this.handleChange}
/> />
@ -62,11 +106,11 @@ class InputTimezone extends Component {
<FormattedMessage id="settings_page.time_zone_warning" /> <FormattedMessage id="settings_page.time_zone_warning" />
</small> </small>
</div> </div>
<Button bsStyle="primary" onClick={this.props.disableEdit}> <Button bsStyle="primary" onClick={this.disableEdit}>
Cancel <FormattedMessage id="general.cancel" />
</Button> </Button>
<Button bsStyle="default" onClick={this.handleUpdate}> <Button bsStyle="default" onClick={this.handleUpdate}>
Update <FormattedMessage id="general.update" />
</Button> </Button>
</Wrapper> </Wrapper>
); );
@ -74,10 +118,8 @@ class InputTimezone extends Component {
} }
InputTimezone.propTypes = { InputTimezone.propTypes = {
labelValue: PropType.string.isRequired, value: string.isRequired,
inputValue: PropType.string.isRequired, loadPreferences: func.isRequired
disableEdit: PropType.func.isRequired,
saveData: PropType.func.isRequired
}; };
export default InputTimezone; export default InputTimezone;

View file

@ -1,10 +1,9 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import styled from "styled-components";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { Button } from "react-bootstrap";
import styled from "styled-components";
import InputDisabled from "../../components/InputDisabled"; import { getUserPreferencesInfo } from "../../../../services/api/users_api";
import SettingsAccountWrapper from "../../components/SettingsAccountWrapper"; import SettingsAccountWrapper from "../../components/SettingsAccountWrapper";
import InputTimezone from "./components/InputTimezone"; import InputTimezone from "./components/InputTimezone";
import NotificationsGroup from "./components/NotificationsGroup"; import NotificationsGroup from "./components/NotificationsGroup";
@ -21,106 +20,50 @@ import {
BORDER_LIGHT_COLOR BORDER_LIGHT_COLOR
} from "../../../../config/constants/colors"; } from "../../../../config/constants/colors";
const WrapperInputDisabled = styled.div` const TutorialWrapper = styled.div`
margin: 20px 0; margin: 20px 0;
padding-bottom: 15px; padding-bottom: 15px;
border-bottom: 1px solid ${BORDER_LIGHT_COLOR}; border-bottom: 1px solid ${BORDER_LIGHT_COLOR};
`
.settings-warning {
margin-top: -5px;
}
`;
class SettingsPreferences extends Component { class SettingsPreferences extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
isTimeZoneEditable: false, timeZone: "",
email: "", assignmentsNotification: false,
notifications: { assignmentsEmailNotification: false,
assignmentsNotification: false, recentNotification: false,
assignmentsNotificationEmail: false, recentEmailNotification: false,
recentNotification: false, systemMessageEmailNofification: false
recentNotificationEmail: false,
systemMessageNofificationEmail: false
}
}; };
this.setData = this.setData.bind(this); this.getPreferencesInfo = this.getPreferencesInfo.bind(this)
} }
componentDidMount() { componentDidMount() {
this.getPreferencesInfo(); this.getPreferencesInfo();
} }
toggleIsEditable(fieldNameEnabled) {
const editableState = this.state[fieldNameEnabled];
this.setState({ [fieldNameEnabled]: !editableState });
}
setData({ data }) {
const user = data.user;
const newData = {
timeZone: user.timeZone,
notifications: {
assignmentsNotification: user.notifications.assignmentsNotification,
assignmentsNotificationEmail:
user.notifications.assignmentsNotificationEmail,
recentNotification: user.notifications.recentNotification,
recentNotificationEmail: user.notifications.recentNotificationEmail,
systemMessageNofificationEmail:
user.notifications.systemMessageNofificationEmail
}
};
this.setState(Object.assign({}, this.state, newData));
}
getPreferencesInfo() { getPreferencesInfo() {
// axios getUserPreferencesInfo().then(data => {
// .get("/client_api/users/preferences_info") this.setState(data);
// .then(response => this.setData(response)) });
// .catch(error => console.log(error));
} }
render() { render() {
const isTimeZoneEditable = "isTimeZoneEditable";
let timezoneField;
if (this.state.isTimeZoneEditable) {
timezoneField = (
<InputTimezone
labelValue="Time zone"
inputValue={this.state.timeZone}
disableEdit={() => this.toggleIsEditable(isTimeZoneEditable)}
saveData={timeZone => this.props.changeTimezone(timeZone)}
/>
);
} else {
timezoneField = (
<WrapperInputDisabled>
<InputDisabled
labelTitle="settings_page.time_zone"
inputValue={this.state.timeZone}
inputType="text"
enableEdit={() => this.toggleIsEditable(isTimeZoneEditable)}
/>
<div className="settings-warning">
<small>
<FormattedMessage id="settings_page.time_zone_warning" />
</small>
</div>
</WrapperInputDisabled>
);
}
return ( return (
<SettingsAccountWrapper> <SettingsAccountWrapper>
<div className="col-xs-12 col-sm-9"> <div className="col-xs-12 col-sm-9">
{timezoneField} <InputTimezone
value={this.state.timeZone}
loadPreferences={this.getPreferencesInfo}
/>
<TutorialWrapper>
<Button bsStyle="success">
<FormattedMessage id="settings_page.repeat_tutorial" />
</Button>
</TutorialWrapper>
<h3>Notifications</h3> <h3>Notifications</h3>
<NotificationsGroup <NotificationsGroup
type={ASSIGNMENT_NOTIFICATION} type={ASSIGNMENT_NOTIFICATION}
@ -152,11 +95,4 @@ class SettingsPreferences extends Component {
} }
} }
SettingsPreferences.propTypes = { export default SettingsPreferences;
changeTimezone: PropTypes.func.isRequired,
avatarPath: PropTypes.string.isRequired
};
const mapStateToProps = state => state.current_user;
export default connect(mapStateToProps)(SettingsPreferences);

View file

@ -9,6 +9,7 @@ export const TEAMS_PATH = "/client_api/teams";
export const CHANGE_TEAM_PATH = "/client_api/teams/change_team"; export const CHANGE_TEAM_PATH = "/client_api/teams/change_team";
export const TEAM_DETAILS_PATH = "/client_api/teams/:team_id/details"; export const TEAM_DETAILS_PATH = "/client_api/teams/:team_id/details";
export const TEAM_UPDATE_PATH = "/client_api/teams/update"; export const TEAM_UPDATE_PATH = "/client_api/teams/update";
export const CURRENT_USER_PATH = "/client_api/current_user_info"
// search // search
export const SEARCH_PATH = "/search"; export const SEARCH_PATH = "/search";
@ -19,6 +20,7 @@ export const RECENT_NOTIFICATIONS_PATH = "/client_api/recent_notifications";
// users // users
export const USER_PROFILE_INFO = "/client_api/users/profile_info"; export const USER_PROFILE_INFO = "/client_api/users/profile_info";
export const UPDATE_USER_PATH = "/client_api/users/update"; export const UPDATE_USER_PATH = "/client_api/users/update";
export const PREFERENCES_INFO_PATH = "/client_api/users/preferences_info"
// info dropdown_title // info dropdown_title
export const CUSTOMER_SUPPORT_LINK = "http://scinote.net/support"; export const CUSTOMER_SUPPORT_LINK = "http://scinote.net/support";

View file

@ -2,12 +2,15 @@ import { axiosInstance } from "./config";
import { import {
USER_PROFILE_INFO, USER_PROFILE_INFO,
UPDATE_USER_PATH, UPDATE_USER_PATH,
CURRENT_USER_PATH CURRENT_USER_PATH,
PREFERENCES_INFO_PATH
} from "./endpoints"; } from "./endpoints";
export const getUserProfileInfo = () => { export const getUserProfileInfo = () =>
return axiosInstance.get(USER_PROFILE_INFO).then(({ data }) => data.user); axiosInstance.get(USER_PROFILE_INFO).then(({ data }) => data.user);
};
export const getUserPreferencesInfo = () =>
axiosInstance.get(PREFERENCES_INFO_PATH).then(({ data }) => data);
export const updateUser = (params, formObj = false) => { export const updateUser = (params, formObj = false) => {
if (formObj) { if (formObj) {

View file

@ -36,7 +36,7 @@ class User < ApplicationRecord
size: { less_than: Constants::AVATAR_MAX_SIZE_MB.megabytes } size: { less_than: Constants::AVATAR_MAX_SIZE_MB.megabytes }
validate :time_zone_check validate :time_zone_check
store_accessor :settings, :time_zone store_accessor :settings, :time_zone, :notifications
default_settings( default_settings(
time_zone: 'UTC', time_zone: 'UTC',
@ -48,7 +48,31 @@ class User < ApplicationRecord
system_message_email: false system_message_email: false
} }
) )
# json.assignmentsNotification notifications['assignments']
# json.assignmentsEmailNotification notifications['assignments_email']
# json.recentNotification notifications['recent']
# json.recentEmailNotification notifications['recent_email']
# json.systemMessageEmailNofification notifications['system_message_email']
# joson friendly attributes
NOTIFICATIONS_TYPES = %w(assignmentsNotification assignmentsEmailNotification
recentNotification recentEmailNotification
systemMessageEmailNofification)
# declare notifications getters
NOTIFICATIONS_TYPES.each do |name|
define_method(name) do
attr_name = name.slice!('Notification').underscore
self.notifications.fetch(attr_name.to_sym)
end
end
# declare notifications setters
NOTIFICATIONS_TYPES.each do |name|
define_method("#{name}=") do |value|
attr_name = name.slice!('Notification').underscore
self.notifications[attr_name.to_sym] = value
save
end
end
# Relations # Relations
has_many :user_teams, inverse_of: :user has_many :user_teams, inverse_of: :user
has_many :teams, through: :user_teams has_many :teams, through: :user_teams

View file

@ -1,10 +1,6 @@
json.user do json.timeZone timeZone
json.timeZone user.time_zone json.assignmentsNotification notifications['assignments']
json.notifications do json.assignmentsEmailNotification notifications['assignments_email']
json.assignmentsNotification user.assignments_notification json.recentNotification notifications['recent']
json.assignmentsNotificationEmail user.assignments_notification_email json.recentEmailNotification notifications['recent_email']
json.recentNotification user.recent_notification json.systemMessageEmailNofification notifications['system_message_email']
json.recentNotificationEmail user.recent_notification_email
json.systemMessageNofificationEmail user.system_message_notification_email
end
end

View file

@ -1,5 +1,5 @@
json.user do json.user do
json.id user.id json.id user.id
json.fullName user.full_name json.fullName user.full_name
json.avatarPath avatar_path(user, :icon_small) json.avatarThumb avatar_path(user, :icon_small)
end end