mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-26 08:01:49 +00:00
add Preferences modal(30%done) & move some modules (actions, reducer, socket, store -> lib/~)
This commit is contained in:
78
lib/actions.js
Normal file
78
lib/actions.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// Action types
|
||||
export const USER_UPDATE = 'USER_UPDATE'
|
||||
export const ARTICLE_REFRESH = 'ARTICLE_REFRESH'
|
||||
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
|
||||
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
|
||||
|
||||
export const SWITCH_USER = 'SWITCH_USER'
|
||||
export const SWITCH_FOLDER = 'SWITCH_FOLDER'
|
||||
export const SWITCH_MODE = 'SWITCH_MODE'
|
||||
export const SWITCH_ARTICLE = 'SWITCH_ARTICLE'
|
||||
|
||||
// Status - mode
|
||||
export const IDLE_MODE = 'IDLE_MODE'
|
||||
export const CREATE_MODE = 'CREATE_MODE'
|
||||
export const EDIT_MODE = 'EDIT_MODE'
|
||||
|
||||
// Article status
|
||||
export const NEW = 'NEW'
|
||||
export const SYNCING = 'SYNCING'
|
||||
export const UNSYNCED = 'UNSYNCED'
|
||||
|
||||
// DB
|
||||
export function updateUser (user) {
|
||||
return {
|
||||
type: USER_UPDATE,
|
||||
data: user
|
||||
}
|
||||
}
|
||||
|
||||
export function refreshArticles (userId, articles) {
|
||||
return {
|
||||
type: ARTICLE_REFRESH,
|
||||
data: { userId, articles }
|
||||
}
|
||||
}
|
||||
|
||||
export function updateArticle (userId, article) {
|
||||
return {
|
||||
type: ARTICLE_UPDATE,
|
||||
data: { userId, article }
|
||||
}
|
||||
}
|
||||
|
||||
export function destroyArticle (userId, articleKey) {
|
||||
return {
|
||||
type: ARTICLE_DESTROY,
|
||||
data: { userId, articleKey }
|
||||
}
|
||||
}
|
||||
|
||||
// Nav
|
||||
export function switchUser (userId) {
|
||||
return {
|
||||
type: SWITCH_USER,
|
||||
data: userId
|
||||
}
|
||||
}
|
||||
|
||||
export function switchFolder (folderId) {
|
||||
return {
|
||||
type: SWITCH_FOLDER,
|
||||
data: folderId
|
||||
}
|
||||
}
|
||||
|
||||
export function switchMode (mode) {
|
||||
return {
|
||||
type: SWITCH_MODE,
|
||||
data: mode
|
||||
}
|
||||
}
|
||||
|
||||
export function switchArticle (articleKey) {
|
||||
return {
|
||||
type: SWITCH_ARTICLE,
|
||||
data: articleKey
|
||||
}
|
||||
}
|
||||
20
lib/api.js
20
lib/api.js
@@ -17,6 +17,24 @@ export function signup (input) {
|
||||
.send(input)
|
||||
}
|
||||
|
||||
export function updateUserInfo (input) {
|
||||
return request
|
||||
.put(API_URL + 'auth/user')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + auth.token()
|
||||
})
|
||||
.send(input)
|
||||
}
|
||||
|
||||
export function updatePassword (input) {
|
||||
return request
|
||||
.post(API_URL + 'auth/password')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + auth.token()
|
||||
})
|
||||
.send(input)
|
||||
}
|
||||
|
||||
export function fetchCurrentUser () {
|
||||
return request
|
||||
.get(API_URL + 'auth/user')
|
||||
@@ -113,6 +131,8 @@ export function sendEmail (input) {
|
||||
export default {
|
||||
login,
|
||||
signup,
|
||||
updateUserInfo,
|
||||
updatePassword,
|
||||
fetchCurrentUser,
|
||||
fetchArticles,
|
||||
createArticle,
|
||||
|
||||
@@ -32,22 +32,22 @@ export default class CreateNewFolder extends React.Component {
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
if (err.code === '') {
|
||||
let alert = {
|
||||
var alert
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
alert = {
|
||||
type: 'error',
|
||||
message: 'Can\'t connect to API server.'
|
||||
}
|
||||
}
|
||||
else if (err.status != null) {
|
||||
let alert = {
|
||||
} else if (err.status != null) {
|
||||
alert = {
|
||||
type: 'error',
|
||||
message: err.response.body.message
|
||||
}
|
||||
this.setState({alert: alert})
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
|
||||
this.setState({alert: alert})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
427
lib/components/modal/Preferences.js
Normal file
427
lib/components/modal/Preferences.js
Normal file
@@ -0,0 +1,427 @@
|
||||
import React, { PropTypes, findDOMNode } from 'react'
|
||||
import { connect, Provider } from 'react-redux'
|
||||
import auth from 'boost/auth'
|
||||
import linkState from 'boost/linkState'
|
||||
import Select from 'react-select'
|
||||
import api from 'boost/api'
|
||||
import ProfileImage from 'boost/components/ProfileImage'
|
||||
import store from 'boost/store'
|
||||
|
||||
const PROFILE = 'PROFILE'
|
||||
const PREFERENCES = 'PREFERENCES'
|
||||
const HELP = 'HELP'
|
||||
const TEAM = 'TEAM'
|
||||
const MEMBER = 'MEMBER'
|
||||
const FOLDER = 'FOLDER'
|
||||
|
||||
function getUsers (input, cb) {
|
||||
api.searchUser(input)
|
||||
.then(function (res) {
|
||||
let users = res.body
|
||||
|
||||
cb(null, {
|
||||
options: users.map(user => {
|
||||
return { value: user.name, label: user.name }
|
||||
}),
|
||||
complete: false
|
||||
})
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
class Preferences extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
currentTab: PROFILE,
|
||||
profile: {
|
||||
userInfo: {
|
||||
profileName: props.currentUser.profileName,
|
||||
email: props.currentUser.email,
|
||||
alert: null
|
||||
},
|
||||
password: {
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmation: '',
|
||||
error: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleNavButtonClick (tab) {
|
||||
return e => {
|
||||
this.setState({currentTab: tab})
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let content = this.renderContent()
|
||||
|
||||
let tabs = [
|
||||
{target: PROFILE, label: 'Profile'},
|
||||
{target: PREFERENCES, label: 'Preferences'},
|
||||
{target: HELP, label: 'Help & Feedback'},
|
||||
{target: TEAM, label: 'Team setting'},
|
||||
{target: MEMBER, label: 'Manage member'},
|
||||
{target: FOLDER, label: 'Manage folder'}
|
||||
]
|
||||
|
||||
let navButtons = tabs.map(tab => (
|
||||
<button key={tab.target} onClick={e => this.handleNavButtonClick(tab.target)(e)} className={this.state.currentTab === tab.target ? 'active' : ''}>{tab.label}</button>
|
||||
))
|
||||
|
||||
return (
|
||||
<div className='Preferences modal'>
|
||||
<div className='header'>
|
||||
<div className='title'>Setting</div>
|
||||
<button onClick={e => this.props.close()} className='closeBtn'>Done</button>
|
||||
</div>
|
||||
|
||||
<div className='nav'>
|
||||
{navButtons}
|
||||
</div>
|
||||
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
switch (this.state.currentTab) {
|
||||
case PREFERENCES:
|
||||
return this.renderPreferences()
|
||||
case HELP:
|
||||
return this.renderHelp()
|
||||
case TEAM:
|
||||
return this.renderTeamSetting()
|
||||
case MEMBER:
|
||||
return this.renderMemberSetting()
|
||||
case FOLDER:
|
||||
return this.renderFolderSetting()
|
||||
case PROFILE:
|
||||
default:
|
||||
return this.renderProfile()
|
||||
}
|
||||
}
|
||||
|
||||
handleProfileSaveButtonClick (e) {
|
||||
let profileState = this.state.profile
|
||||
profileState.userInfo.alert = {
|
||||
type: 'info',
|
||||
message: 'Sending...'
|
||||
}
|
||||
this.setState({profile: profileState}, () => {
|
||||
let input = {
|
||||
profileName: profileState.userInfo.profileName,
|
||||
email: profileState.userInfo.email
|
||||
}
|
||||
api.updateUserInfo(input)
|
||||
.then(res => {
|
||||
let profileState = this.state.profile
|
||||
profileState.userInfo.alert = {
|
||||
type: 'success',
|
||||
message: 'Successfully done!'
|
||||
}
|
||||
this.setState({profile: profileState})
|
||||
})
|
||||
.catch(err => {
|
||||
var message
|
||||
if (err.status != null) {
|
||||
message = err.response.body.message
|
||||
} else if (err.code === 'ECONNREFUSED') {
|
||||
message = 'Can\'t connect to API server.'
|
||||
} else throw err
|
||||
|
||||
let profileState = this.state.profile
|
||||
profileState.userInfo.alert = {
|
||||
type: 'error',
|
||||
message: message
|
||||
}
|
||||
|
||||
this.setState({profile: profileState})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handlePasswordSaveButton (e) {
|
||||
let profileState = this.state.profile
|
||||
|
||||
if (profileState.password.newPassword !== profileState.password.confirmation) {
|
||||
profileState.password.alert = {
|
||||
type: 'error',
|
||||
message: 'Confirmation doesn\'t match'
|
||||
}
|
||||
this.setState({profile: profileState})
|
||||
return
|
||||
}
|
||||
|
||||
profileState.password.alert = {
|
||||
type: 'info',
|
||||
message: 'Sending...'
|
||||
}
|
||||
|
||||
this.setState({profile: profileState}, () => {
|
||||
let input = {
|
||||
password: profileState.password.currentPassword,
|
||||
newPassword: profileState.password.newPassword
|
||||
}
|
||||
api.updatePassword(input)
|
||||
.then(res => {
|
||||
let profileState = this.state.profile
|
||||
profileState.password.alert = {
|
||||
type: 'success',
|
||||
message: 'Successfully done!'
|
||||
}
|
||||
profileState.password.currentPassword = ''
|
||||
profileState.password.newPassword = ''
|
||||
profileState.password.confirmation = ''
|
||||
|
||||
this.setState({profile: profileState})
|
||||
})
|
||||
.catch(err => {
|
||||
var message
|
||||
if (err.status != null) {
|
||||
message = err.response.body.message
|
||||
} else if (err.code === 'ECONNREFUSED') {
|
||||
message = 'Can\'t connect to API server.'
|
||||
} else throw err
|
||||
|
||||
let profileState = this.state.profile
|
||||
profileState.password.alert = {
|
||||
type: 'error',
|
||||
message: message
|
||||
}
|
||||
profileState.password.currentPassword = ''
|
||||
profileState.password.newPassword = ''
|
||||
profileState.password.confirmation = ''
|
||||
|
||||
this.setState({profile: profileState}, () => {
|
||||
if (this.refs.currentPassword != null) findDOMNode(this.refs.currentPassword).focus()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
renderProfile () {
|
||||
let profileState = this.state.profile
|
||||
return (
|
||||
<div className='content profile'>
|
||||
<div className='section userSection'>
|
||||
<div className='sectionTitle'>User Info</div>
|
||||
<div className='sectionInput'>
|
||||
<label>Profile Name</label>
|
||||
<input valueLink={this.linkState('profile.userInfo.profileName')} type='text'/>
|
||||
</div>
|
||||
<div className='sectionInput'>
|
||||
<label>E-mail</label>
|
||||
<input valueLink={this.linkState('profile.userInfo.email')} type='text'/>
|
||||
</div>
|
||||
<div className='sectionConfirm'>
|
||||
<button onClick={e => this.handleProfileSaveButtonClick(e)}>Save</button>
|
||||
|
||||
{this.state.profile.userInfo.alert != null
|
||||
? (
|
||||
<div className={'alert ' + profileState.userInfo.alert.type}>{profileState.userInfo.alert.message}</div>
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='section passwordSection'>
|
||||
<div className='sectionTitle'>Password</div>
|
||||
<div className='sectionInput'>
|
||||
<label>Current Password</label>
|
||||
<input ref='currentPassword' valueLink={this.linkState('profile.password.currentPassword')} type='password' placeholder='Current Password'/>
|
||||
</div>
|
||||
<div className='sectionInput'>
|
||||
<label>New Password</label>
|
||||
<input valueLink={this.linkState('profile.password.newPassword')} type='password' placeholder='New Password'/>
|
||||
</div>
|
||||
<div className='sectionInput'>
|
||||
<label>Confirmation</label>
|
||||
<input valueLink={this.linkState('profile.password.confirmation')} type='password' placeholder='Confirmation'/>
|
||||
</div>
|
||||
<div className='sectionConfirm'>
|
||||
<button onClick={e => this.handlePasswordSaveButton(e)}>Save</button>
|
||||
|
||||
{this.state.profile.password.alert != null
|
||||
? (
|
||||
<div className={'alert ' + profileState.password.alert.type}>{profileState.password.alert.message}</div>
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderPreferences () {
|
||||
return (
|
||||
<div className='content preferences'>
|
||||
<div className='section passwordSection'>
|
||||
<div className='sectionTitle'>Hotkey</div>
|
||||
<div className='sectionInput'>
|
||||
<label>Toggle Finder(popup)</label>
|
||||
<input type='text'/>
|
||||
</div>
|
||||
<div className='sectionConfirm'>
|
||||
<button>Save</button>
|
||||
</div>
|
||||
<div className='description'>
|
||||
<ul>
|
||||
<li><code>0</code> to <code>9</code></li>
|
||||
<li><code>A</code> to <code>Z</code></li>
|
||||
<li><code>F1</code> to <code>F24</code></li>
|
||||
<li>Punctuations like <code>~</code>, <code>!</code>, <code>@</code>, <code>#</code>, <code>$</code>, etc.</li>
|
||||
<li><code>Plus</code></li>
|
||||
<li><code>Space</code></li>
|
||||
<li><code>Backspace</code></li>
|
||||
<li><code>Delete</code></li>
|
||||
<li><code>Insert</code></li>
|
||||
<li><code>Return</code> (or <code>Enter</code> as alias)</li>
|
||||
<li><code>Up</code>, <code>Down</code>, <code>Left</code> and <code>Right</code></li>
|
||||
<li><code>Home</code> and <code>End</code></li>
|
||||
<li><code>PageUp</code> and <code>PageDown</code></li>
|
||||
<li><code>Escape</code> (or <code>Esc</code> for short)</li>
|
||||
<li><code>VolumeUp</code>, <code>VolumeDown</code> and <code>VolumeMute</code></li>
|
||||
<li><code>MediaNextTrack</code>, <code>MediaPreviousTrack</code>, <code>MediaStop</code> and <code>MediaPlayPause</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderHelp () {
|
||||
return (
|
||||
<div className='content help'>
|
||||
Comming soon
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderTeamSetting () {
|
||||
return (
|
||||
<div className='content teamSetting'>
|
||||
<div className='header'>
|
||||
<select>
|
||||
<option></option>
|
||||
</select>
|
||||
<div>'s Team Setting</div>
|
||||
</div>
|
||||
<div className='section'>
|
||||
<div className='sectionTitle'>Team profile</div>
|
||||
<div className='sectionInput'>
|
||||
<div className='label'>Team Name</div>
|
||||
<input type='text'/>
|
||||
</div>
|
||||
<div className='sectionConfirm'>
|
||||
<button>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{false
|
||||
? (
|
||||
<div className='section teamDelete'>
|
||||
<div className='label'>Delete this team</div>
|
||||
<button>Delete</button>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='section teamDeleteConfirm'>
|
||||
<div>Are you sure to delete this team?</div>
|
||||
<button>Sure</button>
|
||||
<button>Cancel</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderMemberSetting () {
|
||||
let membersEl = [].map(member => {
|
||||
let isCurrentUser = this.state.currentUser.id === member.id
|
||||
|
||||
return (
|
||||
<li key={'user-' + member.id}>
|
||||
<ProfileImage className='userPhoto' email={member.email} size='30'/>
|
||||
<div className='userInfo'>
|
||||
<div className='userName'>{`${member.profileName} (${member.name})`}</div>
|
||||
<div className='userEmail'>{member.email}</div>
|
||||
</div>
|
||||
|
||||
<div className='userControl'>
|
||||
<select onChange={e => this.handleMemberRoleChange(member.name)(e)} disabled={isCurrentUser} value={member._pivot_role} className='userRole'>
|
||||
<option value='owner'>Owner</option>
|
||||
<option value='member'>Member</option>
|
||||
</select>
|
||||
<button onClick={e => this.handleMemberDeleteButtonClick(member.name)(e)} disabled={isCurrentUser}><i className='fa fa-times fa-fw'/></button>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='content memberSetting'>
|
||||
<div className='header'>
|
||||
<select>
|
||||
<option></option>
|
||||
</select>
|
||||
<div>'s Team Setting</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Select
|
||||
className='memberName'
|
||||
autoload={false}
|
||||
asyncOptions={getUsers}
|
||||
onChange={val => this.handleNewMemberChange(val)}
|
||||
value={null}
|
||||
/>
|
||||
<button onClick={e => this.handleClickAddMemberButton(e)} className='addMemberBtn'>add</button>
|
||||
</div>
|
||||
<ul className='memberList'>
|
||||
{membersEl}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFolderSetting () {
|
||||
return (
|
||||
<div className='content folderSetting'></div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Preferences.propTypes = {
|
||||
currentUser: PropTypes.shape(),
|
||||
close: PropTypes.func
|
||||
}
|
||||
|
||||
Preferences.prototype.linkState = linkState
|
||||
|
||||
function remap (state) {
|
||||
let currentUser = state.currentUser
|
||||
|
||||
return {
|
||||
currentUser
|
||||
}
|
||||
}
|
||||
|
||||
let RootComponent = connect(remap)(Preferences)
|
||||
export default class PreferencesModal extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
{() => <RootComponent/>}
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
115
lib/reducer.js
Normal file
115
lib/reducer.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import { combineReducers } from 'redux'
|
||||
import { findIndex } from 'lodash'
|
||||
import { SWITCH_USER, SWITCH_FOLDER, SWITCH_MODE, SWITCH_ARTICLE, USER_UPDATE, ARTICLE_REFRESH, ARTICLE_UPDATE, ARTICLE_DESTROY, IDLE_MODE, CREATE_MODE } from './actions'
|
||||
import auth from 'boost/auth'
|
||||
|
||||
const initialStatus = {
|
||||
mode: IDLE_MODE
|
||||
}
|
||||
|
||||
function getInitialArticles () {
|
||||
let initialCurrentUser = auth.user()
|
||||
if (initialCurrentUser == null) return []
|
||||
|
||||
let teams = Array.isArray(initialCurrentUser.Teams) ? initialCurrentUser.Teams : []
|
||||
|
||||
let users = [initialCurrentUser, ...teams]
|
||||
let initialArticles = users.reduce((res, user) => {
|
||||
res['team-' + user.id] = JSON.parse(localStorage.getItem('team-' + user.id))
|
||||
return res
|
||||
}, {})
|
||||
|
||||
return initialArticles
|
||||
}
|
||||
|
||||
function currentUser (state, action) {
|
||||
switch (action.type) {
|
||||
case USER_UPDATE:
|
||||
let user = action.data
|
||||
|
||||
return auth.user(user)
|
||||
default:
|
||||
if (state == null) return auth.user()
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function status (state, action) {
|
||||
switch (action.type) {
|
||||
case SWITCH_USER:
|
||||
state.userId = action.data
|
||||
state.mode = IDLE_MODE
|
||||
state.folderId = null
|
||||
return state
|
||||
case SWITCH_FOLDER:
|
||||
state.folderId = action.data
|
||||
state.mode = IDLE_MODE
|
||||
return state
|
||||
case SWITCH_MODE:
|
||||
state.mode = action.data
|
||||
if (state.mode === CREATE_MODE) state.articleKey = null
|
||||
return state
|
||||
case SWITCH_ARTICLE:
|
||||
state.articleKey = action.data
|
||||
state.mode = IDLE_MODE
|
||||
return state
|
||||
default:
|
||||
if (state == null) return initialStatus
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function genKey (id) {
|
||||
return 'team-' + id
|
||||
}
|
||||
|
||||
function articles (state, action) {
|
||||
switch (action.type) {
|
||||
case ARTICLE_REFRESH:
|
||||
{
|
||||
let { userId, articles } = action.data
|
||||
let teamKey = genKey(userId)
|
||||
localStorage.setItem(teamKey, JSON.stringify(articles))
|
||||
state[teamKey] = articles
|
||||
}
|
||||
|
||||
return state
|
||||
case ARTICLE_UPDATE:
|
||||
{
|
||||
let { userId, article } = action.data
|
||||
let teamKey = genKey(userId)
|
||||
let articles = JSON.parse(localStorage.getItem(teamKey))
|
||||
|
||||
let targetIndex = findIndex(articles, _article => article.key === _article.key)
|
||||
if (targetIndex < 0) articles.unshift(article)
|
||||
else articles.splice(targetIndex, 1, article)
|
||||
|
||||
localStorage.setItem(teamKey, JSON.stringify(articles))
|
||||
state[teamKey] = articles
|
||||
}
|
||||
return state
|
||||
case ARTICLE_DESTROY:
|
||||
{
|
||||
let { userId, articleKey } = action.data
|
||||
let teamKey = genKey(userId)
|
||||
let articles = JSON.parse(localStorage.getItem(teamKey))
|
||||
console.log(articles)
|
||||
console.log(articleKey)
|
||||
let targetIndex = findIndex(articles, _article => articleKey === _article.key)
|
||||
if (targetIndex >= 0) articles.splice(targetIndex, 1)
|
||||
|
||||
localStorage.setItem(teamKey, JSON.stringify(articles))
|
||||
state[teamKey] = articles
|
||||
}
|
||||
return state
|
||||
default:
|
||||
if (state == null) return getInitialArticles()
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
currentUser,
|
||||
status,
|
||||
articles
|
||||
})
|
||||
44
lib/socket.js
Normal file
44
lib/socket.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { API_URL } from '../config'
|
||||
import socketio from 'socket.io-client'
|
||||
import auth from './auth'
|
||||
import store from './store'
|
||||
import { updateArticle, destroyArticle } from './actions'
|
||||
|
||||
export const CONN = 'CONN'
|
||||
export const ALERT = 'ALERT'
|
||||
export const USER_UPDATE = 'USER_UPDATE'
|
||||
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
|
||||
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
|
||||
|
||||
let io = socketio(API_URL)
|
||||
|
||||
io.on(CONN, function (data) {
|
||||
console.log('connected', data)
|
||||
|
||||
let token = auth.token()
|
||||
if (token != null) {
|
||||
io.emit('JOIN', {token})
|
||||
}
|
||||
})
|
||||
|
||||
io.on(ALERT, function (data) {
|
||||
console.log(ALERT, data)
|
||||
})
|
||||
|
||||
io.on(USER_UPDATE, function (data) {
|
||||
console.log(USER_UPDATE, data)
|
||||
})
|
||||
|
||||
io.on(ARTICLE_UPDATE, function (data) {
|
||||
console.log(ARTICLE_UPDATE, data)
|
||||
let { userId, article } = data
|
||||
store.dispatch(updateArticle(userId, article))
|
||||
})
|
||||
|
||||
io.on(ARTICLE_DESTROY, function (data) {
|
||||
console.log(ARTICLE_DESTROY, data)
|
||||
let { userId, articleKey } = data
|
||||
store.dispatch(destroyArticle(userId, articleKey))
|
||||
})
|
||||
|
||||
export default io
|
||||
20
lib/store.js
Normal file
20
lib/store.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import reducer from './reducer'
|
||||
import { createStore } from 'redux'
|
||||
// Devtools
|
||||
import { compose } from 'redux'
|
||||
import { devTools, persistState } from 'redux-devtools'
|
||||
import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react'
|
||||
|
||||
let finalCreateStore = compose(devTools(), persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)))(createStore)
|
||||
let store = finalCreateStore(reducer)
|
||||
|
||||
export let devToolElement = (
|
||||
<DebugPanel top right bottom>
|
||||
<DevTools store={store} monitor={LogMonitor} visibleOnLoad={false}/>
|
||||
</DebugPanel>
|
||||
)
|
||||
|
||||
// let store = createStore(reducer)
|
||||
|
||||
export default store
|
||||
Reference in New Issue
Block a user