mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-03-22 19:05:56 -04:00
Merge pull request #497 from mewtaylor/feature/conference-schedule
Feature: conference schedule page
This commit is contained in:
commit
cf2165d295
24 changed files with 773 additions and 132 deletions
bin
src
static/svgs/conference/schedule
|
@ -55,6 +55,7 @@ var getViewPaths = function (routes) {
|
|||
*/
|
||||
var pathsToCondition = function (paths) {
|
||||
return 'req.url~"' + paths.reduce(function (conditionString, pattern) {
|
||||
pattern = pattern.replace(/(:[^/]+)\//gi, '.+?/');
|
||||
return conditionString + (conditionString ? '|' : '') + pattern;
|
||||
}, '') + '"';
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ var connect = require('react-redux').connect;
|
|||
var omit = require('lodash.omit');
|
||||
var React = require('react');
|
||||
|
||||
var actions = require('../../redux/actions.js');
|
||||
var sessionActions = require('../../redux/session.js');
|
||||
|
||||
var Modal = require('../modal/modal.jsx');
|
||||
var Registration = require('../registration/registration.jsx');
|
||||
|
@ -48,7 +48,7 @@ var Intro = React.createClass({
|
|||
this.setState({'registrationOpen': false});
|
||||
},
|
||||
completeRegistration: function () {
|
||||
this.props.dispatch(actions.refreshSession());
|
||||
this.props.dispatch(sessionActions.refreshSession());
|
||||
this.closeRegistration();
|
||||
},
|
||||
render: function () {
|
||||
|
|
|
@ -24,6 +24,9 @@ var Navigation = React.createClass({
|
|||
<li className="link plan">
|
||||
<a href="/conference/plan">Plan Your Visit</a>
|
||||
</li>
|
||||
<li className="link schedule">
|
||||
<a href="/conference/schedule">Schedule</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -5,7 +5,7 @@ var ReactIntl = require('react-intl');
|
|||
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||
var injectIntl = ReactIntl.injectIntl;
|
||||
|
||||
var actions = require('../../../redux/actions.js');
|
||||
var sessionActions = require('../../../redux/session.js');
|
||||
|
||||
var Api = require('../../../mixins/api.jsx');
|
||||
var Avatar = require('../../avatar/avatar.jsx');
|
||||
|
@ -132,7 +132,7 @@ var Navigation = React.createClass({
|
|||
this.showCanceledDeletion();
|
||||
}
|
||||
}.bind(this));
|
||||
this.props.dispatch(actions.refreshSession());
|
||||
this.props.dispatch(sessionActions.refreshSession());
|
||||
}
|
||||
}
|
||||
// JS error already logged by api mixin
|
||||
|
@ -149,7 +149,7 @@ var Navigation = React.createClass({
|
|||
}, function (err) {
|
||||
if (err) log.error(err);
|
||||
this.closeLogin();
|
||||
this.props.dispatch(actions.refreshSession());
|
||||
this.props.dispatch(sessionActions.refreshSession());
|
||||
}.bind(this));
|
||||
},
|
||||
handleAccountNavClick: function (e) {
|
||||
|
@ -169,7 +169,7 @@ var Navigation = React.createClass({
|
|||
this.setState({'registrationOpen': false});
|
||||
},
|
||||
completeRegistration: function () {
|
||||
this.props.dispatch(actions.refreshSession());
|
||||
this.props.dispatch(sessionActions.refreshSession());
|
||||
this.closeRegistration();
|
||||
},
|
||||
render: function () {
|
||||
|
|
|
@ -6,7 +6,7 @@ var ReactDOM = require('react-dom');
|
|||
var StoreProvider = require('react-redux').Provider;
|
||||
|
||||
var IntlProvider = require('./intl.jsx').IntlProvider;
|
||||
var actions = require('../redux/actions.js');
|
||||
var sessionActions = require('../redux/session.js');
|
||||
var reducer = require('../redux/reducer.js');
|
||||
|
||||
require('../main.scss');
|
||||
|
@ -43,7 +43,7 @@ var render = function (jsx, element) {
|
|||
);
|
||||
|
||||
// Get initial session
|
||||
store.dispatch(actions.refreshSession());
|
||||
store.dispatch(sessionActions.refreshSession());
|
||||
};
|
||||
|
||||
module.exports = render;
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var api = require('../mixins/api.jsx').api;
|
||||
var jar = require('../lib/jar.js');
|
||||
|
||||
var Types = keyMirror({
|
||||
SET_SESSION: null,
|
||||
SET_SESSION_ERROR: null,
|
||||
SET_TOKEN: null,
|
||||
SET_TOKEN_ERROR: null,
|
||||
USE_TOKEN: null
|
||||
});
|
||||
|
||||
var Actions = {
|
||||
types: Types,
|
||||
|
||||
setSessionError: function (error) {
|
||||
return {
|
||||
type: Types.SET_SESSION_ERROR,
|
||||
error: error
|
||||
};
|
||||
},
|
||||
|
||||
setSession: function (session) {
|
||||
return {
|
||||
type: Types.SET_SESSION,
|
||||
session: session
|
||||
};
|
||||
},
|
||||
|
||||
refreshSession: function () {
|
||||
return function (dispatch) {
|
||||
api({
|
||||
host: '',
|
||||
uri: '/session/'
|
||||
}, function (err, body) {
|
||||
if (err) return dispatch(Actions.setSessionError(err));
|
||||
|
||||
if (typeof body !== 'undefined') {
|
||||
if (body.banned) {
|
||||
return window.location = body.redirectUrl;
|
||||
} else {
|
||||
dispatch(Actions.getToken());
|
||||
dispatch(Actions.setSession(body));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
getToken: function () {
|
||||
return function (dispatch) {
|
||||
jar.get('scratchsessionsid', function (err, value) {
|
||||
if (err) return dispatch(Actions.setTokenError(err));
|
||||
jar.unsign(value, function (err, contents) {
|
||||
if (err) return dispatch(Actions.setTokenError(err));
|
||||
try {
|
||||
var sessionData = JSON.parse(contents);
|
||||
} catch (err) {
|
||||
return dispatch(Actions.setTokenError(err));
|
||||
}
|
||||
return dispatch(Actions.setToken(sessionData.token));
|
||||
});
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
setToken: function (token) {
|
||||
return {
|
||||
type: Types.SET_TOKEN,
|
||||
token: token
|
||||
};
|
||||
},
|
||||
|
||||
setTokenError: function (error) {
|
||||
return {
|
||||
type: Types.SET_SESSION_ERROR,
|
||||
error: error
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Actions;
|
83
src/redux/conference-details.js
Normal file
83
src/redux/conference-details.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var api = require('../mixins/api.jsx').api;
|
||||
|
||||
var Types = keyMirror({
|
||||
SET_DETAILS: null,
|
||||
SET_DETAILS_FETCHING: null,
|
||||
SET_DETAILS_ERROR: null
|
||||
});
|
||||
|
||||
module.exports.detailsReducer = function (state, action) {
|
||||
if (typeof state === 'undefined') {
|
||||
state = {};
|
||||
}
|
||||
switch (action.type) {
|
||||
case Types.SET_DETAILS:
|
||||
return action.details;
|
||||
case Types.SET_DETAILS_FETCHING:
|
||||
return {fetching: action.fetching};
|
||||
case Types.SET_DETAILS_ERROR:
|
||||
return {error: action.error};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.setDetailsError = function (error) {
|
||||
return {
|
||||
type: Types.SET_DETAILS_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.setDetails = function (details) {
|
||||
return {
|
||||
type: Types.SET_DETAILS,
|
||||
details: details
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.setDetailsFetching = function () {
|
||||
return {
|
||||
type: Types.SET_DETAILS_FETCHING,
|
||||
fetching: true
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.startGetDetails = function (id) {
|
||||
return function (dispatch) {
|
||||
dispatch(module.exports.setDetailsFetching());
|
||||
dispatch(module.exports.getDetails(id));
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.getDetails = function (id) {
|
||||
return function (dispatch) {
|
||||
api({
|
||||
uri: '/conference/' + id + '/details'
|
||||
}, function (err, body) {
|
||||
if (err) {
|
||||
dispatch(module.exports.setDetailsError(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof body !== 'undefined') {
|
||||
var columns = body.columns;
|
||||
if (body.rows) {
|
||||
var details = body.rows[0];
|
||||
var detailsObject = details.reduce(function (prev, cur, index) {
|
||||
prev[columns[index]] = cur;
|
||||
return prev;
|
||||
}, {});
|
||||
dispatch(module.exports.setDetails(detailsObject));
|
||||
} else {
|
||||
dispatch(module.exports.setDetailsError('Not Found'));
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
dispatch(module.exports.setDetailsError('An unexpected error occurred'));
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
138
src/redux/conference-schedule.js
Normal file
138
src/redux/conference-schedule.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var api = require('../mixins/api.jsx').api;
|
||||
|
||||
var Types = keyMirror({
|
||||
SET_SCHEDULE: null,
|
||||
SET_SCHEDULE_FETCHING: null,
|
||||
SET_SCHEDULE_ERROR: null
|
||||
});
|
||||
|
||||
module.exports.scheduleReducer = function (state, action) {
|
||||
if (typeof state === 'undefined') {
|
||||
state = {
|
||||
timeSlots: [],
|
||||
day: ''
|
||||
};
|
||||
}
|
||||
switch (action.type) {
|
||||
case Types.SET_SCHEDULE:
|
||||
return action.schedule;
|
||||
case Types.SET_SCHEDULE_FETCHING:
|
||||
return state;
|
||||
case Types.SET_SCHEDULE_ERROR:
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.setSchedule = function (schedule) {
|
||||
return {
|
||||
type: Types.SET_SCHEDULE,
|
||||
schedule: schedule
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.setScheduleFetching = function () {
|
||||
return {
|
||||
type: Types.SET_SCHEDULE_FETCHING,
|
||||
fetching: true
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.setScheduleError = function (error) {
|
||||
return {
|
||||
type: Types.SET_SCHEDULE_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.startGetSchedule = function (day) {
|
||||
return function (dispatch) {
|
||||
dispatch(module.exports.setScheduleFetching());
|
||||
dispatch(module.exports.getDaySchedule(day));
|
||||
};
|
||||
};
|
||||
|
||||
// group periods of time by start time
|
||||
module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
|
||||
var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false;
|
||||
var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false;
|
||||
var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1));
|
||||
var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1));
|
||||
|
||||
if (timeSlot1Time < timeSlot2Time) {
|
||||
if (timeSlot2Am && !timeSlot1Am) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (timeSlot1Am && !timeSlot2Am) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the schedule for the given day from the api
|
||||
* @param {String} day Day of the conference (Thursday, Friday or Satrurday)
|
||||
*
|
||||
* @return {Object} Schedule for the day, broken into chunks
|
||||
*/
|
||||
module.exports.getDaySchedule = function (day) {
|
||||
return function (dispatch) {
|
||||
api({
|
||||
uri: '/conference/schedule/' + day
|
||||
}, function (err, body) {
|
||||
if (err) {
|
||||
dispatch(module.exports.setScheduleError(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof body !== 'undefined') {
|
||||
var columns = body.columns;
|
||||
var rows = body.rows || [];
|
||||
// Group events by the time period in which they occur (for presentation)
|
||||
var scheduleByTimeSlot = rows.reduce(function (prev, cur) {
|
||||
var cleanedRow = {};
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
if (cur[i].length > 0) {
|
||||
cleanedRow[columns[i]] = cur[i];
|
||||
}
|
||||
}
|
||||
cleanedRow['uri'] = '/conference/' + cleanedRow.rowid + '/details';
|
||||
var timeSlot = cleanedRow.Chunk;
|
||||
if (typeof prev.timeSlots[timeSlot] === 'undefined') {
|
||||
prev.timeSlots[timeSlot] = [cleanedRow];
|
||||
prev.info.push({
|
||||
name: timeSlot,
|
||||
time: cleanedRow.Start
|
||||
});
|
||||
} else {
|
||||
prev.timeSlots[timeSlot].push(cleanedRow);
|
||||
}
|
||||
return prev;
|
||||
}, {timeSlots: [], info: []});
|
||||
|
||||
scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
|
||||
var schedule = scheduleByTimeSlot.info.map(function (timeSlot) {
|
||||
return {
|
||||
info: timeSlot,
|
||||
items: scheduleByTimeSlot.timeSlots[timeSlot.name]
|
||||
};
|
||||
});
|
||||
dispatch(module.exports.setSchedule({
|
||||
timeSlots: schedule,
|
||||
day: day
|
||||
}));
|
||||
return;
|
||||
} else {
|
||||
dispatch(module.exports.setScheduleError('An unexpected error occurred'));
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
|
@ -1,43 +1,15 @@
|
|||
var combineReducers = require('redux').combineReducers;
|
||||
|
||||
var actionTypes = require('./actions.js').types;
|
||||
|
||||
|
||||
var sessionReducer = function (state, action) {
|
||||
// Reducer for handling changes to session state
|
||||
if (typeof state === 'undefined') {
|
||||
state = {};
|
||||
}
|
||||
switch (action.type) {
|
||||
case actionTypes.SET_SESSION:
|
||||
return action.session;
|
||||
case actionTypes.SET_SESSION_ERROR:
|
||||
// TODO: do something with action.error
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
var tokenReducer = function (state, action) {
|
||||
// Reducer for updating the api token
|
||||
if (typeof state === 'undefined') {
|
||||
state = '';
|
||||
}
|
||||
switch (action.type) {
|
||||
case actionTypes.SET_TOKEN:
|
||||
return action.token;
|
||||
case actionTypes.SET_TOKEN_ERROR:
|
||||
// TODO: do something with the error
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
var scheduleReducer = require('./conference-schedule.js').scheduleReducer;
|
||||
var detailsReducer = require('./conference-details.js').detailsReducer;
|
||||
var sessionReducer = require('./session.js').sessionReducer;
|
||||
var tokenReducer = require('./token.js').tokenReducer;
|
||||
|
||||
var appReducer = combineReducers({
|
||||
session: sessionReducer,
|
||||
token: tokenReducer
|
||||
token: tokenReducer,
|
||||
conferenceSchedule: scheduleReducer,
|
||||
conferenceDetails: detailsReducer
|
||||
});
|
||||
|
||||
module.exports = appReducer;
|
||||
|
|
60
src/redux/session.js
Normal file
60
src/redux/session.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
var keyMirror = require('keymirror');
|
||||
|
||||
var api = require('../mixins/api.jsx').api;
|
||||
var tokenActions = require('./token.js');
|
||||
|
||||
var Types = keyMirror({
|
||||
SET_SESSION: null,
|
||||
SET_SESSION_ERROR: null
|
||||
});
|
||||
|
||||
module.exports.sessionReducer = function (state, action) {
|
||||
// Reducer for handling changes to session state
|
||||
if (typeof state === 'undefined') {
|
||||
state = {};
|
||||
}
|
||||
switch (action.type) {
|
||||
case Types.SET_SESSION:
|
||||
return action.session;
|
||||
case Types.SET_SESSION_ERROR:
|
||||
// TODO: do something with action.error
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.setSessionError = function (error) {
|
||||
return {
|
||||
type: Types.SET_SESSION_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.setSession = function (session) {
|
||||
return {
|
||||
type: Types.SET_SESSION,
|
||||
session: session
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.refreshSession = function () {
|
||||
return function (dispatch) {
|
||||
api({
|
||||
host: '',
|
||||
uri: '/session/'
|
||||
}, function (err, body) {
|
||||
if (err) return dispatch(module.exports.setSessionError(err));
|
||||
|
||||
if (typeof body !== 'undefined') {
|
||||
if (body.banned) {
|
||||
return window.location = body.url;
|
||||
} else {
|
||||
dispatch(tokenActions.getToken());
|
||||
dispatch(module.exports.setSession(body));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
55
src/redux/token.js
Normal file
55
src/redux/token.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var jar = require('../lib/jar.js');
|
||||
|
||||
var Types = keyMirror({
|
||||
SET_TOKEN: null,
|
||||
SET_TOKEN_ERROR: null,
|
||||
USE_TOKEN: null
|
||||
});
|
||||
|
||||
module.exports.tokenReducer = function (state, action) {
|
||||
// Reducer for updating the api token
|
||||
if (typeof state === 'undefined') {
|
||||
state = '';
|
||||
}
|
||||
switch (action.type) {
|
||||
case Types.SET_TOKEN:
|
||||
return action.token;
|
||||
case Types.SET_TOKEN_ERROR:
|
||||
// TODO: do something with the error
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.getToken = function () {
|
||||
return function (dispatch) {
|
||||
jar.get('scratchsessionsid', function (err, value) {
|
||||
if (err) return dispatch(module.exports.setTokenError(err));
|
||||
jar.unsign(value, function (err, contents) {
|
||||
if (err) return dispatch(module.exports.setTokenError(err));
|
||||
try {
|
||||
var sessionData = JSON.parse(contents);
|
||||
} catch (err) {
|
||||
return dispatch(module.exports.setTokenError(err));
|
||||
}
|
||||
return dispatch(module.exports.setToken(sessionData.token));
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.setToken = function (token) {
|
||||
return {
|
||||
type: Types.SET_TOKEN,
|
||||
token: token
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.setTokenError = function (error) {
|
||||
return {
|
||||
type: Types.SET_TOKEN_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
|
@ -80,6 +80,20 @@
|
|||
"title": "What to Expect",
|
||||
"viewportWidth": "device-width"
|
||||
},
|
||||
{
|
||||
"name": "conference-schedule",
|
||||
"pattern": "^/conference/schedule/?$",
|
||||
"view": "conference/schedule/schedule",
|
||||
"title": "Conference Schedule",
|
||||
"viewportWidth": "device-width"
|
||||
},
|
||||
{
|
||||
"name": "conference-details",
|
||||
"pattern": "^/conference/:id/details/?$",
|
||||
"view": "conference/details/details",
|
||||
"title": "Event Details",
|
||||
"viewportWidth": "device-width"
|
||||
},
|
||||
{
|
||||
"name": "donate",
|
||||
"pattern": "^/info/donate/?",
|
||||
|
|
90
src/views/conference/details/details.jsx
Normal file
90
src/views/conference/details/details.jsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
var classNames = require('classnames');
|
||||
var connect = require('react-redux').connect;
|
||||
var React = require('react');
|
||||
var render = require('../../../lib/render.jsx');
|
||||
|
||||
var detailsActions = require('../../../redux/conference-details.js');
|
||||
|
||||
var Page = require('../../../components/page/conference/page.jsx');
|
||||
|
||||
require('./details.scss');
|
||||
|
||||
var ConferenceDetails = React.createClass({
|
||||
type: 'ConferenceDetails',
|
||||
propTypes: {
|
||||
detailsId: React.PropTypes.number,
|
||||
conferenceDetails: React.PropTypes.object
|
||||
},
|
||||
componentDidMount: function () {
|
||||
var pathname = window.location.pathname.toLowerCase();
|
||||
if (pathname[pathname.length - 1] === '/') {
|
||||
pathname = pathname.substring(0, pathname.length - 1);
|
||||
}
|
||||
var path = pathname.split('/');
|
||||
var detailsId = path[path.length - 2];
|
||||
this.props.dispatch(detailsActions.startGetDetails(detailsId));
|
||||
},
|
||||
render: function () {
|
||||
var backUri = '/conference/schedule';
|
||||
if (!this.props.conferenceDetails.error && !this.props.conferenceDetails.fetching) {
|
||||
backUri = backUri + '#' + this.props.conferenceDetails.Day;
|
||||
}
|
||||
var classes = classNames({
|
||||
'inner': true,
|
||||
'details': true,
|
||||
'fetching': this.props.conferenceDetails.fetching
|
||||
});
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="back">
|
||||
<a href={backUri}>
|
||||
← Back to Full Schedule
|
||||
</a>
|
||||
</div>
|
||||
{this.props.conferenceDetails.error ? [
|
||||
<h2>Agenda Item Not Found</h2>
|
||||
] : [
|
||||
<h2>{this.props.conferenceDetails.Title}</h2>,
|
||||
<ul className="logistics">
|
||||
<li>
|
||||
<img src="/svgs/conference/schedule/presenter-icon.svg" alt="presenter icon" />
|
||||
{this.props.conferenceDetails.Presenter}
|
||||
</li>
|
||||
<li>
|
||||
<img src="/svgs/conference/schedule/time-icon.svg" alt="time icon" />
|
||||
{this.props.conferenceDetails.Start} – {this.props.conferenceDetails.End}
|
||||
</li>
|
||||
<li>
|
||||
<img src="/svgs/conference/schedule/event-icon.svg" alt="event icon" />
|
||||
{this.props.conferenceDetails.Type}
|
||||
</li>
|
||||
<li>
|
||||
<img src="/svgs/conference/schedule/location-icon.svg" alt="location icon" />
|
||||
{this.props.conferenceDetails.Location}
|
||||
</li>
|
||||
</ul>,
|
||||
<div className="description">
|
||||
{this.props.conferenceDetails.Description}
|
||||
</div>,
|
||||
<div className="back">
|
||||
{this.props.conferenceDetails.fetching ? [] : [
|
||||
<a href={backUri}>
|
||||
← Back to Full Schedule
|
||||
</a>
|
||||
]}
|
||||
</div>
|
||||
]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var mapStateToProps = function (state) {
|
||||
return {
|
||||
conferenceDetails: state.conferenceDetails
|
||||
};
|
||||
};
|
||||
|
||||
var ConnectedDetails = connect(mapStateToProps)(ConferenceDetails);
|
||||
|
||||
render(<Page><ConnectedDetails /></Page>, document.getElementById('app'));
|
43
src/views/conference/details/details.scss
Normal file
43
src/views/conference/details/details.scss
Normal file
|
@ -0,0 +1,43 @@
|
|||
@import "../../../frameless";
|
||||
|
||||
#view {
|
||||
@media only screen and (max-width: $desktop - 1) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
&.inner {
|
||||
margin-top: 2rem;
|
||||
|
||||
&.fetching {
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
|
||||
.back {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
&.logistics {
|
||||
margin: .25rem 0 2.5rem;
|
||||
padding-left: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: .25rem 0;
|
||||
|
||||
img {
|
||||
margin-right: .5rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
|
@ -231,7 +231,7 @@ var ConferenceExpectations = React.createClass({
|
|||
<tr>
|
||||
<td>
|
||||
<b>4:30p</b>
|
||||
<p>Poster Session</p>
|
||||
<p>Poster Sessions</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -55,11 +55,13 @@ var ConferenceSplash = React.createClass({
|
|||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
<img src="/images/conference/schedule/coming-soon.png" alt="schedule-coming-soon" />
|
||||
Schedule
|
||||
<a href="/conference/schedule">
|
||||
<img src="/images/conference/schedule/schedule.png" alt="schedule" />
|
||||
Schedule
|
||||
</a>
|
||||
</h3>
|
||||
<p>
|
||||
Stay tuned for the full schedule of events and sessions
|
||||
Full schedule of events and sessions
|
||||
</p>
|
||||
</div>
|
||||
</FlexRow>
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
align-items: flex-start;
|
||||
|
||||
|
|
130
src/views/conference/schedule/schedule.jsx
Normal file
130
src/views/conference/schedule/schedule.jsx
Normal file
|
@ -0,0 +1,130 @@
|
|||
var classNames = require('classnames');
|
||||
var connect = require('react-redux').connect;
|
||||
var React = require('react');
|
||||
var render = require('../../../lib/render.jsx');
|
||||
|
||||
var scheduleActions = require('../../../redux/conference-schedule.js');
|
||||
|
||||
var FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
||||
var Page = require('../../../components/page/conference/page.jsx');
|
||||
var SubNavigation = require('../../../components/subnavigation/subnavigation.jsx');
|
||||
var TitleBanner = require('../../../components/title-banner/title-banner.jsx');
|
||||
|
||||
require('./schedule.scss');
|
||||
|
||||
var ConferenceSchedule = React.createClass({
|
||||
type: 'ConferenceSchedule',
|
||||
propTypes: {
|
||||
conferenceSchedule: React.PropTypes.object
|
||||
},
|
||||
componentDidMount: function () {
|
||||
var day = window.location.hash.substr(1) || 'thursday';
|
||||
this.handleScheduleChange(day);
|
||||
},
|
||||
handleScheduleChange: function (day) {
|
||||
this.props.dispatch(scheduleActions.startGetSchedule(day));
|
||||
},
|
||||
renderChunkItems: function (timeSlot) {
|
||||
return timeSlot.map(function (item) {
|
||||
if (item.Presenter) {
|
||||
return (
|
||||
<a href={item.uri} className="item-url">
|
||||
<div key={item.rowid} className="agenda-item">
|
||||
<h3>{item.Title}</h3>
|
||||
<FlexRow>
|
||||
<p>
|
||||
<img src="/svgs/conference/schedule/time-icon.svg" alt="time icon" />
|
||||
{item.Start} – {item.End}
|
||||
</p>
|
||||
<p>
|
||||
<img src="/svgs/conference/schedule/location-icon.svg" alt="location icon" />
|
||||
{item.Location}
|
||||
</p>
|
||||
</FlexRow>
|
||||
<FlexRow>
|
||||
<p>
|
||||
<img src="/svgs/conference/schedule/presenter-icon.svg" alt="presenter icon" />
|
||||
{item.Presenter}
|
||||
</p>
|
||||
<p>
|
||||
<img src="/svgs/conference/schedule/event-icon.svg" alt="event icon" />
|
||||
{item.Type}
|
||||
</p>
|
||||
</FlexRow>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div key={item.rowid} className="agenda-item no-click">
|
||||
<h3>{item.Title}</h3>
|
||||
<FlexRow>
|
||||
<p>{item.Start} – {item.End}</p>
|
||||
<p>{item.Location}</p>
|
||||
</FlexRow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
var tabClasses = {
|
||||
'thursday': classNames({
|
||||
'selected': (this.props.conferenceSchedule.day === 'thursday')
|
||||
}),
|
||||
'friday': classNames({
|
||||
'selected': (this.props.conferenceSchedule.day === 'friday')
|
||||
}),
|
||||
'saturday': classNames({
|
||||
'last': true,
|
||||
'selected': (this.props.conferenceSchedule.day === 'saturday')
|
||||
})
|
||||
};
|
||||
return (
|
||||
<div className="schedule">
|
||||
<TitleBanner>
|
||||
<h1>
|
||||
Schedule
|
||||
</h1>
|
||||
</TitleBanner>
|
||||
<SubNavigation>
|
||||
<li className={tabClasses.thursday}
|
||||
onClick={this.handleScheduleChange.bind(this, 'thursday')}>
|
||||
<img src="/svgs/conference/expect/aug4-icon.svg" alt="August 4th Icon" />
|
||||
<span>Thursday</span>
|
||||
</li>
|
||||
<li className={tabClasses.friday}
|
||||
onClick={this.handleScheduleChange.bind(this, 'friday')}>
|
||||
<img src="/svgs/conference/expect/aug5-icon.svg" alt="August 5th Icon" />
|
||||
<span>Friday</span>
|
||||
</li>
|
||||
<li className={tabClasses.saturday}
|
||||
onClick={this.handleScheduleChange.bind(this, 'saturday')}>
|
||||
<img src="/svgs/conference/expect/aug6-icon.svg" alt="August 6th Icon" />
|
||||
<span>Saturday</span>
|
||||
</li>
|
||||
</SubNavigation>
|
||||
<div className="inner">
|
||||
{this.props.conferenceSchedule.timeSlots.map(function (timeSlot) {
|
||||
return ([
|
||||
<h2 key={timeSlot.info.name} className="breaking-title">
|
||||
<span>{timeSlot.info.name} – {timeSlot.info.time}</span>
|
||||
</h2>,
|
||||
this.renderChunkItems(timeSlot.items)
|
||||
]);
|
||||
}.bind(this))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var mapStateToProps = function (state) {
|
||||
return {
|
||||
conferenceSchedule: state.conferenceSchedule
|
||||
};
|
||||
};
|
||||
|
||||
var ConnectedSchedule = connect(mapStateToProps)(ConferenceSchedule);
|
||||
|
||||
render(<Page><ConnectedSchedule /></Page>, document.getElementById('app'));
|
128
src/views/conference/schedule/schedule.scss
Normal file
128
src/views/conference/schedule/schedule.scss
Normal file
|
@ -0,0 +1,128 @@
|
|||
@import "../../../colors";
|
||||
@import "../../../frameless";
|
||||
|
||||
.schedule {
|
||||
.title-banner {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.sub-nav {
|
||||
z-index: -1;
|
||||
box-shadow: 0 2px 5px $ui-dark-gray;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-top: 4px solid transparent;
|
||||
border-left: 2px solid $active-gray;
|
||||
border-radius: 0;
|
||||
padding: .75em 1em;
|
||||
color: $type-gray;
|
||||
font-size: 1rem;
|
||||
|
||||
&.last {
|
||||
border-right: 2px solid $active-gray;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&.selected {
|
||||
border-top: 4px solid $ui-orange;
|
||||
border-left: 2px solid $active-gray;
|
||||
box-shadow: none;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: .5em;
|
||||
width: 2em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.inner {
|
||||
h2 {
|
||||
&.breaking-title {
|
||||
margin: 4rem 0 2rem 0;
|
||||
border-bottom: 1px solid $ui-dark-gray;
|
||||
width: 100%;
|
||||
height: 1.7rem; // match the line-height for h2
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: $ui-white;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
&.item-url {
|
||||
display: block;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($ui-blue, 40);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-item {
|
||||
margin: 1rem 0;
|
||||
border: 1px solid $active-gray;
|
||||
border-radius: 5px;
|
||||
padding: 1.25rem 2.25rem;
|
||||
|
||||
&.no-click {
|
||||
background-color: $ui-gray;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
margin: .5rem 0;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
width: 50%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: .5rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $tablet - 1) {
|
||||
.inner {
|
||||
h2 {
|
||||
&.breaking-title {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-item {
|
||||
.flex-row {
|
||||
p {
|
||||
margin: .5rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ var omit = require('lodash.omit');
|
|||
var React = require('react');
|
||||
var render = require('../../lib/render.jsx');
|
||||
|
||||
var authActions = require('../../redux/actions.js');
|
||||
var sessionActions = require('../../redux/session.js');
|
||||
|
||||
var Api = require('../../mixins/api.jsx');
|
||||
|
||||
|
@ -167,7 +167,7 @@ var Splash = injectIntl(React.createClass({
|
|||
useCsrf: true,
|
||||
json: {cue: cue, value: false}
|
||||
}, function (err) {
|
||||
if (!err) this.props.dispatch(authActions.refreshSession());
|
||||
if (!err) this.props.dispatch(sessionActions.refreshSession());
|
||||
});
|
||||
},
|
||||
shouldShowWelcome: function () {
|
||||
|
|
1
static/svgs/conference/schedule/event-icon.svg
Normal file
1
static/svgs/conference/schedule/event-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="13.01" height="13.24" viewBox="0 0 13.01 13.24"><title>event-icon</title><path d="M12.26,1a1.25,1.25,0,0,0-1.32.18,2.9,2.9,0,0,1-3.55,0A5.52,5.52,0,0,0,1.74.47,0.82,0.82,0,0,0,1,0H0.83A0.83,0.83,0,0,0,0,.83V12.41a0.83,0.83,0,0,0,.83.83H1a0.83,0.83,0,0,0,.83-0.83V9.19A0.85,0.85,0,0,0,2.35,9a2.85,2.85,0,0,1,3.41.09,5.35,5.35,0,0,0,6.8,0,1.27,1.27,0,0,0,.45-1v-6A1.26,1.26,0,0,0,12.26,1Zm-1,6.93A3.75,3.75,0,0,1,6.84,7.8a4.62,4.62,0,0,0-5-.46v-5a3.74,3.74,0,0,1,4.48.12,4.58,4.58,0,0,0,5,.46v5Z" fill="#929497"/></svg>
|
After (image error) Size: 597 B |
1
static/svgs/conference/schedule/location-icon.svg
Normal file
1
static/svgs/conference/schedule/location-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="13.46" height="19.12" viewBox="0 0 13.46 19.12"><defs><style>.cls-1{fill:#929497;}</style></defs><title>location-icon</title><path class="cls-1" d="M12,21.56a1.25,1.25,0,0,1-1.22-1,15.77,15.77,0,0,0-3.16-5.91A14.75,14.75,0,0,1,6.46,13,6.57,6.57,0,0,1,5.27,9.17a6.73,6.73,0,1,1,12.22,3.9,14,14,0,0,1-1.11,1.58,15.8,15.8,0,0,0-3.17,5.95A1.24,1.24,0,0,1,12,21.56ZM12,4.14a5,5,0,0,0-4.1,7.94A14.34,14.34,0,0,0,9,13.63a19.24,19.24,0,0,1,3,5.13,19.21,19.21,0,0,1,3-5.14,13.8,13.8,0,0,0,1-1.46,5.12,5.12,0,0,0,1-3A5,5,0,0,0,12,4.14Z" transform="translate(-5.27 -2.44)"/><path class="cls-1" d="M12,12.39a3,3,0,1,1,3-3A3,3,0,0,1,12,12.39Zm0-4.86a1.83,1.83,0,1,0,1.83,1.83A1.83,1.83,0,0,0,12,7.54Z" transform="translate(-5.27 -2.44)"/></svg>
|
After (image error) Size: 811 B |
1
static/svgs/conference/schedule/presenter-icon.svg
Normal file
1
static/svgs/conference/schedule/presenter-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="15.45" height="19.32" viewBox="0 0 15.45 19.32"><defs><style>.cls-1{fill:#929497;}</style></defs><title>facilitator-icon</title><path class="cls-1" d="M16,10.13a4.91,4.91,0,1,0-8.9-2.87A4.86,4.86,0,0,0,8,10.13a8.92,8.92,0,0,0-3.75,7.44c0,1.35.95,2.49,2.67,3.22a13.65,13.65,0,0,0,5.06.87c3.84,0,7.72-1.26,7.72-4.09A8.94,8.94,0,0,0,16,10.13ZM12,4A3.21,3.21,0,0,1,14.08,9.7a3.29,3.29,0,0,1-3.53.41,2.61,2.61,0,0,1-.4-0.23L9.91,9.69A3.21,3.21,0,0,1,12,4ZM12,20c-3.38,0-6-1-6-2.39a7.15,7.15,0,0,1,3.3-6.23l0.06,0,0.25,0.15a4.53,4.53,0,0,0,1,.42L10.93,12a5.5,5.5,0,0,0,2.14,0L13.42,12a4.53,4.53,0,0,0,1-.42l0.25-.15,0.06,0A7.16,7.16,0,0,1,18,17.57C18,18.91,15.38,20,12,20Z" transform="translate(-4.27 -2.34)"/><path class="cls-1" d="M14.41,11.53a4.75,4.75,0,0,1-1,.42A4.53,4.53,0,0,0,14.41,11.53ZM10.93,12a4.47,4.47,0,0,0,2.14,0A5.5,5.5,0,0,1,10.93,12ZM10.58,12a4.75,4.75,0,0,1-1-.42A4.53,4.53,0,0,0,10.58,12Z" transform="translate(-4.27 -2.34)"/></svg>
|
After (image error) Size: 1 KiB |
1
static/svgs/conference/schedule/time-icon.svg
Normal file
1
static/svgs/conference/schedule/time-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="13.12" height="13.12" viewBox="0 0 13.12 13.12"><defs><style>.cls-1{fill:#929497;}</style></defs><title>time-icon</title><path class="cls-1" d="M12,5.44A6.56,6.56,0,1,0,18.56,12,6.57,6.57,0,0,0,12,5.44Zm0,11.45A4.89,4.89,0,1,1,16.89,12,4.9,4.9,0,0,1,12,16.89Z" transform="translate(-5.44 -5.44)"/><path class="cls-1" d="M14.5,12a0.59,0.59,0,0,1-.59.6H12a0.6,0.6,0,0,1-.6-0.6V8.57a0.6,0.6,0,0,1,1.2,0V11.4h1.31A0.59,0.59,0,0,1,14.5,12Z" transform="translate(-5.44 -5.44)"/></svg>
|
After (image error) Size: 558 B |
Loading…
Reference in a new issue