1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 01:36:22 +00:00

add PersonalSettingModal

This commit is contained in:
Rokt33r
2015-07-24 15:13:04 +09:00
parent 2f754bbb87
commit 2e4aaf7345
14 changed files with 460 additions and 67 deletions

View File

@@ -3,5 +3,6 @@ var Reflux = require('reflux')
module.exports = Reflux.createActions([
'login',
'register',
'logout'
'logout',
'updateProfile'
])

View File

@@ -0,0 +1,171 @@
/* global localStorage */
var React = require('react/addons')
var request = require('superagent')
var Catalyst = require('../Mixins/Catalyst')
var AuthActions = require('../Actions/AuthActions')
var apiUrl = 'http://localhost:8000/'
module.exports = React.createClass({
mixins: [Catalyst.LinkedStateMixin],
propTypes: {
close: React.PropTypes.func,
currentUser: React.PropTypes.object
},
getInitialState: function () {
return {
currentTab: 'profile',
userName: this.props.currentUser.name,
email: this.props.currentUser.email,
currentPassword: '',
newPassword: '',
confirmation: '',
contactTitle: '',
contactContent: ''
}
},
activeProfile: function () {
this.setState({currentTab: 'profile'})
},
activeContact: function () {
this.setState({currentTab: 'contact'})
},
activeInfo: function () {
this.setState({currentTab: 'info'})
},
activeLogout: function () {
this.setState({currentTab: 'logout'})
},
saveProfile: function () {
AuthActions.updateProfile({
name: this.state.userName,
email: this.state.email
})
},
savePassword: function () {
if (this.state.newPassword === this.state.confirmation) {
request
.put(apiUrl + 'auth/password')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send({
currentPassword: this.state.currentPassword,
newPassword: this.state.newPassword
})
.end(function (err, res) {
this.setState({
currentPassword: '',
newPassword: '',
confirmation: ''
})
if (err) {
console.error(err)
return
}
}.bind(this))
}
},
sendEmail: function () {
},
logOut: function () {
AuthActions.logout()
},
interceptClick: function (e) {
e.stopPropagation()
},
render: function () {
var content
if (this.state.currentTab === 'profile') {
content = (
<div className='profile'>
<div className='profileTop'>
<div className='profileFormRow'>
<label>Name</label>
<input valueLink={this.linkState('userName')} className='block-input' type='text' placeholder='Name'/>
</div>
<div className='profileFormRow'>
<label>E-mail</label>
<input valueLink={this.linkState('email')} className='block-input' type='text' placeholder='E-mail'/>
</div>
<div className='profileFormRow'>
<button onClick={this.saveProfile} className='saveButton btn-primary'>Save</button>
</div>
</div>
<div className='profileBottom'>
<div className='profileFormRow'>
<label>Current password</label>
<input valueLink={this.linkState('currentPassword')} className='block-input' type='password' placeholder='Current password'/>
</div>
<div className='profileFormRow'>
<label>New password</label>
<input valueLink={this.linkState('newPassword')} className='block-input' type='password' placeholder='New password'/>
</div>
<div className='profileFormRow'>
<label>Confirmation</label>
<input valueLink={this.linkState('confirmation')} className='block-input' type='password' placeholder='Confirmation'/>
</div>
<div className='profileFormRow'>
<button onClick={this.savePassword} className='saveButton btn-primary'>Save</button>
</div>
</div>
</div>
)
} else if (this.state.currentTab === 'contact') {
content = (
<div className='contact'>
<p>
Let us know your opinion about CodeXen.<br/>
Your feedback might be used to improvement of CodeXen.
</p>
<input valueLink={this.linkState('contactTitle')} className='block-input' type='text' placeholder='title'/>
<textarea valueLink={this.linkState('contactContent')} className='block-input' placeholder='message content'/>
<div className='contactFormRow'>
<button onClick={this.sendEmail} className='saveButton btn-primary'>Send</button>
</div>
</div>
)
} else if (this.state.currentTab === 'info') {
content = (
<div className='info'>
<h2 className='infoLabel'>External links</h2>
<ul className='externalList'>
<li><a>CodeXen Homepage <i className='fa fa-external-link'/></a></li>
<li><a>Regulation <i className='fa fa-external-link'/></a></li>
<li><a>Private policy <i className='fa fa-external-link'/></a></li>
</ul>
</div>
)
} else {
content = (
<div className='logout'>
<p className='logoutLabel'>Are you sure to logout?</p>
<img className='userPhoto' width='150' height='150' src='../vendor/dummy.jpg'/><br/>
<button onClick={this.logOut} className='logoutButton btn-default'>Logout</button>
</div>
)
}
return (
<div onClick={this.interceptClick} className='PersonalSettingModal modal'>
<div className='settingNav'>
<h1>Personal setting</h1>
<nav>
<button className={this.state.currentTab === 'profile' ? 'active' : ''} onClick={this.activeProfile}><i className='fa fa-user fa-fw'/> Profile</button>
<button className={this.state.currentTab === 'contact' ? 'active' : ''} onClick={this.activeContact}><i className='fa fa-phone fa-fw'/> Contact</button>
<button className={this.state.currentTab === 'info' ? 'active' : ''} onClick={this.activeInfo}><i className='fa fa-info-circle fa-fw'/> Info</button>
<button className={this.state.currentTab === 'logout' ? 'active' : ''} onClick={this.activeLogout}><i className='fa fa-sign-out fa-fw'/> Logout</button>
</nav>
</div>
<div className='settingBody'>
{content}
</div>
</div>
)
}
})

