mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-03 10:24:30 +08:00
Merge branch 'decoupling-settings-page' of https://github.com/biosistemika/scinote-web into zd_SCI_1493_v3
This commit is contained in:
commit
34be62a648
36 changed files with 12259 additions and 484 deletions
|
@ -17,12 +17,7 @@
|
|||
}
|
||||
},
|
||||
"rules": {
|
||||
"import/no-unresolved": [
|
||||
"error",
|
||||
{
|
||||
"ignore": ["app/javascript/"]
|
||||
}
|
||||
],
|
||||
"import/no-unresolved": [2, {"commonjs": true, "amd": true}],
|
||||
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
|
||||
"spaced-comment": [
|
||||
"error",
|
||||
|
|
|
@ -8,3 +8,18 @@ export const DESTROY_GLOBAL_ACTIVITIES_DATA = "DESTROY_GLOBAL_ACTIVITIES_DATA";
|
|||
|
||||
// users
|
||||
export const SET_CURRENT_USER = "SET_CURRENT_USER";
|
||||
export const CHANGE_CURRENT_USER_FULL_NAME = "CHANGE_CURRENT_USER_FULL_NAME";
|
||||
export const CHANGE_CURRENT_USER_INITIALS = "CHANGE_CURRENT_USER_INITIALS";
|
||||
export const CHANGE_CURRENT_USER_EMAIL = "CHANGE_CURRENT_USER_EMAIL";
|
||||
export const CHANGE_CURRENT_USER_PASSWORD = "CHANGE_CURRENT_USER_PASSWORD";
|
||||
export const CHANGE_CURRENT_USER_AVATAR = "CHANGE_CURRENT_USER_AVATAR";
|
||||
export const CHANGE_CURRENT_USER_TIMEZONE = "CHANGE_CURRENT_USER_TIMEZONE";
|
||||
export const CHANGE_ASSIGNMENTS_NOTIFICATION =
|
||||
"CHANGE_ASSIGNMENTS_NOTIFICATION";
|
||||
export const CHANGE_ASSIGNMENTS_NOTIFICATION_EMAIL =
|
||||
"CHANGE_ASSIGNMENTS_NOTIFICATION_EMAIL";
|
||||
export const CHANGE_RECENT_NOTIFICATION = "CHANGE_RECENT_NOTIFICATION";
|
||||
export const CHANGE_RECENT_NOTIFICATION_EMAIL =
|
||||
"CHANGE_RECENT_NOTIFICATION_EMAIL";
|
||||
export const CHANGE_SYSTEM_MESSAGE_NOTIFICATION_EMAIL =
|
||||
"CHANGE_SYSTEM_MESSAGE_NOTIFICATION_EMAIL";
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
export const MAIN_COLOR_BLUE = "#37a0d9";
|
||||
export const LIGHT_BLUE_COLOR = "#37a0d9";
|
||||
export const WHITE_COLOR = "#fff";
|
||||
export const BORDER_GRAY_COLOR = "#d2d2d2";
|
||||
export const DARK_GRAY_COLOR = "#7a7a7a";
|
||||
export const BORDER_LIGHT_COLOR = "#e3e3e3";
|
||||
export const WILD_SAND_COLOR = "#f5f5f5";
|
||||
export const MYSTIC_COLOR = "#eaeff2";
|
||||
export const COLOR_CONCRETE = "#f2f2f2";
|
||||
export const COLOR_MINE_SHAFT = "#333"
|
||||
export const COLOR_BLACK = "#000";
|
||||
export const COLOR_GRAY_LIGHT_YADCF = "#ccc";
|
||||
export const ICON_GREEN_COLOR = "#8fd13f";
|
||||
export const NOTIFICATION_YES = "#5a8921";
|
||||
export const NOTIFICATION_YES_BORDER = "#4d751c";
|
||||
export const SIDEBAR_HOVER_GRAY_COLOR = "#D2D2D2";
|
||||
|
||||
export const COLOR_ALTO = "#dddddd";
|
||||
export const COLOR_GRAY = "#909088";
|
||||
export const COLOR_ALABASTER = "#fcfcfc";
|
||||
|
|
|
@ -21,3 +21,8 @@ export const RELEASE_NOTES_LINK = "http://scinote.net/docs/release-notes/";
|
|||
export const PREMIUM_LINK = "http://scinote.net/premium/";
|
||||
export const CONTACT_US_LINK =
|
||||
"http://scinote.net/story-of-scinote/#contact-scinote";
|
||||
|
||||
// settings
|
||||
export const SETTINGS_ACCOUNT_PROFILE = "/settings/account/profile";
|
||||
export const SETTINGS_ACCOUNT_PREFERENCES = "/settings/account/preferences";
|
||||
export const SETTINGS_TEAMS = "/settings/teams";
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
export default {
|
||||
"en-US": {
|
||||
general: {
|
||||
close: "Close"
|
||||
close: "Close",
|
||||
cancel: "Cancel",
|
||||
update: "Update",
|
||||
edit: "Edit",
|
||||
loading: "Loading ..."
|
||||
},
|
||||
navbar: {
|
||||
page_title: "sciNote",
|
||||
|
@ -13,6 +17,44 @@ export default {
|
|||
notifications_label: "Notifications",
|
||||
info_label: "Info"
|
||||
},
|
||||
settings_page: {
|
||||
account: "Account",
|
||||
team: "Team",
|
||||
avatar: "Avatar",
|
||||
edit_avatar: "Edit Avatar",
|
||||
change: "Change",
|
||||
change_password: "Change Password",
|
||||
new_email: "New email",
|
||||
initials: "Initials",
|
||||
full_name: "Full name",
|
||||
my_profile: "My Profile",
|
||||
my_statistics: "My Statistics",
|
||||
teams: "Teams",
|
||||
project: "Project",
|
||||
projects: "Projects",
|
||||
experiment: "Experiment",
|
||||
experiments: "Experiments",
|
||||
protocol: "Protocol",
|
||||
protocols: "Protocols",
|
||||
time_zone: "Time zone",
|
||||
time_zone_warning:
|
||||
"Time zone setting affects all time & date fields throughout application.",
|
||||
profile: "Profile",
|
||||
preferences: "Preferences",
|
||||
assignement: "Assignement",
|
||||
assignement_msg:
|
||||
"Assignment notifications appear whenever you get assigned to a team, project, task.",
|
||||
recent_changes: "Recent changes",
|
||||
recent_changes_msg:
|
||||
"Recent changes notifications appear whenever there is a change on a task you are assigned to.",
|
||||
system_message: "System message",
|
||||
system_message_msg:
|
||||
"System message notifications are specifically sent by site maintainers to notify all users about a system update.",
|
||||
show_in_scinote: "Show in sciNote",
|
||||
notify_me_via_email: "Notify me via email",
|
||||
no: "No",
|
||||
yes: "Yes"
|
||||
},
|
||||
activities: {
|
||||
modal_title: "Activities",
|
||||
no_data: "No Data",
|
||||
|
|
5
app/javascript/packs/shared/404/NotFound.jsx
Normal file
5
app/javascript/packs/shared/404/NotFound.jsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React from "react";
|
||||
|
||||
const NotFound = () => <h1>404 Not found</h1>;
|
||||
|
||||
export default NotFound;
|
|
@ -1,6 +1,19 @@
|
|||
import axios from "../../app/axios";
|
||||
import { CURRENT_USER_PATH } from "../../app/routes";
|
||||
import { SET_CURRENT_USER } from "../../app/action_types";
|
||||
import {
|
||||
SET_CURRENT_USER,
|
||||
CHANGE_CURRENT_USER_FULL_NAME,
|
||||
CHANGE_CURRENT_USER_INITIALS,
|
||||
CHANGE_CURRENT_USER_EMAIL,
|
||||
CHANGE_CURRENT_USER_PASSWORD,
|
||||
CHANGE_CURRENT_USER_AVATAR,
|
||||
CHANGE_CURRENT_USER_TIMEZONE,
|
||||
CHANGE_ASSIGNMENTS_NOTIFICATION,
|
||||
CHANGE_ASSIGNMENTS_NOTIFICATION_EMAIL,
|
||||
CHANGE_RECENT_NOTIFICATION,
|
||||
CHANGE_RECENT_NOTIFICATION_EMAIL,
|
||||
CHANGE_SYSTEM_MESSAGE_NOTIFICATION_EMAIL
|
||||
} from "../../app/action_types";
|
||||
|
||||
function addCurrentUser(data) {
|
||||
return {
|
||||
|
@ -21,3 +34,80 @@ export function getCurrentUser() {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function changeFullName(name) {
|
||||
return {
|
||||
type: CHANGE_CURRENT_USER_FULL_NAME,
|
||||
payload: name
|
||||
};
|
||||
}
|
||||
|
||||
export function changeInitials(initials) {
|
||||
return {
|
||||
type: CHANGE_CURRENT_USER_INITIALS,
|
||||
payload: initials
|
||||
};
|
||||
}
|
||||
|
||||
export function changeEmail(email) {
|
||||
return {
|
||||
type: CHANGE_CURRENT_USER_EMAIL,
|
||||
payload: email
|
||||
};
|
||||
}
|
||||
|
||||
export function changePassword(password) {
|
||||
return {
|
||||
type: CHANGE_CURRENT_USER_PASSWORD,
|
||||
payload: password
|
||||
};
|
||||
}
|
||||
|
||||
export function changeAvatar(avatarSrc) {
|
||||
return {
|
||||
type: CHANGE_CURRENT_USER_AVATAR,
|
||||
payload: avatarSrc
|
||||
};
|
||||
}
|
||||
|
||||
export function changeTimezone(timezone) {
|
||||
return {
|
||||
type: CHANGE_CURRENT_USER_TIMEZONE,
|
||||
payload: timezone
|
||||
};
|
||||
}
|
||||
|
||||
export function changeAssignmentsNotification(status) {
|
||||
return {
|
||||
type: CHANGE_ASSIGNMENTS_NOTIFICATION,
|
||||
payload: status
|
||||
};
|
||||
}
|
||||
|
||||
export function changeAssignmentsNotificationEmail(status) {
|
||||
return {
|
||||
type: CHANGE_ASSIGNMENTS_NOTIFICATION_EMAIL,
|
||||
payload: status
|
||||
};
|
||||
}
|
||||
|
||||
export function changeRecentNotification(status) {
|
||||
return {
|
||||
type: CHANGE_RECENT_NOTIFICATION,
|
||||
payload: status
|
||||
};
|
||||
}
|
||||
|
||||
export function changeRecentNotificationEmail(status) {
|
||||
return {
|
||||
type: CHANGE_RECENT_NOTIFICATION_EMAIL,
|
||||
payload: status
|
||||
};
|
||||
}
|
||||
|
||||
export function changeSystemMessageNotificationEmail(status) {
|
||||
return {
|
||||
type: CHANGE_SYSTEM_MESSAGE_NOTIFICATION_EMAIL,
|
||||
payload: status
|
||||
};
|
||||
}
|
||||
|
|
149
app/javascript/packs/shared/data_grid/index.jsx
Normal file
149
app/javascript/packs/shared/data_grid/index.jsx
Normal file
|
@ -0,0 +1,149 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ReactDataGrid from 'react-data-grid';
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
WHITE_COLOR,
|
||||
COLOR_GRAY
|
||||
} from "../../app/constants/colors";
|
||||
|
||||
const StyledReactDataGrid = styled(ReactDataGrid)`
|
||||
background-color: inherit;
|
||||
|
||||
.react-grid-Grid {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.react-grid-Header {
|
||||
.react-grid-HeaderCell {
|
||||
background-color: ${COLOR_GRAY};
|
||||
|
||||
color: ${WHITE_COLOR};
|
||||
font-weight: normal;
|
||||
vertical-align: bottom;
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.react-grid-Canvas {
|
||||
background-color: inherit;
|
||||
}
|
||||
`;
|
||||
|
||||
class DataGrid extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.cleanProps = this.cleanProps.bind(this);
|
||||
this.transformColumns = this.transformColumns.bind(this);
|
||||
this.setupDefaultProps = this.setupDefaultProps.bind(this);
|
||||
|
||||
this.transformColumns();
|
||||
this.setupDefaultProps();
|
||||
|
||||
// Store the original rows array, and make a copy that
|
||||
// can be used for modifying eg.filtering, sorting
|
||||
this.state = {
|
||||
originalRows: this.props.data,
|
||||
rows: this.props.data.slice(0)
|
||||
};
|
||||
}
|
||||
|
||||
setupDefaultProps() {
|
||||
// Setup the default props if they're not provided
|
||||
if ('rowGetter' in this.props) {
|
||||
this._rowGetter = this.props.rowGetter;
|
||||
} else {
|
||||
this._rowGetter = ((i) => this.state.rows[i]);
|
||||
}
|
||||
this._rowGetter = this._rowGetter.bind(this);
|
||||
|
||||
if ('rowsCount' in this.props) {
|
||||
this._rowsCount = this.props.rowsCount;
|
||||
} else {
|
||||
this._rowsCount = this.props.data.length;
|
||||
}
|
||||
|
||||
if ('onGridSort' in this.props) {
|
||||
this._onGridSort = this.props.onGridSort;
|
||||
} else {
|
||||
this._onGridSort = ((sortColumn, sortDirection) => {
|
||||
const comparer = (a, b) => {
|
||||
if (sortDirection === 'ASC') {
|
||||
return (a[sortColumn] > b[sortColumn]) ? 1 : -1;
|
||||
} else if (sortDirection === 'DESC') {
|
||||
return (a[sortColumn] < b[sortColumn]) ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
let rows;
|
||||
if (sortDirection === 'NONE') {
|
||||
rows = this.state.originalRows.slice(0);
|
||||
} else {
|
||||
rows = this.state.rows.sort(comparer);
|
||||
}
|
||||
|
||||
this.setState({ rows });
|
||||
});
|
||||
}
|
||||
this._onGridSort = this._onGridSort.bind(this);
|
||||
}
|
||||
|
||||
transformColumns() {
|
||||
// Transform columns from the "sciNote" representation into
|
||||
// ReactDataGrid-compatible representation
|
||||
this._columns =
|
||||
this.props.columns
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.filter((col) => (!('visible' in col) || col.visible))
|
||||
.map((col) => ({
|
||||
key: col.textId,
|
||||
name: col.name,
|
||||
locked: col.locked,
|
||||
sortable: col.sortable
|
||||
}));
|
||||
}
|
||||
|
||||
cleanProps() {
|
||||
// Remove additional props from the props value
|
||||
const {
|
||||
columns, rowGetter, rowsCount, ...cleanProps
|
||||
} = this.props;
|
||||
return cleanProps;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyledReactDataGrid
|
||||
columns={this._columns}
|
||||
rowGetter={this._rowGetter}
|
||||
rowsCount={this._rowsCount}
|
||||
onGridSort={this._onGridSort}
|
||||
{...this.cleanProps()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DataGrid.propTypes = {
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
isKey: PropTypes.bool.isRequired,
|
||||
textId: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
position: PropTypes.number.isRequired,
|
||||
visible: PropTypes.bool,
|
||||
sortable: PropTypes.bool,
|
||||
locked: PropTypes.bool
|
||||
})
|
||||
).isRequired,
|
||||
data: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
rowGetter: PropTypes.func,
|
||||
rowsCount: PropTypes.number,
|
||||
onGridSort: PropTypes.func
|
||||
};
|
||||
|
||||
export default DataGrid;
|
111
app/javascript/packs/shared/data_table/index.jsx
Normal file
111
app/javascript/packs/shared/data_table/index.jsx
Normal file
|
@ -0,0 +1,111 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
WHITE_COLOR,
|
||||
COLOR_GRAY,
|
||||
COLOR_ALTO,
|
||||
COLOR_ALABASTER
|
||||
} from "../../app/constants/colors";
|
||||
|
||||
const StyledBootstrapTable = styled(BootstrapTable)`
|
||||
thead {
|
||||
background-color: ${COLOR_GRAY};
|
||||
|
||||
> tr > th,
|
||||
>tr > td {
|
||||
padding: 6px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
> tr > th {
|
||||
border-bottom: 2px solid ${COLOR_ALTO};
|
||||
border-bottom-width: 0;
|
||||
border-left: 2px solid ${COLOR_ALABASTER};
|
||||
color: ${WHITE_COLOR};
|
||||
font-weight: normal;
|
||||
vertical-align: bottom;
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td, th {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
td {
|
||||
overflow-wrap: break-word;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
}
|
||||
`;
|
||||
|
||||
class DataTable extends Component {
|
||||
static cleanColumnAttributes(col) {
|
||||
// Remove additional attributes from the columns
|
||||
const {
|
||||
id, isKey, textId, name, position, visible,
|
||||
sortable, locked, ...cleanCol
|
||||
} = col;
|
||||
return cleanCol;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.cleanProps = this.cleanProps.bind(this);
|
||||
this.displayHeader = this.displayHeader.bind(this);
|
||||
}
|
||||
|
||||
cleanProps() {
|
||||
// Remove additional props from the props value
|
||||
const {columns, ...cleanProps} = this.props;
|
||||
return cleanProps;
|
||||
}
|
||||
|
||||
|
||||
displayHeader() {
|
||||
const orderedCols = this.props.columns.sort((a, b) => a.position - b.position);
|
||||
return orderedCols.map((col) =>
|
||||
<TableHeaderColumn
|
||||
key={col.id}
|
||||
dataField={col.textId}
|
||||
isKey={col.isKey}
|
||||
hidden={('visible' in col) && !col.visible}
|
||||
dataSort={col.sortable}
|
||||
{...DataTable.cleanColumnAttributes(col)}
|
||||
>
|
||||
{col.name}
|
||||
</TableHeaderColumn>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyledBootstrapTable {...this.cleanProps()}>
|
||||
{this.displayHeader()}
|
||||
</StyledBootstrapTable>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DataTable.propTypes = {
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
isKey: PropTypes.bool.isRequired,
|
||||
textId: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
position: PropTypes.number.isRequired,
|
||||
visible: PropTypes.bool,
|
||||
sortable: PropTypes.bool,
|
||||
locked: PropTypes.bool
|
||||
})
|
||||
).isRequired,
|
||||
data: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
export default DataTable;
|
|
@ -1,11 +1,71 @@
|
|||
import { SET_CURRENT_USER } from "../../app/action_types";
|
||||
import {
|
||||
SET_CURRENT_USER,
|
||||
CHANGE_CURRENT_USER_FULL_NAME,
|
||||
CHANGE_CURRENT_USER_INITIALS,
|
||||
CHANGE_CURRENT_USER_EMAIL,
|
||||
CHANGE_CURRENT_USER_PASSWORD,
|
||||
CHANGE_CURRENT_USER_AVATAR,
|
||||
CHANGE_CURRENT_USER_TIMEZONE,
|
||||
CHANGE_ASSIGNMENTS_NOTIFICATION,
|
||||
CHANGE_ASSIGNMENTS_NOTIFICATION_EMAIL,
|
||||
CHANGE_RECENT_NOTIFICATION,
|
||||
CHANGE_RECENT_NOTIFICATION_EMAIL,
|
||||
CHANGE_SYSTEM_MESSAGE_NOTIFICATION_EMAIL
|
||||
} from "../../app/action_types";
|
||||
|
||||
export function currentUser(
|
||||
state = { id: 0, fullName: "", avatarPath: "" },
|
||||
state = {
|
||||
id: 0,
|
||||
fullName: "",
|
||||
initials: "",
|
||||
email: "",
|
||||
avatarPath: "",
|
||||
avatarThumbPath: "",
|
||||
timezone: "",
|
||||
assignmentsNotification: false,
|
||||
assignmentsNotificationEmail: false,
|
||||
recentNotification: false,
|
||||
recentNotificationEmail: false,
|
||||
systemMessageNotificationEmail: false
|
||||
},
|
||||
action
|
||||
) {
|
||||
if (action.type === SET_CURRENT_USER) {
|
||||
return Object.assign({}, state, action.payload);
|
||||
switch (action.type) {
|
||||
case SET_CURRENT_USER:
|
||||
return Object.assign({}, state, action.payload);
|
||||
case CHANGE_CURRENT_USER_FULL_NAME:
|
||||
return Object.assign({}, state, { fullName: action.payload });
|
||||
case CHANGE_CURRENT_USER_INITIALS:
|
||||
return Object.assign({}, state, { initials: action.payload });
|
||||
case CHANGE_CURRENT_USER_EMAIL:
|
||||
return Object.assign({}, state, { email: action.payload });
|
||||
case CHANGE_CURRENT_USER_PASSWORD:
|
||||
console.log("handle sending password to the server");
|
||||
// return Object.assign({}, state, { password: action.payload });
|
||||
return state;
|
||||
case CHANGE_CURRENT_USER_AVATAR:
|
||||
return Object.assign({}, state, { avatar: action.payload });
|
||||
case CHANGE_CURRENT_USER_TIMEZONE:
|
||||
return Object.assign({}, state, { timezone: action.payload });
|
||||
case CHANGE_ASSIGNMENTS_NOTIFICATION:
|
||||
return Object.assign({}, state, {
|
||||
assignmentsNotification: action.payload
|
||||
});
|
||||
case CHANGE_ASSIGNMENTS_NOTIFICATION_EMAIL:
|
||||
return Object.assign({}, state, {
|
||||
assignmentsNotificationEmail: action.payload
|
||||
});
|
||||
case CHANGE_RECENT_NOTIFICATION:
|
||||
return Object.assign({}, state, { recentNotification: action.payload });
|
||||
case CHANGE_RECENT_NOTIFICATION_EMAIL:
|
||||
return Object.assign({}, state, {
|
||||
recentNotificationEmail: action.payload
|
||||
});
|
||||
case CHANGE_SYSTEM_MESSAGE_NOTIFICATION_EMAIL:
|
||||
return Object.assign({}, state, {
|
||||
systemMessageNotificationEmail: action.payload
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { IntlProvider } from "react-intl";
|
||||
import { addLocaleData } from "react-intl";
|
||||
import { IntlProvider, addLocaleData } from "react-intl";
|
||||
import enLocaleData from "react-intl/locale-data/en";
|
||||
|
||||
import { flattenMessages } from "../../locales/utils";
|
||||
import store from "../../app/store";
|
||||
import messages from "../../locales/messages";
|
||||
|
||||
import Navigation from "../../shared/navigation";
|
||||
import MainNav from "./components/MainNav";
|
||||
|
||||
addLocaleData([...enLocaleData]);
|
||||
const locale = "en-US";
|
||||
|
||||
const SettingsPage = () =>
|
||||
<div>
|
||||
<Navigation page="Settings" />
|
||||
....
|
||||
<MainNav />
|
||||
</div>;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
@ -26,7 +26,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
locale={locale}
|
||||
messages={flattenMessages(messages[locale])}
|
||||
>
|
||||
<SettingsPage />
|
||||
<div>
|
||||
<BrowserRouter>
|
||||
<SettingsPage />
|
||||
</BrowserRouter>
|
||||
</div>
|
||||
</IntlProvider>
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
|
|
67
app/javascript/packs/src/settings/components/MainNav.jsx
Normal file
67
app/javascript/packs/src/settings/components/MainNav.jsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import React, { Component } from "react";
|
||||
import { Route, Switch, Redirect } from "react-router-dom";
|
||||
import { LinkContainer } from "react-router-bootstrap";
|
||||
import { Nav, NavItem } from "react-bootstrap";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Navigation from "../../../shared/navigation";
|
||||
|
||||
import { SETTINGS_ACCOUNT_PROFILE, SETTINGS_TEAMS } from "../../../app/routes";
|
||||
|
||||
import NotFound from "../../../shared/404/NotFound";
|
||||
import SettingsAccount from ".././components/account/SettingsAccount";
|
||||
import SettingsTeams from ".././components/team/SettingsTeams";
|
||||
|
||||
export default class MainNav extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
active: "1"
|
||||
};
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
}
|
||||
|
||||
handleSelect(eventKey) {
|
||||
event.preventDefault();
|
||||
this.setState({ active: eventKey });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Navigation page="Settings" />
|
||||
<div className="container">
|
||||
<Nav bsStyle="tabs" activeKey="1" onSelect={this.handleSelect}>
|
||||
<LinkContainer
|
||||
active={this.state.active === "1"}
|
||||
to={SETTINGS_ACCOUNT_PROFILE}
|
||||
>
|
||||
<NavItem eventKey="1">
|
||||
<FormattedMessage id="settings_page.account" />
|
||||
</NavItem>
|
||||
</LinkContainer>
|
||||
<LinkContainer
|
||||
to={SETTINGS_TEAMS}
|
||||
active={this.state.active === "2"}
|
||||
>
|
||||
<NavItem eventKey="2">
|
||||
<FormattedMessage id="settings_page.team" />
|
||||
</NavItem>
|
||||
</LinkContainer>
|
||||
</Nav>
|
||||
<Switch>
|
||||
<Route exact path="/" component={SettingsAccount} />
|
||||
<Route
|
||||
exact
|
||||
path="/settings"
|
||||
render={() => <Redirect to="/settings/account/profile" />}
|
||||
/>
|
||||
<Route path="/settings/account" component={SettingsAccount} />
|
||||
<Route path="/settings/teams" component={SettingsTeams} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import React from "react";
|
||||
import { string, func } from "prop-types";
|
||||
import { FormGroup, FormControl, ControlLabel, Button } from "react-bootstrap";
|
||||
import styled from "styled-components";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const Wrapper = styled.div`margin-top: 19px;`;
|
||||
|
||||
const MyFormControl = styled(FormControl)`
|
||||
width: 160px;
|
||||
display: inline;
|
||||
`;
|
||||
|
||||
const MyButton = styled(Button)`
|
||||
border-radius: 0 50px 50px 0;
|
||||
margin-top: -3px;
|
||||
margin-left: -3px;
|
||||
`;
|
||||
|
||||
const InputDisabled = props =>
|
||||
<Wrapper>
|
||||
<form>
|
||||
<FormGroup>
|
||||
<ControlLabel>
|
||||
<FormattedMessage id={props.labelTitle} />
|
||||
</ControlLabel>
|
||||
<FormGroup>
|
||||
<MyFormControl
|
||||
type={props.inputType}
|
||||
value={props.inputValue}
|
||||
disabled
|
||||
/>
|
||||
<MyButton bsStyle="default" onClick={props.enableEdit}>
|
||||
<FormattedMessage id="general.edit" />
|
||||
</MyButton>
|
||||
</FormGroup>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Wrapper>;
|
||||
|
||||
InputDisabled.propTypes = {
|
||||
labelTitle: string.isRequired,
|
||||
inputType: string.isRequired,
|
||||
inputValue: string.isRequired,
|
||||
enableEdit: func.isRequired
|
||||
};
|
||||
|
||||
export default InputDisabled;
|
|
@ -0,0 +1,152 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { FormGroup, FormControl, ControlLabel, Button } from "react-bootstrap";
|
||||
|
||||
import { BORDER_LIGHT_COLOR } from "../../../../app/constants/colors";
|
||||
|
||||
const StyledInputEnabled = styled.div`
|
||||
border: 1px solid ${BORDER_LIGHT_COLOR};
|
||||
padding: 19px;
|
||||
margin: 20px 0;
|
||||
|
||||
input {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ErrorMsg = styled.div`color: red;`;
|
||||
|
||||
class InputEnabled extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (props.inputType === "password") {
|
||||
this.state = {
|
||||
value: "",
|
||||
value2: ""
|
||||
};
|
||||
} else {
|
||||
this.state = {
|
||||
value: this.props.inputValue
|
||||
};
|
||||
}
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleChange2 = this.handleChange2.bind(this);
|
||||
this.handleUpdate = this.handleUpdate.bind(this);
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({ value: event.target.value });
|
||||
}
|
||||
|
||||
handleChange2(event) {
|
||||
this.setState({ value2: event.target.value });
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
handleUpdate() {
|
||||
this.props.saveData(this.state.value);
|
||||
this.props.disableEdit();
|
||||
}
|
||||
|
||||
confirmationField() {
|
||||
let inputs;
|
||||
const type = this.props.inputType;
|
||||
|
||||
if (type === "email" || type === "password") {
|
||||
inputs = (
|
||||
<div>
|
||||
<p>
|
||||
Current password (we need your current password to confirm your
|
||||
changes)
|
||||
</p>
|
||||
<FormControl type="password" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
errorMsg() {
|
||||
return this.state.value !== this.state.value2
|
||||
? <ErrorMsg>Passwords do not match!</ErrorMsg>
|
||||
: "";
|
||||
}
|
||||
|
||||
inputField() {
|
||||
let input;
|
||||
|
||||
if (this.props.inputType === "password") {
|
||||
input = (
|
||||
<div>
|
||||
<FormControl
|
||||
type={this.props.inputType}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<p>New password confirmation</p>
|
||||
<FormControl
|
||||
type={this.props.inputType}
|
||||
value={this.state.value2}
|
||||
onChange={this.handleChange2}
|
||||
/>
|
||||
{this.errorMsg()}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
input = (
|
||||
<FormControl
|
||||
type={this.props.inputType}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyledInputEnabled>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<FormGroup>
|
||||
<h4>
|
||||
<FormattedMessage id="settings_page.change" />{" "}
|
||||
<FormattedMessage id={this.props.labelTitle} />
|
||||
</h4>
|
||||
{this.confirmationField()}
|
||||
<ControlLabel>
|
||||
{this.props.labelValue}
|
||||
</ControlLabel>
|
||||
{this.inputField()}
|
||||
<Button bsStyle="primary" onClick={this.props.disableEdit}>
|
||||
<FormattedMessage id="general.cancel" />
|
||||
</Button>
|
||||
<Button bsStyle="default" onClick={this.handleUpdate}>
|
||||
<FormattedMessage id="general.update" />
|
||||
</Button>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</StyledInputEnabled>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InputEnabled.propTypes = {
|
||||
inputType: PropTypes.string.isRequired,
|
||||
labelValue: PropTypes.string.isRequired,
|
||||
inputValue: PropTypes.string.isRequired,
|
||||
disableEdit: PropTypes.func.isRequired,
|
||||
saveData: PropTypes.func.isRequired,
|
||||
labelTitle: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default InputEnabled;
|
|
@ -0,0 +1,39 @@
|
|||
import React, { Component } from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
|
||||
import SettingsLeftTab from "./SettingsLeftTab";
|
||||
import SettingsProfile from "./profile/SettingsProfile";
|
||||
import SettingsPreferences from "./preferences/SettingsPreferences";
|
||||
|
||||
import { BORDER_LIGHT_COLOR } from "../../../../app/constants/colors";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background: white;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid ${BORDER_LIGHT_COLOR};
|
||||
border-top: none;
|
||||
margin: 0;
|
||||
padding: 16px 0 50px 0;
|
||||
`;
|
||||
|
||||
class SettingsAccount extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Wrapper className="row">
|
||||
<div className="col-xs-12 col-sm-3">
|
||||
<SettingsLeftTab />
|
||||
</div>
|
||||
<Switch>
|
||||
<Route path="/settings/account/profile" component={SettingsProfile} />
|
||||
<Route
|
||||
path="/settings/account/preferences"
|
||||
component={SettingsPreferences}
|
||||
/>
|
||||
</Switch>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsAccount;
|
|
@ -0,0 +1,61 @@
|
|||
import React, { Component } from "react";
|
||||
import { Nav, NavItem } from "react-bootstrap";
|
||||
import { LinkContainer } from "react-router-bootstrap";
|
||||
import styled from "styled-components";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import {
|
||||
SETTINGS_ACCOUNT_PROFILE,
|
||||
SETTINGS_ACCOUNT_PREFERENCES
|
||||
} from "../../../../app/routes";
|
||||
|
||||
import {
|
||||
SIDEBAR_HOVER_GRAY_COLOR,
|
||||
LIGHT_BLUE_COLOR
|
||||
} from "../../../../app/constants/colors";
|
||||
|
||||
const MyLinkContainer = styled(LinkContainer)`
|
||||
a {
|
||||
color: ${LIGHT_BLUE_COLOR};
|
||||
padding-left: 0;
|
||||
}
|
||||
&.active > a:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
margin-top: -19px;
|
||||
border-top: 19px solid transparent;
|
||||
border-left: 13px solid ${LIGHT_BLUE_COLOR};
|
||||
border-bottom: 19px solid transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: ${SIDEBAR_HOVER_GRAY_COLOR} !important;
|
||||
}
|
||||
|
||||
&.active {
|
||||
a {
|
||||
background-color: ${LIGHT_BLUE_COLOR} !important;
|
||||
border-radius: 3px 0 0 3px;
|
||||
border-left: 13px solid ${LIGHT_BLUE_COLOR};
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const SettingsLeftTab = () =>
|
||||
<Nav bsStyle="pills" stacked activeKey={1}>
|
||||
<MyLinkContainer to={SETTINGS_ACCOUNT_PROFILE}>
|
||||
<NavItem>
|
||||
<FormattedMessage id="settings_page.profile" />
|
||||
</NavItem>
|
||||
</MyLinkContainer>
|
||||
<MyLinkContainer to={SETTINGS_ACCOUNT_PREFERENCES}>
|
||||
<NavItem>
|
||||
<FormattedMessage id="settings_page.preferences" />
|
||||
</NavItem>
|
||||
</MyLinkContainer>
|
||||
</Nav>;
|
||||
|
||||
export default SettingsLeftTab;
|
|
@ -0,0 +1,3 @@
|
|||
export const ASSIGNMENT_NOTIFICATION = "ASSIGNMENT";
|
||||
export const RECENT_NOTIFICATION = "RECENT_NOTIFICATION";
|
||||
export const SYSTEM_NOTIFICATION = "SYSTEM_NOTIFICATION";
|
|
@ -0,0 +1,83 @@
|
|||
import React, { Component } from "react";
|
||||
import PropType 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 { BORDER_LIGHT_COLOR } from "../../../../../app/constants/colors";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
border: 1px solid ${BORDER_LIGHT_COLOR};
|
||||
padding: 19px;
|
||||
margin: 20px 0;
|
||||
|
||||
input {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.settings-warning {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
`;
|
||||
|
||||
class InputTimezone extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: props.inputValue
|
||||
};
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleUpdate = this.handleUpdate.bind(this);
|
||||
}
|
||||
|
||||
handleChange(timezone) {
|
||||
this.setState({ value: timezone });
|
||||
}
|
||||
|
||||
handleUpdate() {
|
||||
if (this.state.value !== "") {
|
||||
this.props.saveData(this.state.value);
|
||||
}
|
||||
this.props.disableEdit();
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Wrapper>
|
||||
<h4>
|
||||
{this.props.labelValue}
|
||||
</h4>
|
||||
<TimezonePicker
|
||||
absolute
|
||||
defaultValue="Europe/London"
|
||||
value={this.props.inputValue}
|
||||
placeholder="Select timezone..."
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="settings-warning">
|
||||
<small>
|
||||
<FormattedMessage id="settings_page.time_zone_warning" />
|
||||
</small>
|
||||
</div>
|
||||
<Button bsStyle="primary" onClick={this.props.disableEdit}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button bsStyle="default" onClick={this.handleUpdate}>
|
||||
Update
|
||||
</Button>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InputTimezone.propTypes = {
|
||||
labelValue: PropType.string.isRequired,
|
||||
inputValue: PropType.string.isRequired,
|
||||
disableEdit: PropType.func.isRequired,
|
||||
saveData: PropType.func.isRequired
|
||||
};
|
||||
|
||||
export default InputTimezone;
|
|
@ -0,0 +1,92 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import NotificationsSwitchGroup from "./NotificationsSwitchGroup";
|
||||
import { WHITE_COLOR } from "../../../../../app/constants/colors";
|
||||
|
||||
const Wrapper = styled.div`margin-bottom: 6px;`;
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
margin-top: 12px;
|
||||
margin-left: 7px;
|
||||
`;
|
||||
|
||||
const Icon = styled.span`
|
||||
border-radius: 50%;
|
||||
color: ${WHITE_COLOR};
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
height: 30px;
|
||||
margin-right: 15px;
|
||||
padding: 7px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
width: 30px;
|
||||
`;
|
||||
|
||||
const Image = styled.span`
|
||||
border-radius: 50%;
|
||||
color: ${WHITE_COLOR};
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
height: 30px;
|
||||
margin-right: 15px;
|
||||
width: 30px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
class NotificationsGroup extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let imgOrIcon;
|
||||
|
||||
if (this.props.imgUrl === "") {
|
||||
imgOrIcon = (
|
||||
<Icon style={{ backgroundColor: this.props.iconBackground }}>
|
||||
<i className={this.props.iconClasses} />
|
||||
</Icon>
|
||||
);
|
||||
} else {
|
||||
imgOrIcon = (
|
||||
<Image>
|
||||
<img src={this.props.imgUrl} alt="default avatar" />
|
||||
</Image>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper className="row">
|
||||
<IconWrapper className="col-sm-1">
|
||||
{imgOrIcon}
|
||||
</IconWrapper>
|
||||
<div className="col-sm-10">
|
||||
<h5>
|
||||
<strong>
|
||||
<FormattedMessage id={this.props.title} />
|
||||
</strong>
|
||||
</h5>
|
||||
<p>
|
||||
<FormattedMessage id={this.props.subtitle} />
|
||||
</p>
|
||||
<NotificationsSwitchGroup type={this.props.type} />
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NotificationsGroup.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
imgUrl: PropTypes.string.isRequired,
|
||||
iconClasses: PropTypes.string.isRequired,
|
||||
iconBackground: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default NotificationsGroup;
|
|
@ -0,0 +1,103 @@
|
|||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import {
|
||||
WHITE_COLOR,
|
||||
NOTIFICATION_YES,
|
||||
NOTIFICATION_YES_BORDER
|
||||
} from "../../../../../app/constants/colors";
|
||||
|
||||
const Wrapper = styled.div`margin-top: 13px;`;
|
||||
|
||||
const LeftButton = styled.button`border-radius: 50px 0 0 50px;`;
|
||||
const RightButton = styled.button`
|
||||
border-radius: 0 50px 50px 0;
|
||||
&.btn-primary {
|
||||
color: ${WHITE_COLOR};
|
||||
border-color: ${NOTIFICATION_YES_BORDER};
|
||||
background-color: ${NOTIFICATION_YES};
|
||||
&:hover {
|
||||
background-color: ${NOTIFICATION_YES_BORDER};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class NotificationsSwitch extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
if (!this.props.isDisabled) {
|
||||
this.props.toggleSwitch();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let switchBtn;
|
||||
|
||||
if (this.props.isSwitchOn) {
|
||||
switchBtn = (
|
||||
<div className="btn-group">
|
||||
<LeftButton
|
||||
className="btn btn-default"
|
||||
disabled={this.props.isDisabled}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<FormattedMessage id="settings_page.no" />
|
||||
</LeftButton>
|
||||
<RightButton
|
||||
className="btn btn-primary"
|
||||
disabled
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<FormattedMessage id="settings_page.yes" />
|
||||
</RightButton>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
switchBtn = (
|
||||
<div className="btn-group">
|
||||
<LeftButton
|
||||
className="btn btn-danger"
|
||||
disabled
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<FormattedMessage id="settings_page.no" />
|
||||
</LeftButton>
|
||||
<RightButton
|
||||
className="btn btn-default"
|
||||
disabled={this.props.isDisabled}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<FormattedMessage id="settings_page.yes" />
|
||||
</RightButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper className="row">
|
||||
<div className="col-sm-4 col-sm-offset-1">
|
||||
<FormattedMessage id={this.props.title} />
|
||||
</div>
|
||||
<div className="col-sm-7">
|
||||
{switchBtn}
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NotificationsSwitch.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
isSwitchOn: PropTypes.bool.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
toggleSwitch: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default NotificationsSwitch;
|
|
@ -0,0 +1,157 @@
|
|||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { string, bool, func } from "prop-types";
|
||||
|
||||
import NotificationsSwitch from "./NotificationsSwitch";
|
||||
|
||||
import {
|
||||
ASSIGNMENT_NOTIFICATION,
|
||||
RECENT_NOTIFICATION,
|
||||
SYSTEM_NOTIFICATION
|
||||
} from "../constants";
|
||||
|
||||
import {
|
||||
changeAssignmentsNotification,
|
||||
changeAssignmentsNotificationEmail,
|
||||
changeRecentNotification,
|
||||
changeRecentNotificationEmail,
|
||||
changeSystemMessageNotificationEmail
|
||||
} from "../../../../../shared/actions/UsersActions";
|
||||
|
||||
class NotificationsSwitchGroup extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isSciNoteSwitchOn: false,
|
||||
isEmailSwitchOn: false
|
||||
};
|
||||
|
||||
this.toggleFirstSwitch = this.toggleFirstSwitch.bind(this);
|
||||
this.toggleSecondSwitch = this.toggleSecondSwitch.bind(this);
|
||||
this.isSwitchDisabled = this.isSwitchDisabled.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
switch (this.props.type) {
|
||||
case ASSIGNMENT_NOTIFICATION:
|
||||
this.setState({
|
||||
isSciNoteSwitchOn: this.props.assignmentsNotification,
|
||||
isEmailSwitchOn: this.props.assignmentsNotificationEmail,
|
||||
sciNoteDispatch: state =>
|
||||
this.props.changeAssignmentsNotification(state),
|
||||
emailDispatch: state =>
|
||||
this.props.changeAssignmentsNotificationEmail(state)
|
||||
});
|
||||
break;
|
||||
case RECENT_NOTIFICATION:
|
||||
this.setState({
|
||||
isSciNoteSwitchOn: this.props.recentNotification,
|
||||
isEmailSwitchOn: this.props.recentNotificationEmail,
|
||||
sciNoteDispatch: state => this.props.changeRecentNotification(state),
|
||||
emailDispatch: state =>
|
||||
this.props.changeRecentNotificationEmail(state)
|
||||
});
|
||||
break;
|
||||
case SYSTEM_NOTIFICATION:
|
||||
this.setState({
|
||||
isSciNoteSwitchOn: true,
|
||||
isEmailSwitchOn: this.props.systemMessageNotificationEmail,
|
||||
sciNoteDispatch: state => `${state}: Do Nothing`,
|
||||
emailDispatch: state =>
|
||||
this.props.changeSystemMessageNotificationEmail(state)
|
||||
});
|
||||
break;
|
||||
default:
|
||||
this.setState({
|
||||
isSciNoteSwitchOn: true,
|
||||
isEmailSwitchOn: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toggleFirstSwitch() {
|
||||
if (this.state.isSciNoteSwitchOn) {
|
||||
this.setState({ isSciNoteSwitchOn: false, isEmailSwitchOn: false });
|
||||
this.state.sciNoteDispatch(false);
|
||||
this.state.emailDispatch(false);
|
||||
} else {
|
||||
this.setState({ isSciNoteSwitchOn: true });
|
||||
this.state.sciNoteDispatch(true);
|
||||
}
|
||||
}
|
||||
|
||||
toggleSecondSwitch() {
|
||||
if (this.state.isEmailSwitchOn) {
|
||||
this.setState({ isEmailSwitchOn: false });
|
||||
this.state.emailDispatch(false);
|
||||
} else {
|
||||
this.setState({ isEmailSwitchOn: true });
|
||||
this.state.emailDispatch(true);
|
||||
}
|
||||
}
|
||||
|
||||
isSwitchDisabled() {
|
||||
if (this.props.type === SYSTEM_NOTIFICATION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<NotificationsSwitch
|
||||
title="settings_page.show_in_scinote"
|
||||
isSwitchOn={this.state.isSciNoteSwitchOn}
|
||||
toggleSwitch={this.toggleFirstSwitch}
|
||||
isDisabled={this.isSwitchDisabled()}
|
||||
/>
|
||||
<NotificationsSwitch
|
||||
title="settings_page.notify_me_via_email"
|
||||
isSwitchOn={this.state.isEmailSwitchOn}
|
||||
toggleSwitch={this.toggleSecondSwitch}
|
||||
isDisabled={!this.state.isSciNoteSwitchOn}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NotificationsSwitchGroup.propTypes = {
|
||||
type: string.isRequired,
|
||||
assignmentsNotification: bool.isRequired,
|
||||
assignmentsNotificationEmail: bool.isRequired,
|
||||
recentNotification: bool.isRequired,
|
||||
recentNotificationEmail: bool.isRequired,
|
||||
systemMessageNotificationEmail: bool.isRequired,
|
||||
changeAssignmentsNotification: func.isRequired,
|
||||
changeAssignmentsNotificationEmail: func.isRequired,
|
||||
changeRecentNotification: func.isRequired,
|
||||
changeRecentNotificationEmail: func.isRequired,
|
||||
changeSystemMessageNotificationEmail: func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => state.current_user;
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
changeAssignmentsNotification(status) {
|
||||
dispatch(changeAssignmentsNotification(status));
|
||||
},
|
||||
changeAssignmentsNotificationEmail(status) {
|
||||
dispatch(changeAssignmentsNotificationEmail(status));
|
||||
},
|
||||
changeRecentNotification(status) {
|
||||
dispatch(changeRecentNotification(status));
|
||||
},
|
||||
changeRecentNotificationEmail(status) {
|
||||
dispatch(changeRecentNotificationEmail(status));
|
||||
},
|
||||
changeSystemMessageNotificationEmail(status) {
|
||||
dispatch(changeSystemMessageNotificationEmail(status));
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
||||
NotificationsSwitchGroup
|
||||
);
|
|
@ -0,0 +1,128 @@
|
|||
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 InputDisabled from "../InputDisabled";
|
||||
import InputTimezone from "./InputTimezone";
|
||||
import { changeTimezone } from "../../../../../shared/actions/UsersActions";
|
||||
import NotificationsGroup from "./NotificationsGroup";
|
||||
|
||||
import {
|
||||
ASSIGNMENT_NOTIFICATION,
|
||||
RECENT_NOTIFICATION,
|
||||
SYSTEM_NOTIFICATION
|
||||
} from "../constants";
|
||||
|
||||
import {
|
||||
MAIN_COLOR_BLUE,
|
||||
ICON_GREEN_COLOR,
|
||||
BORDER_LIGHT_COLOR
|
||||
} from "../../../../../app/constants/colors";
|
||||
|
||||
const WrapperInputDisabled = 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
|
||||
};
|
||||
}
|
||||
|
||||
toggleIsEditable(fieldNameEnabled) {
|
||||
const editableState = this.state[fieldNameEnabled];
|
||||
this.setState({ [fieldNameEnabled]: !editableState });
|
||||
}
|
||||
|
||||
render() {
|
||||
const isTimeZoneEditable = "isTimeZoneEditable";
|
||||
let timezoneField;
|
||||
|
||||
if (this.state.isTimeZoneEditable) {
|
||||
timezoneField = (
|
||||
<InputTimezone
|
||||
labelValue="Time zone"
|
||||
inputValue={this.props.timezone}
|
||||
disableEdit={() => this.toggleIsEditable(isTimeZoneEditable)}
|
||||
saveData={timeZone => this.props.changeTimezone(timeZone)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
timezoneField = (
|
||||
<WrapperInputDisabled>
|
||||
<InputDisabled
|
||||
labelTitle="settings_page.time_zone"
|
||||
inputValue={this.props.timezone}
|
||||
inputType="text"
|
||||
enableEdit={() => this.toggleIsEditable(isTimeZoneEditable)}
|
||||
/>
|
||||
<div className="settings-warning">
|
||||
<small>
|
||||
<FormattedMessage id="settings_page.time_zone_warning" />
|
||||
</small>
|
||||
</div>
|
||||
</WrapperInputDisabled>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-xs-12 col-sm-9">
|
||||
{timezoneField}
|
||||
|
||||
<h3>Notifications</h3>
|
||||
<NotificationsGroup
|
||||
type={ASSIGNMENT_NOTIFICATION}
|
||||
title="settings_page.assignement"
|
||||
subtitle="settings_page.assignement_msg"
|
||||
imgUrl=""
|
||||
iconClasses="fa fa-newspaper-o"
|
||||
iconBackground={MAIN_COLOR_BLUE}
|
||||
/>
|
||||
<NotificationsGroup
|
||||
type={RECENT_NOTIFICATION}
|
||||
title="settings_page.recent_changes"
|
||||
subtitle="settings_page.recent_changes_msg"
|
||||
imgUrl={this.props.avatarPath}
|
||||
iconClasses=""
|
||||
iconBackground=""
|
||||
/>
|
||||
<NotificationsGroup
|
||||
type={SYSTEM_NOTIFICATION}
|
||||
title="settings_page.system_message"
|
||||
subtitle="settings_page.system_message_msg"
|
||||
imgUrl=""
|
||||
iconClasses="glyphicon glyphicon-tower"
|
||||
iconBackground={ICON_GREEN_COLOR}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPreferences.propTypes = {
|
||||
timezone: PropTypes.string.isRequired,
|
||||
changeTimezone: PropTypes.func.isRequired,
|
||||
avatarPath: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => state.current_user;
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
changeTimezone(timezone) {
|
||||
dispatch(changeTimezone(timezone));
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
||||
SettingsPreferences
|
||||
);
|
|
@ -0,0 +1,42 @@
|
|||
import React from "react";
|
||||
import PropTypes, { string } from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import {
|
||||
WHITE_COLOR,
|
||||
DARK_GRAY_COLOR
|
||||
} from "../../../../../app/constants/colors";
|
||||
|
||||
const AvatarWrapper = styled.div`
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
`;
|
||||
const EditAvatar = styled.span`
|
||||
color: ${WHITE_COLOR};
|
||||
background-color: ${DARK_GRAY_COLOR};
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
opacity: 0.7;
|
||||
padding: 5px;
|
||||
`;
|
||||
|
||||
const Avatar = props =>
|
||||
<AvatarWrapper onClick={props.enableEdit}>
|
||||
<img src={props.imgSource} alt="default avatar" />
|
||||
<EditAvatar className="text-center">
|
||||
<span className="glyphicon glyphicon-pencil" />
|
||||
<FormattedMessage id="settings_page.edit_avatar" />
|
||||
</EditAvatar>
|
||||
</AvatarWrapper>;
|
||||
|
||||
Avatar.propTypes = {
|
||||
imgSource: string.isRequired,
|
||||
enableEdit: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Avatar;
|
|
@ -0,0 +1,214 @@
|
|||
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 Avatar from "./Avatar";
|
||||
import InputDisabled from "../InputDisabled";
|
||||
import InputEnabled from "../InputEnabled";
|
||||
|
||||
import {
|
||||
changeFullName,
|
||||
changeInitials,
|
||||
changeEmail,
|
||||
changePassword,
|
||||
changeAvatar
|
||||
} from "../../../../../shared/actions/UsersActions";
|
||||
|
||||
const AvatarLabel = styled.h4`
|
||||
margin-top: 15px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
class MyProfile extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isFullNameEditable: false,
|
||||
areInitialsEditable: false,
|
||||
isEmailEditable: false,
|
||||
isPasswordEditable: false,
|
||||
isAvatarEditable: false
|
||||
};
|
||||
|
||||
this.toggleIsEditable = this.toggleIsEditable.bind(this);
|
||||
}
|
||||
|
||||
toggleIsEditable(fieldNameEnabled) {
|
||||
const editableState = this.state[fieldNameEnabled];
|
||||
this.setState({ [fieldNameEnabled]: !editableState });
|
||||
}
|
||||
|
||||
render() {
|
||||
const areInitialsEditable = "areInitialsEditable";
|
||||
const isFullNameEditable = "isFullNameEditable";
|
||||
const isEmailEditable = "isEmailEditable";
|
||||
const isPasswordEditable = "isPasswordEditable";
|
||||
const isAvatarEditable = "isAvatarEditable";
|
||||
let fullNameField;
|
||||
let initialsField;
|
||||
let emailField;
|
||||
let passwordField;
|
||||
let avatarField;
|
||||
|
||||
if (this.state.isAvatarEditable) {
|
||||
avatarField = (
|
||||
<InputEnabled
|
||||
labelTitle="settings_page.avatar"
|
||||
labelValue="Avatar"
|
||||
inputType="file"
|
||||
inputValue=""
|
||||
disableEdit={() => this.toggleIsEditable(isAvatarEditable)}
|
||||
saveData={avatarSrc => this.props.changeAvatar(avatarSrc)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
avatarField = (
|
||||
<Avatar
|
||||
imgSource={this.props.avatarThumbPath}
|
||||
enableEdit={() => this.toggleIsEditable(isAvatarEditable)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.isPasswordEditable) {
|
||||
passwordField = (
|
||||
<InputEnabled
|
||||
labelTitle="settings_page.change_password"
|
||||
labelValue="Change password"
|
||||
inputType="password"
|
||||
inputValue=""
|
||||
disableEdit={() => this.toggleIsEditable(isPasswordEditable)}
|
||||
saveData={newPassword => this.props.changePassword(newPassword)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
passwordField = (
|
||||
<InputDisabled
|
||||
labelTitle="settings_page.change_password"
|
||||
inputType="password"
|
||||
inputValue=""
|
||||
enableEdit={() => this.toggleIsEditable(isPasswordEditable)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.isEmailEditable) {
|
||||
emailField = (
|
||||
<InputEnabled
|
||||
labelTitle="settings_page.new_email"
|
||||
labelValue="New email"
|
||||
inputType="email"
|
||||
inputValue={this.props.email}
|
||||
disableEdit={() => this.toggleIsEditable(isEmailEditable)}
|
||||
saveData={newEmail => this.props.changeEmail(newEmail)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
emailField = (
|
||||
<InputDisabled
|
||||
labelTitle="settings_page.new_email"
|
||||
inputValue={this.props.email}
|
||||
inputType="email"
|
||||
enableEdit={() => this.toggleIsEditable(isEmailEditable)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.areInitialsEditable) {
|
||||
initialsField = (
|
||||
<InputEnabled
|
||||
labelTitle="settings_page.initials"
|
||||
labelValue="Initials"
|
||||
inputType="text"
|
||||
inputValue={this.props.initials}
|
||||
disableEdit={() => this.toggleIsEditable(areInitialsEditable)}
|
||||
saveData={newName => this.props.changeInitials(newName)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
initialsField = (
|
||||
<InputDisabled
|
||||
labelTitle="settings_page.initials"
|
||||
inputValue={this.props.initials}
|
||||
inputType="text"
|
||||
enableEdit={() => this.toggleIsEditable(areInitialsEditable)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.isFullNameEditable) {
|
||||
fullNameField = (
|
||||
<InputEnabled
|
||||
labelTitle="settings_page.full_name"
|
||||
labelValue="Full name"
|
||||
inputType="text"
|
||||
inputValue={this.props.fullName}
|
||||
disableEdit={() => this.toggleIsEditable(isFullNameEditable)}
|
||||
saveData={newName => this.props.changeFullName(newName)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
fullNameField = (
|
||||
<InputDisabled
|
||||
labelTitle="settings_page.full_name"
|
||||
inputValue={this.props.fullName}
|
||||
inputType="text"
|
||||
enableEdit={() => this.toggleIsEditable(isFullNameEditable)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>
|
||||
<FormattedMessage id="settings_page.my_profile" />
|
||||
</h2>
|
||||
<AvatarLabel>
|
||||
<FormattedMessage id="settings_page.avatar" />
|
||||
</AvatarLabel>
|
||||
{avatarField}
|
||||
{fullNameField}
|
||||
{initialsField}
|
||||
{emailField}
|
||||
{passwordField}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MyProfile.propTypes = {
|
||||
fullName: PropTypes.string.isRequired,
|
||||
avatarThumbPath: PropTypes.string.isRequired,
|
||||
initials: PropTypes.string.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
changeFullName: PropTypes.func.isRequired,
|
||||
changeInitials: PropTypes.func.isRequired,
|
||||
changeEmail: PropTypes.func.isRequired,
|
||||
changePassword: PropTypes.func.isRequired,
|
||||
changeAvatar: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => state.current_user;
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
changeFullName(name) {
|
||||
dispatch(changeFullName(name));
|
||||
},
|
||||
changeInitials(initials) {
|
||||
dispatch(changeInitials(initials));
|
||||
},
|
||||
changeEmail(email) {
|
||||
dispatch(changeEmail(email));
|
||||
},
|
||||
changePassword(password) {
|
||||
dispatch(changePassword(password));
|
||||
},
|
||||
changeAvatar(avatarSrc) {
|
||||
dispatch(changeAvatar(avatarSrc));
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MyProfile);
|
|
@ -0,0 +1,81 @@
|
|||
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 MyStatisticsBox from "./MyStatisticsBox";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin-left: -15px;
|
||||
width: 260px;
|
||||
`;
|
||||
|
||||
class MyStatistics extends Component {
|
||||
render() {
|
||||
const stats = this.props.statistics;
|
||||
|
||||
const statBoxes = () => {
|
||||
let boxes = (
|
||||
<div>
|
||||
<FormattedMessage id="general.loading" />
|
||||
</div>
|
||||
);
|
||||
if (stats) {
|
||||
boxes = (
|
||||
<Wrapper>
|
||||
<MyStatisticsBox
|
||||
typeLength={stats.number_of_teams}
|
||||
plural="settings_page.teams"
|
||||
singular="settings_page.team"
|
||||
/>
|
||||
<MyStatisticsBox
|
||||
typeLength={stats.number_of_projects}
|
||||
plural="settings_page.projects"
|
||||
singular="settings_page.project"
|
||||
/>
|
||||
<MyStatisticsBox
|
||||
typeLength={stats.number_of_experiments}
|
||||
plural="settings_page.experiments"
|
||||
singular="settings_page.experiment"
|
||||
/>
|
||||
<MyStatisticsBox
|
||||
typeLength={stats.number_of_protocols}
|
||||
plural="settings_page.protocols"
|
||||
singular="settings_page.protocol"
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return boxes;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>
|
||||
<FormattedMessage id="settings_page.my_statistics" />
|
||||
</h2>
|
||||
|
||||
{statBoxes()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MyStatistics.defaultProps = {
|
||||
statistics: null
|
||||
};
|
||||
|
||||
MyStatistics.propTypes = {
|
||||
statistics: PropTypes.shape({
|
||||
number_of_teams: PropTypes.number.isRequired,
|
||||
number_of_projects: PropTypes.number.isRequired,
|
||||
number_of_experiments: PropTypes.number.isRequired,
|
||||
number_of_protocols: PropTypes.number.isRequired
|
||||
})
|
||||
};
|
||||
|
||||
const mapStateToProps = state => state.current_user;
|
||||
|
||||
export default connect(mapStateToProps, {})(MyStatistics);
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import PropTypes from "prop-types";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { MAIN_COLOR_BLUE } from "../../../../../app/constants/colors";
|
||||
|
||||
const Box = styled.div`
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: #fff;
|
||||
background-color: ${MAIN_COLOR_BLUE};
|
||||
display: inline-block;
|
||||
margin: 15px;
|
||||
text-align: center;
|
||||
border-radius: 0.25em;
|
||||
`;
|
||||
|
||||
const MyStatisticsBox = props =>
|
||||
<Box>
|
||||
<h2>
|
||||
{props.typeLength}
|
||||
</h2>
|
||||
<h5>
|
||||
{props.typeLength === 1
|
||||
? <FormattedMessage id={props.singular} />
|
||||
: <FormattedMessage id={props.plural} />}
|
||||
</h5>
|
||||
</Box>;
|
||||
|
||||
MyStatisticsBox.propTypes = {
|
||||
typeLength: PropTypes.number.isRequired,
|
||||
plural: PropTypes.string.isRequired,
|
||||
singular: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default MyStatisticsBox;
|
|
@ -0,0 +1,16 @@
|
|||
import React from "react";
|
||||
|
||||
import MyProfile from "./MyProfile";
|
||||
import MyStatistics from "./MyStatistics";
|
||||
|
||||
const SettingsProfile = () =>
|
||||
<div>
|
||||
<div className="col-xs-12 col-sm-4">
|
||||
<MyProfile />
|
||||
</div>
|
||||
<div className="col-xs-12 col-sm-5">
|
||||
<MyStatistics />
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
export default SettingsProfile;
|
|
@ -0,0 +1,20 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { BORDER_LIGHT_COLOR } from "../../../../app/constants/colors";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background: white;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid ${BORDER_LIGHT_COLOR};
|
||||
border-top: none;
|
||||
margin: 0;
|
||||
padding: 16px 0 50px 0;
|
||||
`;
|
||||
|
||||
const SettingsTeams = () =>
|
||||
<Wrapper>
|
||||
<h1 className="text-center">Settings Teams</h1>
|
||||
</Wrapper>;
|
||||
|
||||
export default SettingsTeams;
|
|
@ -35,6 +35,7 @@ $color-candlelight: #ffda23;
|
|||
$color-orange: #ff900b;
|
||||
$color-saturated-green: #008600;
|
||||
$color-blue-yadcf: #337ab7;
|
||||
$primary-hover-color: #75b22b;
|
||||
|
||||
// Red colors
|
||||
$color-mojo: #cf4b48;
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
@import 'constants';
|
||||
@import 'react-bootstrap-timezone-picker/dist/react-bootstrap-timezone-picker.min.css';
|
||||
@import '~react-bootstrap-table/dist/react-bootstrap-table.min';
|
||||
|
||||
body {
|
||||
background-color: $color-concrete;
|
||||
color: $color-emperor;
|
||||
font-family: "Open Sans",Arial,Helvetica,sans-serif;
|
||||
font-family: "Open Sans", Arial, Helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.label-primary {
|
||||
background-color: $color-theme-primary;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: $color-theme-secondary;
|
||||
border-color: $primary-hover-color;
|
||||
margin-right: 7px;
|
||||
&:hover {
|
||||
background-color: $primary-hover-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
json.user do
|
||||
json.id user.id
|
||||
json.fullName user.full_name
|
||||
json.initials user.initials
|
||||
json.email user.email
|
||||
json.avatarPath avatar_path(user, :icon_small)
|
||||
json.avatarThumbPath avatar_path(user, :thumb)
|
||||
json.statistics user.statistics
|
||||
json.timezone user.time_zone
|
||||
json.assignmentsNotification user.assignments_notification
|
||||
json.assignmentsNotificationEmail user.assignments_notification_email
|
||||
json.recentNotification user.recent_notification
|
||||
json.recentNotificationEmail user.recent_notification_email
|
||||
json.systemMessageNotificationEmail user.system_message_notification_email
|
||||
end
|
||||
|
|
3
app/views/client_api/users/update.json.jbuilder
Normal file
3
app/views/client_api/users/update.json.jbuilder
Normal file
|
@ -0,0 +1,3 @@
|
|||
json.user do
|
||||
json.fullName user.full_name
|
||||
end
|
|
@ -10,6 +10,7 @@ Rails.application.routes.draw do
|
|||
|
||||
# Settings root
|
||||
get '/settings', to: 'client_api/settings#index'
|
||||
get '/settings/*all', to: 'client_api/settings#index'
|
||||
|
||||
# Client APP endpoints
|
||||
namespace :client_api, defaults: { format: 'json' } do
|
||||
|
|
9824
package-lock.json
generated
Normal file
9824
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -63,6 +63,7 @@
|
|||
"rails-erb-loader": "^5.0.2",
|
||||
"react": "^15.6.1",
|
||||
"react-bootstrap": "^0.31.1",
|
||||
"react-bootstrap-timezone-picker": "^1.0.11",
|
||||
"react-dom": "^15.6.1",
|
||||
"react-intl": "^2.3.0",
|
||||
"react-intl-redux": "^0.6.0",
|
||||
|
@ -70,6 +71,7 @@
|
|||
"react-redux": "^5.0.5",
|
||||
"react-router-bootstrap": "^0.24.2",
|
||||
"react-router-dom": "^4.1.2",
|
||||
"react-timezone": "^0.2.0",
|
||||
"redux": "^3.7.2",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"resolve-url-loader": "^2.1.0",
|
||||
|
@ -78,6 +80,8 @@
|
|||
"styled-components": "^2.1.1",
|
||||
"webpack": "^3.2.0",
|
||||
"webpack-manifest-plugin": "^1.1.2",
|
||||
"webpack-merge": "^4.1.0"
|
||||
"webpack-merge": "^4.1.0",
|
||||
"react-bootstrap-table": "^4.0.0",
|
||||
"react-data-grid": "^2.0.2"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue