Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lessons 30-33 #1

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
dc640e8
login and signup forms
samcorcos Sep 14, 2016
ec0bcb9
login and signup forms
samcorcos Sep 14, 2016
1904335
login and signup forms
samcorcos Sep 14, 2016
94f7343
connect the api
samcorcos Sep 14, 2016
df01d5c
connect the api
samcorcos Sep 14, 2016
263f3d7
connect the api
samcorcos Sep 14, 2016
b38c4a7
connect the api
samcorcos Sep 14, 2016
4f0778d
connect the api
samcorcos Sep 14, 2016
48c1f6d
connect the api
samcorcos Sep 14, 2016
c5281b3
actions reducers store
samcorcos Sep 14, 2016
f0454e2
actions reducers store
samcorcos Sep 14, 2016
aa07b32
actions reducers store
samcorcos Sep 14, 2016
d626590
actions reducers store
samcorcos Sep 14, 2016
baa2669
actions reducers store
samcorcos Sep 14, 2016
2950d90
move logic to redux
samcorcos Sep 14, 2016
54a6052
move logic to redux
samcorcos Sep 14, 2016
50462d2
move logic to redux
samcorcos Sep 14, 2016
7cc5ea9
move logic to redux
samcorcos Sep 14, 2016
e567f03
move logic to redux
samcorcos Sep 14, 2016
df10d84
move logic to redux
samcorcos Sep 14, 2016
c02dbb5
move logic to redux
samcorcos Sep 14, 2016
3b0096a
check login status
samcorcos Sep 14, 2016
6f61b0f
check login status
samcorcos Sep 14, 2016
2af41ae
check login status
samcorcos Sep 14, 2016
64f7f2d
check login status
samcorcos Sep 14, 2016
4c8fe29
check login status
samcorcos Sep 14, 2016
6b8a00a
check login status
samcorcos Sep 14, 2016
834d2bc
check login status
samcorcos Sep 14, 2016
b38b315
check login status
samcorcos Sep 14, 2016
3b7ad7d
npm package for a react component part 1
samcorcos Sep 14, 2016
0578efa
npm package for a react component part 1
samcorcos Sep 14, 2016
a428d52
Connecting the Frontend to AdminChannel
gjaldon Sep 16, 2016
858b697
Admin receives chat messages and changes chat rooms.
gjaldon Sep 16, 2016
1ae6940
Admin can respond in chat.
gjaldon Sep 16, 2016
fa7ca11
Replace hardcoded values with config in webpack.
gjaldon Sep 16, 2016
641ea22
List active and inactive users.
gjaldon Sep 16, 2016
b4fdeee
Fix for seeing when anonymous user in same window is active
gjaldon Sep 20, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added app/components/App/README.md
Empty file.
19 changes: 19 additions & 0 deletions app/components/App/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react"
import { connect } from "react-redux"
import Actions from "../../redux/actions"

export class App extends React.Component {
componentDidMount() {
this.props.dispatch(Actions.userAuth())
}

render() {
return (
<div>
{this.props.children}
</div>
)
}
}

export default connect()(App)
32 changes: 32 additions & 0 deletions app/components/App/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'
import expect from 'expect'
import { shallow, mount } from 'enzyme'

import Actions from '../../redux/actions'

import { App } from './'

const props = {}

describe('<App />', () => {
it('should render', () => {
const renderedComponent = shallow(
<App {...props} />
)
expect(renderedComponent.is('div')).toEqual(true)
})
it('calls dispatch on componentDidMount', () => {
const spy = expect.createSpy()
const renderedComponent = mount(
<App dispatch={spy} />
)
expect(spy).toHaveBeenCalled()
})
it('calls dispatch with Actions.userAuth', () => {
const spy = expect.createSpy()
const renderedComponent = mount(
<App dispatch={spy} />
)
expect(spy).toHaveBeenCalledWith(Actions.userAuth())
})
})
Empty file added app/components/App/style.css
Empty file.
Empty file added app/components/Chat/README.md
Empty file.
138 changes: 138 additions & 0 deletions app/components/Chat/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React from "react"
import cssModules from "react-css-modules"
import { Socket, Presence } from "phoenix"
import { connect } from "react-redux"
import style from "./style.css"

import { default as Sidebar } from "../Sidebar"
import { default as ChatRoom } from "../ChatRoom"