View File

@@ -22,8 +22,8 @@ module.exports = React.createClass({
this.setState({currentTab: 'planetProfile'})
},
activeManageMember: function () {
this.setState({currentTab: 'manageMember'})
activeMembers: function () {
this.setState({currentTab: 'members'})
},
saveProfile: function () {
var currentPlanet = this.props.currentPlanet
@@ -79,7 +79,7 @@ module.exports = React.createClass({
}.bind(this))
content = (
<div className='manageMember'>
<div className='members'>
<ul className='userList'>
{members}
</ul>
@@ -99,8 +99,8 @@ module.exports = React.createClass({
<div className='settingNav'>
<h1>Planet setting</h1>
<nav>
<button className={this.state.currentTab === 'planetProfile' ? 'active' : ''} onClick={this.activePlanetProfile}><i className='fa fa-globe'/> Planet profile</button>
<button className={this.state.currentTab === 'manageMember' ? 'active' : ''} onClick={this.activeManageMember}><i className='fa fa-group'/> Manage member</button>
<button className={this.state.currentTab === 'planetProfile' ? 'active' : ''} onClick={this.activePlanetProfile}><i className='fa fa-globe fa-fw'/> Planet profile</button>
<button className={this.state.currentTab === 'members' ? 'active' : ''} onClick={this.activeMembers}><i className='fa fa-group fa-fw'/> Members</button>
</nav>
</div>

View File

@@ -3,6 +3,7 @@ var ReactRouter = require('react-router')
var Link = ReactRouter.Link
var ModalBase = require('./ModalBase')
var PersonalSettingModal = require('./PersonalSettingModal')
var PlanetCreateModal = require('./PlanetCreateModal')
var AuthStore = require('../Stores/AuthStore')
@@ -27,6 +28,12 @@ module.exports = React.createClass({
onLogout: function () {
this.transitionTo('login')
},
openPersonalSettingModal: function () {
this.setState({isPersonalSettingModalOpen: true})
},
closePersonalSettingModal: function () {
this.setState({isPersonalSettingModalOpen: false})
},
openPlanetCreateModal: function () {
this.setState({isPlanetCreateModalOpen: true})
},
@@ -51,13 +58,18 @@ module.exports = React.createClass({
return (
<div tabIndex='2' className='UserNavigator'>
<Link to='userHome' params={{userName: this.props.currentUser.name}} className='userConfig'>
<button onClick={this.openPersonalSettingModal} className='userButton'>
<img width='50' height='50' src='../vendor/dummy.jpg'/>
</Link>
<ul>
</button>
<ul className='planetList'>
{planets}
</ul>
<button onClick={this.openPlanetCreateModal} className='newPlanet'><i className='fa fa-plus'/></button>
<ModalBase isOpen={this.state.isPersonalSettingModalOpen} close={this.closePersonalSettingModal}>
<PersonalSettingModal currentUser={this.props.currentUser} close={this.closePersonalSettingModal}/>
</ModalBase>
<ModalBase isOpen={this.state.isPlanetCreateModalOpen} close={this.closePlanetCreateModal}>
<PlanetCreateModal close={this.closePlanetCreateModal}/>
</ModalBase>

View File

@@ -2,7 +2,6 @@ var React = require('react/addons')
var ReactRouter = require('react-router')
var Link = ReactRouter.Link
var AuthStore = require('../Stores/AuthStore')
var AuthActions = require('../Actions/AuthActions')
var OnlyGuest = require('../Mixins/OnlyGuest')
@@ -15,12 +14,6 @@ module.exports = React.createClass({
password: ''
}
},
componentDidMount: function () {
this.unsubscribe = AuthStore.listen(this.onLogin)
},
componentWillUnmount: function () {
this.unsubscribe()
},
handleSubmit: function (e) {
AuthActions.login({
email: this.state.email,
@@ -28,14 +21,6 @@ module.exports = React.createClass({
})
e.preventDefault()
},
onLogin: function (user) {
var planet = user.Planets.length > 0 ? user.Planets[0] : null
if (planet == null) {
this.transitionTo('user', {userName: user.name})
return
}
this.transitionTo('planetHome', {userName: user.name, planetName: planet.name})
},
render: function () {
return (
<div className='LoginContainer'>

View File

@@ -2,8 +2,33 @@ var React = require('react/addons')
var ReactRouter = require('react-router')
var RouteHandler = ReactRouter.RouteHandler
var AuthStore = require('../Stores/AuthStore')
module.exports = React.createClass({
mixins: [ReactRouter.Navigation, ReactRouter.State],
componentDidMount: function () {
this.unsubscribe = AuthStore.listen(this.onListen)
},
componentWillUnmount: function () {
this.unsubscribe()
},
onListen: function (res) {
if (res.status === 'loggedIn' || res.status === 'registered') {
var user = res.data
var planet = user.Planets.length > 0 ? user.Planets[0] : null
if (planet == null) {
this.transitionTo('user', {userName: user.name})
return
}
this.transitionTo('planetHome', {userName: user.name, planetName: planet.name})
return
}
if (res.status === 'loggedOut') {
this.transitionTo('login')
return
}
},
render: function () {
// Redirect Login state
if (this.getPath() === '/') {

View File

@@ -56,18 +56,21 @@ module.exports = React.createClass({
},
getInitialState: function () {
return {
currentUser: AuthStore.getUser(),
currentPlanet: null,
search: '',
isFetched: false
}
},
componentDidMount: function () {
this.unsubscribe = PlanetStore.listen(this.onFetched)
this.unsubscribePlanet = PlanetStore.listen(this.onFetched)
this.unsubscribeAuth = AuthStore.listen(this.onListenAuth)
PlanetActions.fetchPlanet(this.props.params.userName, this.props.params.planetName)
},
componentWillUnmount: function () {
this.unsubscribe()
this.unsubscribePlanet()
this.unsubscribeAuth()
},
componentDidUpdate: function () {
if (this.state.currentPlanet == null || this.state.currentPlanet.name !== this.props.params.planetName || this.state.currentPlanet.userName !== this.props.params.userName) {
@@ -161,6 +164,19 @@ module.exports = React.createClass({
React.findDOMNode(this).querySelector('.PlanetHeader .searchInput input').focus()
}
},
onListenAuth: function (res) {
if (res.status === 'userProfileUpdated') {
if (this.state.currentPlanet != null) {
res.data.Planets.some(function (planet) {
if (planet.id === this.state.currentPlanet.id) {
this.transitionTo('planet', {userName: planet.userName, planetName: planet.name})
return true
}
return false
}.bind(this))
}
}
},
onFetched: function (res) {
if (res == null) {
return
@@ -396,8 +412,7 @@ module.exports = React.createClass({
},
render: function () {
var user = AuthStore.getUser()
if (user == null) return (<div/>)
if (this.state.currentUser == null) return (<div/>)
if (this.state.currentPlanet == null) return (<div/>)
var localId = parseInt(this.props.params.localId, 10)

View File

@@ -2,7 +2,6 @@ var React = require('react/addons')
var ReactRouter = require('react-router')
var Link = ReactRouter.Link
var AuthStore = require('../Stores/AuthStore')
var AuthActions = require('../Actions/AuthActions')
var OnlyGuest = require('../Mixins/OnlyGuest')
@@ -17,12 +16,6 @@ module.exports = React.createClass({
profileName: ''
}
},
componentDidMount: function () {
this.unsubscribe = AuthStore.listen(this.onRegister)
},
componentWillUnmount: function () {
this.unsubscribe()
},
handleSubmit: function (e) {
AuthActions.register({
email: this.state.email,
@@ -33,14 +26,6 @@ module.exports = React.createClass({
e.preventDefault()
},
onRegister: function (user) {
var planet = user.Planets.length > 0 ? user.Planets[0] : null
if (planet == null) {
this.transitionTo('user', {userName: user.name})
return
}
this.transitionTo('planetHome', {userName: user.name, planetName: planet.name})
},
render: function () {
return (
<div className='RegisterContainer'>

View File

@@ -2,6 +2,8 @@
var React = require('react/addons')
var ReactRouter = require('react-router')
var RouteHandler = ReactRouter.RouteHandler
var Link = ReactRouter.Link
var request = require('superagent')
var UserNavigator = require('../Components/UserNavigator')
@@ -9,22 +11,49 @@ var AuthStore = require('../Stores/AuthStore')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [React.addons.LinkedStateMixin, ReactRouter.Navigation],
mixins: [React.addons.LinkedStateMixin, ReactRouter.Navigation, ReactRouter.State],
propTypes: {
params: React.PropTypes.shape({
userName: React.PropTypes.string,
planetName: React.PropTypes.string
})
},
getInitialState: function () {
return {
currentUser: AuthStore.getUser()
currentUser: AuthStore.getUser(),
isUserFetched: false,
user: null
}
},
componentDidMount: function () {
this.unsubscribe = PlanetStore.listen(this.onListen)
this.unsubscribePlanet = PlanetStore.listen(this.onListen)
this.unsubscribeAuth = AuthStore.listen(this.onListen)
if (this.isActive('userHome')) {
this.fetchUser(this.props.params.userName)
}
},
componentWillUnmount: function () {
this.unsubscribe()
this.unsubscribePlanet()
this.unsubscribeAuth()
},
componentDidUpdate: function () {
if (this.isActive('userHome') && (this.state.user == null || this.state.user.name !== this.props.params.userName)) {
this.fetchUser(this.props.params.userName)
}
},
fetchUser: function (userName) {
request
.get('http://localhost:8000/' + userName)
.send()
.end(function (err, res) {
if (err) {
console.error(err)
return
}
this.setState({user: res.body, isUserFetched: true})
}.bind(this))
},
onListen: function (res) {
if (res.status == null) return
@@ -40,10 +69,15 @@ module.exports = React.createClass({
if (res.status === 'nameChanged') {
this.setState({currentUser: AuthStore.getUser()})
}
if (res.status === 'userProfileUpdated') {
this.setState({currentUser: AuthStore.getUser()})
}
},
render: function () {
var currentPlanetName = this.props.params.planetName
var currentUser = this.state.currentUser
var user = this.state.user
// user must be logged in
if (currentUser == null) return (<div></div>)
@@ -57,10 +91,49 @@ module.exports = React.createClass({
return false
})
var content
if (this.isActive('userHome')) {
if (this.state.isUserFetched === false) {
content = (
<div className='UserHome'>
User Loading...
</div>
)
} else {
var planets = user.Planets.map(function (planet) {
return (
<li key={'planet-' + planet.id}>
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
</li>
)
})
content = (
<div className='UserHome'>
<h1>User Profile</h1>
<div className='userProfile'>
<img className='userPhoto' width='150' height='150' src='../vendor/dummy.jpg'/>
<div className='userIntro'>
<div className='userProfileName'>{user.profileName}</div>
<Link className='userName' to='user' params={{userName: user.name}}>{user.name}</Link>
</div>
</div>
<h2>Planets</h2>
<ul className='userPlanetList'>
{planets}
</ul>
</div>
)
}
} else {
content = (
<RouteHandler/>
)
}
return (
<div className='UserContainer'>
<UserNavigator currentPlanet={currentPlanet} currentUser={currentUser}/>
<RouteHandler/>
{content}
</div>
)
}

View File

@@ -4,16 +4,19 @@ var request = require('superagent')
var AuthActions = require('../Actions/AuthActions')
var apiUrl = 'http://localhost:8000/'
var AuthStore = Reflux.createStore({
init: function () {
this.listenTo(AuthActions.login, this.login)
this.listenTo(AuthActions.register, this.register)
this.listenTo(AuthActions.logout, this.logout)
this.listenTo(AuthActions.updateProfile, this.updateProfile)
},
// Reflux Store
login: function (input) {
request
.post('http://localhost:8000/auth/login')
.post(apiUrl + 'auth/login')
.send(input)
.set('Accept', 'application/json')
.end(function (err, res) {
@@ -27,12 +30,15 @@ var AuthStore = Reflux.createStore({
localStorage.setItem('token', res.body.token)
localStorage.setItem('user', JSON.stringify(res.body.user))
this.trigger(user)
this.trigger({
status: 'loggedIn',
data: user
})
}.bind(this))
},
register: function (input) {
request
.post('http://localhost:8000/auth/signup')
.post(apiUrl + 'auth/signup')
.send(input)
.set('Accept', 'application/json')
.end(function (err, res) {
@@ -46,14 +52,42 @@ var AuthStore = Reflux.createStore({
localStorage.setItem('token', res.body.token)
localStorage.setItem('user', JSON.stringify(res.body.user))
this.trigger(user)
this.trigger({
status: 'registered',
data: user
})
}.bind(this))
},
logout: function () {
localStorage.removeItem('token')
localStorage.removeItem('user')
this.trigger()
this.trigger({
status: 'loggedOut'
})
},
updateProfile: function (input) {
request
.put(apiUrl + 'auth/user')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
.end(function (err, res) {
if (err) {
console.error(err)
this.trigger(null)
return
}
var user = res.body
localStorage.setItem('user', JSON.stringify(user))
this.trigger({
status: 'userProfileUpdated',
data: user
})
}.bind(this))
},
// Methods
check: function () {

View File

@@ -13,7 +13,7 @@ var LoginContainer = require('./Containers/LoginContainer.jsx')
var RegisterContainer = require('./Containers/RegisterContainer.jsx')
var UserContainer = require('./Containers/UserContainer.jsx')
var UserSettingContainer = require('./Containers/UserSettingContainer.jsx')
var PlanetContainer = require('./Containers/PlanetContainer.jsx')
var routes = (
@@ -22,7 +22,7 @@ var routes = (
<Route name='register' path='register' handler={RegisterContainer}/>
<Route name='user' path=':userName' handler={UserContainer}>
<DefaultRoute name='userHome' handler={UserSettingContainer}/>
<DefaultRoute name='userHome'/>
<Route name='planet' path=':planetName' handler={PlanetContainer}>
<DefaultRoute name='planetHome'/>
<Route name='snippets' path='snippets/:localId'/>

View File

@@ -5,19 +5,23 @@
width 50px
text-align center
box-sizing border-box
a.userConfig
.userButton
display block
width 50px
height 50px
background-color black
margin 0
padding 0
border none
cursor pointer
img
transition 0.1s
opacity 0.6
opacity 0.8
box-sizing border-box
&.active, &:active, &.focus, &:focus, &.hover, &:hover
img
opacity 1
ul>li
ul.planetList>li
padding 10px 3px
.shortCut
margin-top 5px
@@ -68,3 +72,29 @@
border-color darken(brandBorderColor, 10%)
background-color brandColor
color white
.UserHome
absolute top bottom right
left 50px
box-sizing border-box
padding 15px
h1
margin 15px 0
.userProfile
clearfix()
.userPhoto
circle()
float left
margin 25px
.userIntro
float left
margin-top 25px
.userProfileName
font-size 2em
margin-bottom 15px
.userName
font-size 1.5em
.userPlanetList
padding-left 20px
li
font-size 1.3em
margin 10px

View File

@@ -1,16 +1,19 @@
btnDefault()
border-style solid
border-width 1px
border-color brandBorderColor
border-color lightButtonColor
background-color transparent
color brandColor
color lightButtonColor
&:hover, &.hover, &:focus, &.focus
border-color darken(brandBorderColor, 30%)
color darken(brandColor, 30%)
border-color darken(lightButtonColor, 50%)
color darken(lightButtonColor, 50%)
&:active, &.active
border-color darken(brandBorderColor, 10%)
background-color brandColor
color white
btnPrimary()
border-style solid
border-width 1px

View File

@@ -107,7 +107,7 @@
height 55px
circle()
btnPrimary()
.PlanetSettingModal.modal
.PlanetSettingModal.modal, .PersonalSettingModal.modal
width 720px
height 500px
.settingNav
@@ -117,7 +117,7 @@
padding 10px
border-right solid 1px borderColor
h1
margin 40px 15px
margin 40px auto
font-size 1.5em
color brandColor
nav
@@ -139,11 +139,70 @@
background-color hoverBackgroundColor
&:active, &.active
color brandColor
.settingBody
absolute top bottom right
left 200px
padding 15px
.PersonalSettingModal.modal
.settingBody
.profile
height 500px
padding-top 50px
.profileTop
box-sizing border-box
height 200px
border-bottom solid 1px borderColor
.profileBottom
margin-top 25px
height 200px
.profileFormRow
clearfix()
margin-bottom 15px
label
display block
float left
width 150px
line-height 33px
text-align left
input
float left
width 250px
.saveButton
float right
.contact
height 500px
padding-top 50px
p
text-align left
margin-bottom 15px
line-height 140%
input
margin-bottom 15px
textarea
margin-bottom 15px
max-height 250px
.contactFormRow
clearfix()
.saveButton
float right
.info
text-align left
.infoLabel
margin 75px 0 25px
.externalList
padding-left 10px
li
margin 15px
.logout
.logoutLabel
margin 100px 0 25px
font-size 1.4em
.userPhoto
margin-bottom 25px
circle()
.PlanetSettingModal.modal
.settingBody
.planetProfile
height 500px
padding-top 50px
@@ -174,7 +233,7 @@
margin-right 0
.deleteButton
float left
.manageMember
.members
height 500px
box-sizing border-box
padding-top 50px