Merge branch 'profile'

This commit is contained in:
SleepWalker 2016-03-21 20:28:18 +02:00
commit 7ab387fb71
8 changed files with 324 additions and 13 deletions

View File

@ -0,0 +1,83 @@
import React, { Component } from 'react';
import { FormattedMessage as Message, FormattedRelative as Relative, FormattedHTMLMessage as HTMLMessage } from 'react-intl';
import Helmet from 'react-helmet';
import { userShape } from 'components/user/User';
import ProfileField from './ProfileField';
import styles from './profile.scss';
import messages from './Profile.messages';
export class Profile extends Component {
static displayName = 'Profile';
static propTypes = {
user: userShape
};
render() {
const { user } = this.props;
return (
<div className={styles.container}>
<Message {...messages.accountPreferencesTitle}>
{(pageTitle) => (
<h2 className={styles.title}>
<Helmet title={pageTitle} />
{pageTitle}
</h2>
)}
</Message>
<div className={styles.content}>
<div className={styles.description}>
<Message {...messages.accountDescription} />
</div>
<div className={styles.options}>
<div className={styles.item}>
<h3 className={styles.optionsTitle}>
<Message {...messages.personalData} />
</h3>
<p className={styles.optionsDescription}>
<Message {...messages.preferencesDescription} />
</p>
</div>
<ProfileField
label={<Message {...messages.nickname} />}
value={user.username}
warningMessage={<Message {...messages.mojangPriorityWarning} />}
/>
<ProfileField
label={'E-mail'}
value={user.email}
/>
<ProfileField
label={<Message {...messages.password} />}
value={<Message {...messages.changedAt} values={{
at: (<Relative value={user.passwordChangedAt * 1000} />)
}} />}
warningMessage={user.shouldChangePassword ? (
<HTMLMessage {...messages.oldHashingAlgoWarning} />
) : ''}
/>
<ProfileField
label={<Message {...messages.twoFactorAuth} />}
value={<Message {...messages.disabled} />}
/>
<ProfileField
label={'UUID'}
value={user.uuid}
readonly
/>
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,59 @@
import { defineMessages } from 'react-intl';
export default defineMessages({
accountPreferencesTitle: {
id: 'accountPreferencesTitle',
defaultMessage: 'Ely.by account preferences'
// defaultMessage: 'Настройки аккаунта Ely.by'
},
personalData: {
id: 'personalData',
defaultMessage: 'Personal data'
// defaultMessage: 'Персональные данные'
},
accountDescription: {
id: 'accountDescription',
defaultMessage: 'Ely.by account allows you to get access to many Minecraft resources. Please, take care of your account safety. Use secure password and change it regularly.'
// defaultMessage: 'Благодаря аккаунту Ely.by вы можете получить доступ ко многим ресурсам, связанным с Minecraft. Берегите свой аккаунт, используйте надёжный пароль и регулярно его меняйте.'
},
preferencesDescription: {
id: 'preferencesDescription',
defaultMessage: 'Here you can change the key preferences of your account. Please note that all actions must be confirmed by entering a password.'
// defaultMessage: 'Здесь вы можете сменить ключевые параметры вашего аккаунта. Обратите внимание, что для всех действий необходимо подтверждение при помощи ввода пароля.'
},
mojangPriorityWarning: {
id: 'mojangPriorityWarning',
defaultMessage: 'A Mojang account with the same nickname was found. According to project rules, account owner has the right to demand the restoration of control over nickname.'
// defaultMessage: 'Найден аккаунт Mojang с таким же ником и, по правилам проекта, его владелец вправе потребовать восстановления контроля над ником.'
},
oldHashingAlgoWarning: {
id: 'oldHashingAlgoWarning',
defaultMessage: 'Your was hashed with an old hashing algorithm.<br />Please, change password.'
// defaultMessage: 'Для пароля применяется старый алгоритм хэширования<br />Пожалуйста, смените пароль.'
},
changedAt: {
id: 'changedAt',
defaultMessage: 'Changed {at}'
// defaultMessage: 'Изменен {at}'
},
disabled: {
id: 'disabled',
defaultMessage: 'Disabled'
// defaultMessage: 'Не включена'
},
nickname: {
id: 'nickname',
defaultMessage: 'Nickname'
// defaultMessage: 'Ник'
},
password: {
id: 'password',
defaultMessage: 'Password'
// defaultMessage: 'Пароль'
},
twoFactorAuth: {
id: 'twoFactorAuth',
defaultMessage: 'Two factor auth'
// defaultMessage: 'Двухфакторная аутентификация'
}
});

View File

@ -0,0 +1,40 @@
import React, { Component, PropTypes } from 'react';
import styles from './profile.scss';
export default class ProfileField extends Component {
static displayName = 'ProfileField';
static propTypes = {
label: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
value: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
warningMessage: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
readonly: PropTypes.bool
};
render() {
const {label, value, warningMessage, readonly} = this.props;
return (
<div className={styles.paramItem}>
<div className={styles.paramRow}>
<div className={styles.paramName}>{label}:</div>
<div className={styles.paramValue}>{value}</div>
{readonly ? '' : (
<div className={styles.paramAction}>
<a href="#">
<span className={styles.paramEditIcon} />
</a>
</div>
)}
</div>
{warningMessage ? (
<div className={styles.paramMessage}>
{warningMessage}
</div>
) : ''}
</div>
);
}
}

View File

@ -0,0 +1,126 @@
@import '~components/ui/fonts.scss';
@import '~components/ui/colors.scss';
.container {
margin-top: 55px;
}
.title {
font-family: $font-family-title;
font-size: 30px;
margin-bottom: 12px;
}
.content {
display: flex;
}
.description {
font-size: 14px;
line-height: 1.4;
color: #9a9a9a;
width: 340px;
padding: 12px 20px 0 0;
box-sizing: border-box;
}
.options {
background: #fff;
flex-grow: 1;
max-width: 416px;
border-bottom: 10px solid #ddd8ce;
}
.optionsTitle {
position: relative;
font-size: 24px;
font-family: $font-family-title;
padding-bottom: 9px;
&:after {
content: '';
display: block;
position: absolute;
left: 0;
bottom: 0;
height: 3px;
width: 86px;
background: $green;
}
}
.optionsDescription {
font-size: 13px;
color: #9a9a9a;
line-height: 1.25;
margin-top: 25px;
}
.item {
padding: 30px;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
}
.paramItem {
composes: item;
$padding: 20px;
padding-top: $padding;
padding-bottom: $padding;
color: #666666;
}
.paramRow {
display: flex;
align-items: baseline;
flex-basis: 0;
flex-grow: 1;
font-size: 14px;
}
.paramName {
width: 125px;
font-family: $font-family-title;
}
.paramValue {
flex-grow: 1;
}
.uuidValue {
composes: paramName;
composes: paramValue;
}
.paramAction {
text-align: center;
}
.paramEditIcon {
composes: pencil from 'components/ui/icons.scss';
color: $light;
transition: .4s;
a:hover & {
color: #444;
}
}
.paramMessage {
padding: 10px 40px 0 0;
color: $red;
font-size: 11px;
line-height: 1.2;
}

View File

@ -17,6 +17,7 @@ export default class User {
const defaults = {
id: null,
uuid: null,
token: '',
username: '',
email: '',
@ -24,7 +25,8 @@ export default class User {
goal: null, // the goal with wich user entered site
isGuest: true,
isActive: true,
shouldChangePassword: false
shouldChangePassword: false, // TODO: нужно ещё пробросить причину необходимости смены
passwordChangedAt: null
};
const user = Object.keys(defaults).reduce((user, key) => {
@ -51,10 +53,12 @@ export default class User {
export const userShape = PropTypes.shape({
id: PropTypes.number,
uuid: PropTypes.string,
token: PropTypes.string,
username: PropTypes.string,
email: PropTypes.string,
avatar: PropTypes.string,
isGuest: PropTypes.bool.isRequired,
isActive: PropTypes.bool.isRequired
isActive: PropTypes.bool.isRequired,
passwordChangedAt: PropTypes.number
});

View File

@ -27,7 +27,7 @@ export default class LoggedInPanel extends Component {
return (
<div className={buttonGroups.horizontalGroup}>
<Link to="/profile" className={classNames(buttons.green, buttonGroups.item)}>
<Link to="/" className={classNames(buttons.green, buttonGroups.item)}>
<span className={styles.userIcon} />
<span className={styles.userName}>{user.username}</span>
</Link>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
<path d="M13.5 0c1.381 0 2.5 1.119 2.5 2.5 0 0.563-0.186 1.082-0.5 1.5l-1 1-3.5-3.5 1-1c0.418-0.314 0.937-0.5 1.5-0.5zM1 11.5l-1 4.5 4.5-1 9.25-9.25-3.5-3.5-9.25 9.25zM11.181 5.681l-7 7-0.862-0.862 7-7 0.862 0.862z"></path>
</svg>

After

Width:  |  Height:  |  Size: 543 B

View File

@ -2,20 +2,13 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Profile } from 'components/profile/Profile';
class IndexPage extends Component {
displayName = 'IndexPage';
render() {
const {user, children} = this.props;
return (
<div>
<h1>
Hello {user.username}!
</h1>
{children}
</div>
);
return (<Profile {...this.props} />);
}
}