Merge pull request from mewtaylor/feature/conference-schedule

Feature: conference schedule page
This commit is contained in:
Matthew Taylor 2016-05-19 18:04:04 -04:00
commit cf2165d295
24 changed files with 773 additions and 132 deletions

View file

@ -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;
}, '') + '"';
};

View file

@ -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 () {

View file

@ -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>

View file

@ -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 () {

View file

@ -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;

View file

@ -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;

View 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;
}
});
};
};

View 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;
}
});
};
};

View file

@ -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
View 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
View 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
};
};

View file

@ -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/?",

View 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}>
&larr; 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} &ndash; {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}>
&larr; 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'));

View 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;
}
}

View file

@ -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>

View file

@ -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>

View file

@ -56,6 +56,7 @@
}
}
}
.flex-row {
align-items: flex-start;

View 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} &ndash; {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} &ndash; {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'));

View 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%;
}
}
}
}
}
}

View file

@ -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 () {

View 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

View 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

View 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

View 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