mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-03-23 19:30:34 -04:00
Add schedule/detail components for conference
This commit is contained in:
parent
b969c4e2ed
commit
cad75217f0
10 changed files with 599 additions and 3 deletions
src
|
@ -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>
|
||||
|
|
80
src/redux/conference-details.js
Normal file
80
src/redux/conference-details.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
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;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
159
src/redux/conference-schedule.js
Normal file
159
src/redux/conference-schedule.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
var keyMirror = require('keymirror');
|
||||
var api = require('../mixins/api.jsx').api;
|
||||
|
||||
var Types = keyMirror({
|
||||
SET_DAY: null,
|
||||
SET_SCHEDULE: null,
|
||||
SET_SCHEDULE_FETCHING: null,
|
||||
SET_DAY_ERROR: null,
|
||||
SET_SCHEDULE_ERROR: null
|
||||
});
|
||||
|
||||
module.exports.dayReducer = function (state, action) {
|
||||
if (typeof state === 'undefined') {
|
||||
state = '';
|
||||
}
|
||||
switch (action.type) {
|
||||
case Types.SET_DAY:
|
||||
return action.day;
|
||||
case Types.SET_DAY_ERROR:
|
||||
return state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.scheduleReducer = function (state, action) {
|
||||
if (typeof state === 'undefined') {
|
||||
state = [];
|
||||
}
|
||||
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.setDayError = function (error) {
|
||||
return {
|
||||
type: Types.SET_DAY_ERROR,
|
||||
error: error
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.setDay = function (day) {
|
||||
return {
|
||||
type: Types.SET_DAY,
|
||||
day: day
|
||||
};
|
||||
};
|
||||
|
||||
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));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.setDayError(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 scheduleByChunk = 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 chunk = cleanedRow.Chunk;
|
||||
if (typeof prev.chunks[chunk] === 'undefined') {
|
||||
prev.chunks[chunk] = [cleanedRow];
|
||||
prev.info.push({
|
||||
name: chunk,
|
||||
time: cleanedRow.Start
|
||||
});
|
||||
} else {
|
||||
prev.chunks[chunk].push(cleanedRow);
|
||||
}
|
||||
return prev;
|
||||
}, {chunks: [], info: []});
|
||||
// group periods of time by start time
|
||||
scheduleByChunk.info.sort(function compare (a, b) {
|
||||
var aAm = (a.time.substr(a.time.length - 1, a.time.length) === 'a') ? true : false;
|
||||
var bAm = (b.time.substr(b.time.length - 1, b.time.length) === 'a') ? true : false;
|
||||
var aTime = parseInt(a.time.substr(0, a.time.length - 1));
|
||||
var bTime = parseInt(b.time.substr(0, b.time.length - 1));
|
||||
|
||||
if (aTime < bTime) {
|
||||
if (bAm && !aAm) {
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
if (aAm && !bAm) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
var schedule = [];
|
||||
for (var i = 0; i < scheduleByChunk.info.length; i++) {
|
||||
schedule.push({
|
||||
info: scheduleByChunk.info[i],
|
||||
items: scheduleByChunk.chunks[scheduleByChunk.info[i].name]
|
||||
});
|
||||
}
|
||||
dispatch(module.exports.setDay(day));
|
||||
dispatch(module.exports.setSchedule(schedule));
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
|
@ -1,10 +1,15 @@
|
|||
var combineReducers = require('redux').combineReducers;
|
||||
|
||||
var authReducers = require('./auth.js');
|
||||
var conferenceScheduleReducers = require('./conference-schedule.js');
|
||||
var conferenceDetailsReducers = require('./conference-details.js');
|
||||
|
||||
var appReducer = combineReducers({
|
||||
session: authReducers.sessionReducer,
|
||||
token: authReducers.tokenReducer,
|
||||
day: conferenceScheduleReducers.dayReducer,
|
||||
schedule: conferenceScheduleReducers.scheduleReducer,
|
||||
details: conferenceDetailsReducers.detailsReducer
|
||||
});
|
||||
|
||||
module.exports = appReducer;
|
||||
|
|
|
@ -74,6 +74,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/?",
|
||||
|
|
88
src/views/conference/details/details.jsx
Normal file
88
src/views/conference/details/details.jsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
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,
|
||||
details: 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.details.error && !this.props.details.fetching) {
|
||||
backUri = backUri + '#' + this.props.details.Day;
|
||||
}
|
||||
var classes = classNames({
|
||||
'inner': true,
|
||||
'details': true,
|
||||
'fetching': this.props.details.fetching
|
||||
});
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="back">
|
||||
<a href={backUri}>
|
||||
← Back to Full Schedule
|
||||
</a>
|
||||
</div>
|
||||
{this.props.details.error ? [
|
||||
<h2>Agenda Item Not Found</h2>
|
||||
] : [
|
||||
<h2>{this.props.details.Title}</h2>,
|
||||
<ul className="logistics">
|
||||
<li>
|
||||
{this.props.details.Presenter}
|
||||
</li>
|
||||
<li>
|
||||
{this.props.details.Start} – {this.props.details.End}
|
||||
</li>
|
||||
<li>
|
||||
{this.props.details.Type}
|
||||
</li>
|
||||
<li>
|
||||
{this.props.details.Location}
|
||||
</li>
|
||||
</ul>,
|
||||
<div className="description">
|
||||
{this.props.details.Description}
|
||||
</div>,
|
||||
<div className="back">
|
||||
{this.props.details.fetching ? [] : [
|
||||
<a href={backUri}>
|
||||
← Back to Full Schedule
|
||||
</a>
|
||||
]}
|
||||
</div>
|
||||
]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var mapStateToProps = function (state) {
|
||||
return {
|
||||
details: state.details,
|
||||
fetching: state.fetching,
|
||||
error: state.error
|
||||
};
|
||||
};
|
||||
|
||||
var ConnectedDetails = connect(mapStateToProps)(ConferenceDetails);
|
||||
|
||||
render(<Page><ConnectedDetails /></Page>, document.getElementById('app'));
|
29
src/views/conference/details/details.scss
Normal file
29
src/views/conference/details/details.scss
Normal file
|
@ -0,0 +1,29 @@
|
|||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
122
src/views/conference/schedule/schedule.jsx
Normal file
122
src/views/conference/schedule/schedule.jsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
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: {
|
||||
day: React.PropTypes.string,
|
||||
schedule: React.PropTypes.array
|
||||
},
|
||||
componentDidMount: function () {
|
||||
var day = window.location.hash.substr(1) || 'thursday';
|
||||
this.handleScheduleChange(day);
|
||||
},
|
||||
handleScheduleChange: function (day) {
|
||||
this.props.dispatch(scheduleActions.startGetSchedule(day));
|
||||
},
|
||||
renderChunkItems: function (chunk) {
|
||||
return chunk.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>{item.Start} – {item.End}</p>
|
||||
<p>{item.Location}</p>
|
||||
</FlexRow>
|
||||
<FlexRow>
|
||||
<p>{item.Presenter}</p>
|
||||
<p>{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.day === 'thursday')
|
||||
}),
|
||||
'friday': classNames({
|
||||
'selected': (this.props.day === 'friday')
|
||||
}),
|
||||
'saturday': classNames({
|
||||
'last': true,
|
||||
'selected': (this.props.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.schedule.map(function (chunk) {
|
||||
return ([
|
||||
<h2 key={chunk.info.name} className="breaking-title">
|
||||
<span>{chunk.info.name} – {chunk.info.time}</span>
|
||||
</h2>,
|
||||
this.renderChunkItems(chunk.items)
|
||||
]);
|
||||
}.bind(this))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var mapStateToProps = function (state) {
|
||||
return {
|
||||
day: state.day,
|
||||
schedule: state.schedule,
|
||||
fetching: state.fetching,
|
||||
error: state.error
|
||||
};
|
||||
};
|
||||
|
||||
var ConnectedSchedule = connect(mapStateToProps)(ConferenceSchedule);
|
||||
|
||||
render(<Page><ConnectedSchedule /></Page>, document.getElementById('app'));
|
94
src/views/conference/schedule/schedule.scss
Normal file
94
src/views/conference/schedule/schedule.scss
Normal file
|
@ -0,0 +1,94 @@
|
|||
@import "../../../colors";
|
||||
|
||||
.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;
|
||||
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;
|
||||
padding: .75em 1em;
|
||||
}
|
||||
|
||||
&.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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue