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

View file

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

View file

@ -1,11 +1,13 @@
import React, { Component } from "react";
import PropType from "prop-types";
import { string, func } from "prop-types";
import { Button } from "react-bootstrap";
import styled from "styled-components";
import TimezonePicker from "react-bootstrap-timezone-picker";
import "react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css";
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";
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 {
constructor(props) {
super(props);
this.state = {
value: props.inputValue
value: "",
disabled: true
};
this.handleChange = this.handleChange.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.enableEdit = this.enableEdit.bind(this);
this.disableEdit = this.disableEdit.bind(this);
}
handleChange(timezone) {
@ -40,20 +55,49 @@ class InputTimezone extends Component {
handleUpdate() {
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() {
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 (
<Wrapper>
<h4>
{this.props.labelValue}
<FormattedMessage id="settings_page.time_zone" />
</h4>
<TimezonePicker
absolute
defaultValue="Europe/London"
value={this.props.inputValue}
value={this.state.value}
placeholder="Select timezone..."
onChange={this.handleChange}
/>
@ -62,11 +106,11 @@ class InputTimezone extends Component {
<FormattedMessage id="settings_page.time_zone_warning" />
</small>
</div>
<Button bsStyle="primary" onClick={this.props.disableEdit}>
Cancel
<Button bsStyle="primary" onClick={this.disableEdit}>
<FormattedMessage id="general.cancel" />
</Button>
<Button bsStyle="default" onClick={this.handleUpdate}>
Update
<FormattedMessage id="general.update" />
</Button>
</Wrapper>
);
@ -74,10 +118,8 @@ class InputTimezone extends Component {
}
InputTimezone.propTypes = {
labelValue: PropType.string.isRequired,
inputValue: PropType.string.isRequired,
disableEdit: PropType.func.isRequired,
saveData: PropType.func.isRequired
value: string.isRequired,
loadPreferences: func.isRequired
};
export default InputTimezone;

View file

@ -1,10 +1,9 @@
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 { 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 InputTimezone from "./components/InputTimezone";
import NotificationsGroup from "./components/NotificationsGroup";
@ -21,106 +20,50 @@ import {
BORDER_LIGHT_COLOR
} from "../../../../config/constants/colors";
const WrapperInputDisabled = styled.div`
const TutorialWrapper = styled.div`
margin: 20px 0;
padding-bottom: 15px;
border-bottom: 1px solid ${BORDER_LIGHT_COLOR};
.settings-warning {
margin-top: -5px;
}
`;
`
class SettingsPreferences extends Component {
constructor(props) {
super(props);
this.state = {
isTimeZoneEditable: false,
email: "",
notifications: {
assignmentsNotification: false,
assignmentsNotificationEmail: false,
recentNotification: false,
recentNotificationEmail: false,
systemMessageNofificationEmail: false
}
timeZone: "",
assignmentsNotification: false,
assignmentsEmailNotification: false,
recentNotification: false,
recentEmailNotification: false,
systemMessageEmailNofification: false
};
this.setData = this.setData.bind(this);
this.getPreferencesInfo = this.getPreferencesInfo.bind(this)
}
componentDidMount() {
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() {
// axios
// .get("/client_api/users/preferences_info")
// .then(response => this.setData(response))
// .catch(error => console.log(error));
getUserPreferencesInfo().then(data => {
this.setState(data);
});
}
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 (
<SettingsAccountWrapper>
<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>
<NotificationsGroup
type={ASSIGNMENT_NOTIFICATION}
@ -152,11 +95,4 @@ class SettingsPreferences extends Component {
}
}
SettingsPreferences.propTypes = {
changeTimezone: PropTypes.func.isRequired,
avatarPath: PropTypes.string.isRequired
};
const mapStateToProps = state => state.current_user;
export default connect(mapStateToProps)(SettingsPreferences);
export default 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 TEAM_DETAILS_PATH = "/client_api/teams/:team_id/details";
export const TEAM_UPDATE_PATH = "/client_api/teams/update";
export const CURRENT_USER_PATH = "/client_api/current_user_info"
// search
export const SEARCH_PATH = "/search";
@ -19,6 +20,7 @@ export const RECENT_NOTIFICATIONS_PATH = "/client_api/recent_notifications";
// users
export const USER_PROFILE_INFO = "/client_api/users/profile_info";
export const UPDATE_USER_PATH = "/client_api/users/update";
export const PREFERENCES_INFO_PATH = "/client_api/users/preferences_info"
// info dropdown_title
export const CUSTOMER_SUPPORT_LINK = "http://scinote.net/support";

View file

@ -2,12 +2,15 @@ import { axiosInstance } from "./config";
import {
USER_PROFILE_INFO,
UPDATE_USER_PATH,
CURRENT_USER_PATH
CURRENT_USER_PATH,
PREFERENCES_INFO_PATH
} from "./endpoints";
export const getUserProfileInfo = () => {
return axiosInstance.get(USER_PROFILE_INFO).then(({ data }) => data.user);
};
export const getUserProfileInfo = () =>
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) => {
if (formObj) {

View file

@ -36,7 +36,7 @@ class User < ApplicationRecord
size: { less_than: Constants::AVATAR_MAX_SIZE_MB.megabytes }
validate :time_zone_check
store_accessor :settings, :time_zone
store_accessor :settings, :time_zone, :notifications
default_settings(
time_zone: 'UTC',
@ -48,7 +48,31 @@ class User < ApplicationRecord
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
has_many :user_teams, inverse_of: :user
has_many :teams, through: :user_teams

View file

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

View file

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