export class Chat extends React.Component {
constructor(props) {
super(props)
this.state = {
presences: {},
messages: [],
currentRoom: null,
input: "",
lobbyList: [],
mountChildren: false
}

this.changeChatroom = this.changeChatroom.bind(this)
this.handleMessageSubmit = this.handleMessageSubmit.bind(this)
this.handleChange = this.handleChange.bind(this)
}

componentDidMount() {
const params = this.props.user
this.socket = new Socket(process.env.SOCKET_HOST, { params })
this.socket.connect()
this.configureAdminChannel()
}

componentWillUnmount() {
if (this.channel) this.channel.leave()
this.adminChannel.leave()
}

configureAdminChannel() {
this.adminChannel = this.socket.channel("admin:active_users")

this.adminChannel.on("presence_state", state => {
const presences = Presence.syncState(this.state.presences, state)
// added clearer logging to track presences
console.log('Presences after sync: ', presences)
this.setState({ presences })
})

this.adminChannel.on("presence_diff", state => {
const presences = Presence.syncDiff(this.state.presences, state)
// added clearer logging to track presences
console.log('Presences after diff: ', presences)
this.setState({ presences })
})

this.adminChannel.on("lobby_list", (user) => {
const userInLobbyList = this.state.lobbyList.find((lobbyUser) => user.id === lobbyUser.id)
if (!userInLobbyList) {
this.setState({ lobbyList: this.state.lobbyList.concat([user]) })
}
})

this.adminChannel.join()
.receive("ok", ({ id, lobby_list }) => {
// added clearer logging to track when a user joins a topic
console.log(`${id} succesfully joined the active_users topic.`)
this.setState({ lobbyList: lobby_list, mountChildren: true })
})
}

changeChatroom(room) {
this.channel = this.socket.channel(`room:${room}`)
this.setState({
messages: []
})
this.configureRoomChannel(room)
}

configureRoomChannel(room) {
this.channel.join()
.receive("ok", ({ messages }) => {
console.log(`Succesfully joined the ${room} chat room.`, messages)
this.setState({
messages,
currentRoom: room
})
})
.receive("error", () => { console.log(`Unable to join the ${room} chat room.`) })

this.channel.on("message", payload => {
this.setState({
messages: this.state.messages.concat([payload])
})
})
}

handleChange(e) {
this.setState({ input: e.target.value })
}

handleMessageSubmit(e) {
if (e.keyCode === 13 && this.state.currentRoom && this.state.input) {
this.channel.push("message", {
room: this.state.currentRoom,
body: this.state.input,
timestamp: new Date().getTime()
})
this.setState({ input: "" })
}
}

// We use a ChatRoom stateless functional component(mouthful!) to replace the
// code for rendering a chat room and its messages.
render() {
return (
<div>
<Sidebar
presences={this.state.presences}
lobbyList={this.state.lobbyList}
onRoomClick={this.changeChatroom} />
<ChatRoom
input={this.state.input}
handleChange={this.handleChange}
handleMessageSubmit={this.handleMessageSubmit}
currentRoom={this.state.currentRoom}
messages={this.state.messages} />
{ this.state.mountChildren ? this.props.children : null }
</div>
)
}
}


const mapStateToProps = state => ({
user: state.user
})

export default connect(mapStateToProps)(cssModules(Chat, style))
16 changes: 16 additions & 0 deletions app/components/Chat/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'
import expect from 'expect'
import { shallow } from 'enzyme'

import { Chat } from './'

const props = {}

describe('<Chat />', () => {
it('should render', () => {
const renderedComponent = shallow(
<Chat {...props} />
)
expect(renderedComponent.is('div')).toEqual(true)
})
})
Empty file added app/components/Chat/style.css
Empty file.
50 changes: 50 additions & 0 deletions app/components/ChatRoom/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from "react"
import cssModules from "react-css-modules"
import style from "./style.css"

// This used to be in the Chat component
const renderMessages = (props) => {
if (!props.currentRoom) {
return (
<div className={style.empty}>
No chat selected
</div>
)
}

return props.messages.map(({ body, id, user_id, anonymous_user_id }) => {
// Basic labels so admin knows who sent which messages
const from = user_id ? 'Me' : anonymous_user_id.substring(0, 10)
const msg = `${from}: ${body}`

return (
<div className={style.message} key={id}>
{ msg }
</div>
)
})
}

// This used to be in the Chat component
const renderInput = (props) => {
if (!props.currentRoom) { return null }
return (
<input
value={props.input}
onKeyDown={props.handleMessageSubmit}
onChange={props.handleChange}
className={style.input} />
)
}

export const ChatRoom = props => {
return (
<div className={style.chatWrapper}>
{ renderMessages(props) }
{ renderInput(props) }
</div>
)
}

export default cssModules(ChatRoom, style)

32 changes: 32 additions & 0 deletions app/components/ChatRoom/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.chatWrapper {
margin-left: 300px;
}

.input {
position: absolute;
bottom: 20px;
left: 20px;
width: calc(100% - 40px);
line-height: 40px;
font-size: 20px;
outline: none;
border: 1px solid #ccc;
border-radius: 3px;
padding-left: 10px;
color: #333;
}

.message {
padding-left: 20px;
line-height: 1.3;
padding-bottom: 10px;
}

.empty {
display: flex;
align-items: center;
justify-content: center;
height: 80%;
color: #ccc;
font-size: 2em;
}
63 changes: 60 additions & 3 deletions app/components/Home/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,74 @@
import React from "react"
import cssModules from "react-css-modules"
import { connect } from "react-redux"
import PhoenixChat from "phoenix-chat"
import style from "./style.css"

import { default as Signup } from "../Signup"
import { default as Login } from "../Login"
import { default as Chat } from "../Chat"

export class Home extends React.Component {
constructor(props) {
super(props)
this.state = {
formState: "login"
}
this.setFormState = this.setFormState.bind(this)
}

setFormState(formState) {
this.setState({ formState })
}

renderToggleContent() {
switch (this.state.formState) {
case "login":
return (
<div
className={style.changeLink}
onClick={() => this.setFormState("signup")}>
Need an account? Signup.
</div>
)
case "signup":
return (
<div
className={style.changeLink}
onClick={() => this.setFormState("login")}>
Have an account? Login.
</div>
)
default: return null
}
}

render() {
if (this.props.user.email) {
return (
<Chat>
<PhoenixChat />
</Chat>
)
}
return (
<div>
<Signup />
<div className={style.leader}>
<h1 className={style.title}>Phoenix Chat</h1>
{ this.state.formState === "signup" ? <Signup /> : null }
{ this.state.formState === "login" ? <Login /> : null }
{ this.renderToggleContent() }
<img
role="presentation"
className={style.circles}
src="https://s3.amazonaws.com/learnphoenix-static-assets/images/circles-full.png" />
<PhoenixChat />
</div>
)
}
}

export default cssModules(Home, style)
const mapStateToProps = state => ({
user: state.user
})

export default connect(mapStateToProps)(cssModules(Home, style))
Loading