diff --git a/src/components/LoginStatusPane.js b/src/components/LoginStatusPane.js
index a748aee..73b4450 100644
--- a/src/components/LoginStatusPane.js
+++ b/src/components/LoginStatusPane.js
@@ -10,97 +10,66 @@ import {
LOGIN_STATUS_FAILURE
} from "../constants/login";
-const ID = "login-status-pane";
-
class LoginStatusPane extends React.Component
{
constructor(props) {
super(props);
}
- componentDidMount() {
- const { status, loginActions } = this.props;
- if (status == LOGIN_STATUS_UNKNOWN) {
- loginActions.getStatus();
- }
- }
+ render() {
+ let statusText;
+ let motd;
+ let reason;
- render_unknown() {
- return (
-
- Login status: unknown
-
- );
- }
+ switch (this.props.status) {
+ case LOGIN_STATUS_UNKNOWN:
+ statusText = "unknown";
+ break;
- render_getting() {
- return (
-
- Login status: fetching...
-
- );
- }
+ case LOGIN_STATUS_GETTING:
+ statusText = "fetching";
+ break;
- render_pending() {
- return (
-
- Logging in as {this.props.username}...
-
- );
- }
+ case LOGIN_STATUS_PENDING:
+ statusText = `logging in as ${this.props.username}`;
+ break;
- render_success() {
- let motd_element;
- if (this.props.motd) {
- motd_element = (
-
- MOTD: {this.props.motd}
-
- );
+ case LOGIN_STATUS_SUCCESS:
+ statusText = `logged in as ${this.props.username}`;
+ motd = (
+
+ MOTD: {this.props.motd}
+
+ );
+ break;
+
+ case LOGIN_STATUS_FAILURE:
+ statusText = `failed to log in as ${this.props.username}`;
+ reason = (
+
+ Reason: {this.props.reason}
+
+ );
+ break;
}
- return (
-
- Logged in as {this.props.username}
- {motd_element}
-
- );
- }
- render_failure() {
return (
-
- Failed to log in as {this.props.username}
-
- Reason: {this.props.reason}
+
+
+ Login status: {statusText}
+ {motd}
+ {reason}
);
}
-
- render() {
- switch (this.props.status) {
- case LOGIN_STATUS_UNKNOWN:
- return this.render_unknown();
- case LOGIN_STATUS_GETTING:
- return this.render_getting();
- case LOGIN_STATUS_PENDING:
- return this.render_pending();
- case LOGIN_STATUS_SUCCESS:
- return this.render_success();
- case LOGIN_STATUS_FAILURE:
- return this.render_failure();
- }
- }
}
LoginStatusPane.propTypes = {
status: propTypeSymbol.isRequired,
username: PropTypes.string,
motd: PropTypes.string,
- reason: PropTypes.string,
- loginActions: PropTypes.shape({
- getStatus: PropTypes.func.isRequired
- }).isRequired
+ reason: PropTypes.string
};
export default LoginStatusPane;
diff --git a/src/components/Room.js b/src/components/Room.js
index db17a59..b30d3c4 100644
--- a/src/components/Room.js
+++ b/src/components/Room.js
@@ -1,23 +1,27 @@
import React, { PropTypes } from "react";
+import { Link } from "react-router";
-const Room = ({ isSelected, name, onClick, room }) => {
+const Room = ({ name, onClick, room }) => {
const classes = ["room"];
- if (isSelected) {
- classes.push("room-selected");
- }
if (room.membership == "Member") {
classes.push("room-joined");
}
+
+ const base64Name = btoa(encodeURIComponent(name));
+ const path = `/app/rooms/${base64Name}`;
+
return (
-
+
{name}
({room.user_count})
-
+
);
};
Room.propTypes = {
- isSelected: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
room: PropTypes.shape({
diff --git a/src/components/RoomChat.js b/src/components/RoomChat.js
index 63e3a0f..e062dcf 100644
--- a/src/components/RoomChat.js
+++ b/src/components/RoomChat.js
@@ -1,9 +1,13 @@
import React, { PropTypes } from "react";
+import { bindActionCreators } from "redux";
+import { connect } from "react-redux";
import ImmutablePropTypes from "react-immutable-proptypes";
-import RoomChatForm from "./RoomChatForm";
-import RoomChatHeader from "./RoomChatHeader";
-import RoomChatMessageList from "./RoomChatMessageList";
+import RoomActions from "../actions/RoomActions";
+
+import RoomChatForm from "../components/RoomChatForm";
+import RoomChatHeader from "../components/RoomChatHeader";
+import RoomChatMessageList from "../components/RoomChatMessageList";
const ID = "room-chat";
@@ -28,7 +32,7 @@ class RoomChat extends React.Component {
}
render() {
- const { login_user_name, room, roomActions } = this.props;
+ const { loginUserName, room, roomActions } = this.props;
if (!room) {
return (
@@ -66,7 +70,7 @@ class RoomChat extends React.Component {
{header}
{
+const RoomChatMessageList = ({ loginUserName, messages }) => {
// Append all messages in the chat room.
const children = [];
let i = 0;
for (const { user_name, message } of messages) {
- if (user_name == login_user_name) {
+ if (user_name == loginUserName) {
children.push(
{message}
@@ -27,7 +27,7 @@ const RoomChatMessageList = ({ login_user_name, messages }) => {
};
RoomChatMessageList.propTypes = {
- login_user_name: PropTypes.string.isRequired,
+ loginUserName: PropTypes.string.isRequired,
messages: ImmutablePropTypes.listOf(
PropTypes.shape({
user_name: PropTypes.string.isRequired,
diff --git a/src/components/RoomList.js b/src/components/RoomList.js
index 64b0970..45dadf0 100644
--- a/src/components/RoomList.js
+++ b/src/components/RoomList.js
@@ -28,7 +28,6 @@ class RoomList extends React.Component {
onClick={onClick}
name={room_name}
room={room_data}
- isSelected={selected == room_name}
/>
);
diff --git a/src/components/SolsticeApp.js b/src/components/SolsticeApp.js
index b23378d..f0d2334 100644
--- a/src/components/SolsticeApp.js
+++ b/src/components/SolsticeApp.js
@@ -1,51 +1,21 @@
import React, {PropTypes} from "react";
-import { STATE_OPEN } from "../constants/socket";
-import { LOGIN_STATUS_SUCCESS } from "../constants/login";
-
-import ConnectForm from "./ConnectForm";
import Header from "./Header";
-import LoginStatusPane from "./LoginStatusPane";
import Footer from "../containers/Footer";
-import RoomsPane from "../containers/RoomsPane";
-
-const ID = "solstice-app";
-
-const SolsticeApp = (props) => {
- const { actions, login, socket } = props;
- if (socket.state !== STATE_OPEN ) {
- return (
-
-
-
- );
- }
- if (login.status !== LOGIN_STATUS_SUCCESS) {
- return (
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
- );
-};
+const SolsticeApp = ({ children }) => (
+
+
+
+ {children}
+
+
+
+);
SolsticeApp.propTypes = {
- actions: PropTypes.object.isRequired,
- login: PropTypes.object.isRequired,
- socket: PropTypes.object.isRequired
+ children: PropTypes.element
};
export default SolsticeApp;
diff --git a/src/containers/App.js b/src/containers/App.js
deleted file mode 100644
index d8691d2..0000000
--- a/src/containers/App.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// This file bootstraps the app with the boilerplate necessary
-// to support hot reloading in Redux
-import React, {PropTypes} from "react";
-import { bindActionCreators } from "redux";
-import { connect } from "react-redux";
-
-import LoginActions from "../actions/LoginActions";
-import RoomActions from "../actions/RoomActions";
-import SocketActions from "../actions/SocketActions";
-import SocketHandlerActions from "../actions/SocketHandlerActions";
-
-import SolsticeApp from "../components/SolsticeApp";
-
-const App = (props) => ( );
-
-App.propTypes = {
- actions: PropTypes.object.isRequired,
- login: PropTypes.object.isRequired,
- socket: PropTypes.object.isRequired
-};
-
-const mapStateToProps = ({ socket, login }) => ({ socket, login });
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: {
- login: bindActionCreators(LoginActions, dispatch),
- room: bindActionCreators(RoomActions, dispatch),
- socket: bindActionCreators(SocketActions, dispatch),
- socketHandlers: bindActionCreators(SocketHandlerActions, dispatch)
- }
- };
-}
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(App);
diff --git a/src/containers/ConnectPage.js b/src/containers/ConnectPage.js
new file mode 100644
index 0000000..3898dc0
--- /dev/null
+++ b/src/containers/ConnectPage.js
@@ -0,0 +1,95 @@
+import React, { PropTypes } from "react";
+import { connect } from "react-redux";
+import { bindActionCreators } from "redux";
+import { hashHistory, withRouter } from "react-router";
+
+import LoginActions from "../actions/LoginActions";
+import SocketActions from "../actions/SocketActions";
+import SocketHandlerActions from "../actions/SocketHandlerActions";
+
+import { STATE_OPEN } from "../constants/socket";
+import {
+ LOGIN_STATUS_SUCCESS,
+ LOGIN_STATUS_UNKNOWN
+} from "../constants/login";
+
+import ConnectForm from "../components/ConnectForm";
+import LoginStatusPane from "../components/LoginStatusPane";
+
+class ConnectPage extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ componentDidMount() {
+ this.getLoginStatusOrRedirect(this.props);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.getLoginStatusOrRedirect(nextProps);
+ }
+
+ getLoginStatusOrRedirect(props) {
+ const { actions, login, router, socket } = props;
+ if (socket.state === STATE_OPEN)
+ {
+ switch (login.status) {
+ case LOGIN_STATUS_UNKNOWN:
+ actions.login.getStatus();
+ break;
+
+ case LOGIN_STATUS_SUCCESS:
+ router.push("/app/rooms");
+ break;
+ }
+ }
+ }
+
+ render() {
+ const { actions, login, socket } = this.props;
+
+ let loginStatusPane;
+
+ if (socket.state === STATE_OPEN &&
+ login.status === LOGIN_STATUS_UNKNOWN)
+ {
+ loginStatusPane = (
+
+ );
+ }
+
+ return (
+
+
+ {loginStatusPane}
+
+ );
+ }
+}
+
+ConnectPage.propTypes = {
+ actions: PropTypes.shape({
+ login: PropTypes.object.isRequired,
+ socket: PropTypes.object.isRequired,
+ socketHandlers: PropTypes.object.isRequired
+ }).isRequired,
+
+ login: PropTypes.object.isRequired,
+ router: PropTypes.object.isRequired,
+ socket: PropTypes.object.isRequired
+};
+
+const mapStateToProps = ({ login, socket }) => ({ login, socket });
+
+const mapDispatchToProps = (dispatch) => ({
+ actions: {
+ login: bindActionCreators(LoginActions, dispatch),
+ socket: bindActionCreators(SocketActions, dispatch),
+ socketHandlers: bindActionCreators(SocketHandlerActions, dispatch)
+ }
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(withRouter(ConnectPage));
diff --git a/src/containers/Footer.js b/src/containers/Footer.js
index 89da9fa..bfbeaaf 100644
--- a/src/containers/Footer.js
+++ b/src/containers/Footer.js
@@ -4,17 +4,16 @@ import { connect } from "react-redux";
import LoginStatusPane from "../components/LoginStatusPane";
import SocketStatusPane from "../components/SocketStatusPane";
-const Footer = ({ actions, login, socket }) => {
+const Footer = ({ login, socket }) => {
return (
);
};
Footer.propTypes = {
- actions: PropTypes.object.isRequired,
login: PropTypes.object.isRequired,
socket: PropTypes.object.isRequired
};
diff --git a/src/containers/RoomsPane.js b/src/containers/RoomsPane.js
index fa2eee9..5debe7d 100644
--- a/src/containers/RoomsPane.js
+++ b/src/containers/RoomsPane.js
@@ -3,6 +3,8 @@ import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import ImmutablePropTypes from "react-immutable-proptypes";
+import RoomActions from "../actions/RoomActions";
+
import RoomChat from "../components/RoomChat";
import RoomList from "../components/RoomList";
import RoomUserList from "../components/RoomUserList";
@@ -13,19 +15,28 @@ class RoomsPane extends React.Component {
}
render() {
- const { login_user_name, rooms, roomActions, selected } = this.props;
+ const { loginUserName, params, rooms, roomActions } = this.props;
- let room;
- if (selected) {
- room = {
- ...rooms.get(selected),
- name: selected
- };
- }
+ let roomName;
+ let roomChat;
- let roomUserList;
- if (room && room.showUsers) {
- roomUserList = ;
+ if (params && params.roomName) {
+ roomName = decodeURIComponent(atob(params.roomName));
+
+ const { membership, messages, showUsers } = rooms.get(roomName);
+ const room = {
+ name: roomName,
+ membership,
+ messages,
+ showUsers
+ };
+ roomChat = (
+
+ );
}
return (
@@ -33,15 +44,10 @@ class RoomsPane extends React.Component {
-
- {roomUserList}
+ {roomChat}
);
@@ -49,18 +55,24 @@ class RoomsPane extends React.Component {
}
RoomsPane.propTypes = {
- login_user_name: PropTypes.string,
- rooms: ImmutablePropTypes.orderedMap.isRequired,
- roomActions: PropTypes.object.isRequired,
- selected: PropTypes.string
+ loginUserName: PropTypes.string.isRequired,
+ params: PropTypes.shape({
+ roomName: PropTypes.string
+ }),
+ rooms: ImmutablePropTypes.orderedMap.isRequired,
+ roomActions: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
- login_user_name: state.login.username,
rooms: state.rooms.rooms,
- selected: state.rooms.selected
+ loginUserName: state.login.username
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ roomActions: bindActionCreators(RoomActions, dispatch)
});
export default connect(
mapStateToProps,
+ mapDispatchToProps
)(RoomsPane);
diff --git a/src/createRoutes.js b/src/createRoutes.js
new file mode 100644
index 0000000..17e6a47
--- /dev/null
+++ b/src/createRoutes.js
@@ -0,0 +1,33 @@
+import React, { PropTypes } from 'react';
+import { Route, IndexRoute } from 'react-router';
+
+import ConnectPage from "./containers/ConnectPage";
+import RoomsPane from "./containers/RoomsPane";
+
+import SolsticeApp from "./components/SolsticeApp";
+
+import { STATE_OPEN } from "./constants/socket";
+import { LOGIN_STATUS_SUCCESS } from "./constants/login";
+
+const createRoutes = (store) => {
+ const requireLoggedIn = (nextState, replaceState) => {
+ let { socket, login } = store.getState();
+ if (socket.state !== STATE_OPEN ||
+ login.status !== LOGIN_STATUS_SUCCESS)
+ {
+ replaceState({}, "/");
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default createRoutes;
diff --git a/src/index.js b/src/index.js
index bc95648..b48626d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,14 +1,20 @@
import React from 'react';
import {render} from 'react-dom';
import { Provider } from 'react-redux';
-import App from './containers/App';
+import { Router, hashHistory } from "react-router";
+
import configureStore from './store/configureStore';
+import createRoutes from "./createRoutes";
+
import './styles/styles.scss'; //Yep, that's right. You can import SASS/CSS files too! Webpack will run the associated loader and plug this into the page.
const store = configureStore();
+const routes = createRoutes(store);
render(
-
+
+ {routes}
+
, document.getElementById('app')
);
diff --git a/src/reducers/rooms.js b/src/reducers/rooms.js
index b017a6b..26ee3e7 100644
--- a/src/reducers/rooms.js
+++ b/src/reducers/rooms.js
@@ -4,46 +4,43 @@ import {
ROOM_JOIN,
ROOM_LEAVE,
ROOM_MESSAGE,
- ROOM_SELECT,
ROOM_SHOW_USERS,
ROOM_HIDE_USERS,
SOCKET_RECEIVE_MESSAGE
} from "../constants/ActionTypes";
const initialState = {
- rooms: Immutable.OrderedMap(),
- selected: null
+ rooms: Immutable.OrderedMap()
};
-const reduceRoomList = (old_rooms, room_list) => {
+const reduceRoomList = (oldRoomMap, roomList) => {
// First sort the room list by room name
- room_list.sort((room_pair_1, room_pair_2) => {
- const name_1 = room_pair_1[0];
- const name_2 = room_pair_2[0];
- if (name_1 < name_2) {
+ roomList.sort(([ roomName1 ], [ roomName2 ]) => {
+ if (roomName1 < roomName2) {
return -1;
- } else if (name_1 > name_2) {
+ } else if (roomName1 > roomName2) {
return 1;
}
return 0;
});
// Then build the new rooms map
- let new_rooms = Immutable.OrderedMap();
- for (const [ room_name, room_data ] of room_list) {
+ let newRoomMap = Immutable.OrderedMap();
+
+ for (const [ roomName, newRoomData ] of roomList) {
// Transform room_data.messages to an immutable list.
- room_data.messages = Immutable.List(room_data.messages);
+ newRoomData.messages = Immutable.List(newRoomData.messages);
// Get the old room data.
- const old_data = old_rooms.get(room_name);
+ const oldRoomData = oldRoomMap.get(roomName);
// Merge the old data and the new data, overwriting with new data if
// conflicting.
- const new_data = {
- ...old_data,
- ...room_data
+ const mergedRoomData = {
+ ...oldRoomData,
+ ...newRoomData
};
- new_rooms = new_rooms.set(room_name, new_data);
+ newRoomMap = newRoomMap.set(roomName, mergedRoomData);
}
- return new_rooms;
+ return newRoomMap;
};
const reduceReceiveMessage = (rooms, { variant, data }) => {
@@ -103,12 +100,6 @@ export default (state = initialState, action) => {
case ROOM_MESSAGE:
return state;
- case ROOM_SELECT:
- return {
- ...state,
- selected: payload
- };
-
case ROOM_JOIN:
{
const rooms = state.rooms.set(payload, {