Commit d213fe83 authored by Zac Wood's avatar Zac Wood
Browse files

Merge branch 'dev-v3' into 'master'

Version 3

See merge request !51
parents b6309e4e 80cf9394
Pipeline #4473 passed with stage
in 2 minutes and 47 seconds
import React from 'react';
import Course from 'src/Course';
export default class CourseList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{this.props.courses.map(course => (
<Course key={course.id} {...course} />
))}
</div>
);
}
}
import React from 'react';
import Cart from 'src/Cart';
import { saveAs } from 'file-saver';
export default class extends React.Component {
render() {
return (
<div
className="modal fade"
id="exportModal"
tabindex="-1"
role="dialog"
aria-labelledby="exportModalLabel"
aria-hidden="true">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title" id="exportModalLabel">
Your calendar has been generated! <br /> (Click on the options below to see further
instructions)
</h5>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div className="modal-body">
<button
type="button"
className="btn-variant"
data-toggle="collapse"
data-target="#apple-info">
{' '}
<h5> Apple Calendar </h5>{' '}
</button>
<div id="apple-info" className="collapse">
To add your schedule to Apple Calendar, click the "Add to calendar" button below. If you
are on a device running macOS or iOS, this will open a dialogue which will walk you
through adding the calendar.
</div>
<hr />
<button
type="button"
className="btn-variant"
data-toggle="collapse"
data-target="#google-info">
{' '}
<h5>Google Calendar</h5>{' '}
</button>
<div id="google-info" className="collapse">
<strong>On desktop:</strong>
<br />
First, download the calendar file using the "Download calendar file" below. Open your{' '}
<a href="https://calendar.google.com/" target="_blank">
Google Calendar
</a>
. Click the "Settings" button in the top right, and then click the Settings tab. In the
menu on the left, click "Import & export" and "Import". Now, upload the calendar file
you downloaded and click "Import".
<br />
<strong>On mobile (Android only):</strong>
<br />
Click the "Download calendar file" button. This will download the calendar file which
you may then open and add to your calendar.
<br />
</div>
<hr />
<button
type="button"
className="btn-variant"
data-toggle="collapse"
data-target="#outlook-info">
{' '}
<h5>Outlook Calendar</h5>{' '}
</button>
<div id="outlook-info" className="collapse">
<button
type="button"
className="btn-variant"
data-toggle="collapse"
data-target="#outlook-desktop">
{' '}
<strong>On desktop (Windows):</strong>{' '}
</button>
<br />
<div id="outlook-desktop" className="collapse">
First, download the calendar file using the “Download calendar file” button below.
In Outlook, choose File, then Open and Export, and then Import/Export. In the Import
and Export Wizard Box, choose “Import and iCalendar (.ics) or vCalendar file (.vcs)”
and the “next” button. Search for the button you downloaded in the beginning. Click
“Okay” and then “Import.”
</div>
<br />
<button
type="button"
className="btn-variant"
data-toggle="collapse"
data-target="#outlook-mac">
{' '}
<strong>On desktop (Mac):</strong>{' '}
</button>
<br />
<div id="outlook-mac" className="collapse">
First, download the calendar file using the “Download calendar file” button below.
Open Outlook and make sure the calendar in which you want to import the file into
has a checkmark next to it. Alternatively, you can add it into a new calendar by
clicking the “Organize” tab and then the “New Calendar” button. Double click the new
Calendar to rename it. Open the Finder application and search for the file you
downloaded in the beginning. Then, drag and drop the file into the desired Calendar
area.
</div>
<br />
<button
type="button"
className="btn-variant"
data-toggle="collapse"
data-target="#outlook-classNameic">
{' '}
<strong>Outlook Online (ClassNameic Layout)</strong>{' '}
</button>
<br />
<div id="outlook-classNameic" className="collapse">
To check if you are using the ClassNameic Layout, look in the top right and see if
“The new Outlook” bar is slid to the left. If it is not, you may consider reading
“The New Outlook Layout” instructions or clicking the bar to slide it to the left.
First, download the calendar file using the “Download calendar file” button below.
Login onto your{' '}
<a href="https://outlook.live.com/owa/" target="blank">
Outlook
</a>{' '}
and click the calendar icon on the bottom left. On the menu bar, located above the
Calendar, choose the “Add Calendar” menu. From the drop down menu, click import from
file and browse for the calendar file you downloaded in the beginning. Click the
save icon, then the calendar will be imported.
</div>
<br />
<button
type="button"
className="btn-variant"
data-toggle="collapse"
data-target="#outlook-new">
{' '}
<strong>Outlook Online (New Outlook Layout)</strong>{' '}
</button>
<br />
<div id="outlook-new" className="collapse">
To check if you are using the New Outlook Layout, look in the top right and see if
“The new Outlook” bar is slid to the right. If it is not, you may consider reading
the “ClassNameic Layout” instructions or clicking the bar to slide it to the right.
First download the calendar file using the “Download calendar file” button below.
Login onto your{' '}
<a href="https://outlook.live.com/owa/" target="blank">
Outlook
</a>{' '}
and click the calendar icon on the bottom left. On the left side bar, under
“Calendars”, click the “Discover calendars” button. Choose on the “From File” menu
under the “Import” Section. Then click the browse button and search for the file you
downloaded in the beginning. Lastly, choose “Import” and your calendar will be
displayed.
</div>
</div>
<br />
<hr />
<h5>.ics file</h5>
To download a .ics file containing your schedule, click the "Download calendar file" button
below.
</div>
<div className="modal-footer flex">
<button
id="download-ics"
type="button"
className="btn btn-secondary"
onClick={this.downloadIcs}>
Download calendar file
</button>
<button
id="add-to-system"
type="button"
className="btn btn-primary"
onClick={this.addToSystemCalendar}>
Add to system calendar
</button>
</div>
</div>
</div>
</div>
);
}
downloadIcs = async () => {
const response = await fetch(
`${window.location.protocol}//${window.location.hostname}${
window.location.port === '3000' ? ':3000' : ''
}/api/schedules?crns=${Cart.crns.join(',')}`
);
const text = await response.text();
const blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'GMU Schedule.ics');
};
addToSystemCalendar = () => {
window.open(
`webcal://${window.location.hostname}${
window.location.port === '3000' ? ':3000' : ''
}/api/schedules?crns=${Cart.crns.join(',')}`
);
};
}
import React from 'react';
export default class InstructorCard extends React.Component {
render() {
const inst = this.props.instructor;
return (
<div className="card p-3">
<span>
<i className="fas fa-chalkboard-teacher mr-2" />
<a href={`/instructors/${inst.id}`}>{this.props.instructor.name}</a>
</span>
</div>
);
}
}
import React from 'react';
import InstructorCard from 'src/InstructorCard';
export default class InstructorList extends React.Component {
render() {
return <div>{this.props.instructors && this.props.instructors.map(i => <InstructorCard instructor={i} />)}</div>;
}
}
import React from 'react';
import Cart from 'src/Cart';
export default class QuickAdd extends React.Component {
constructor(props) {
super(props);
this.state = { crnString: '' };
}
add = e => {
e.preventDefault();
const crns = this.state.crnString.split(',');
crns.forEach(c => c.length === 5 && Cart.addCrn(c));
this.props.loadCalendar();
};
render() {
return (
<div>
<h3 className="quick-add-header">Quick add</h3>
<p>Want to quickly generate a calendar populated with your semester's classes? Enter the CRNs in a comma separated list below.</p>
<form onSubmit={this.add} className="form">
<div className="input-group">
<input
id="crns"
name="crns"
type="text"
value={this.state.crns}
onChange={e => this.setState({ crnString: e.target.value })}
className="form-control"
placeholder="12345,54321,..."
aria-describedby="basic-addon2"
autoComplete="off"
/>
<div className="input-group-append">
<button type="submit" className="btn btn-primary" type="button">
Populate Calendar
</button>
</div>
</div>
</form>
</div>
);
}
}
import React from 'react';
import CourseList from 'src/CourseList';
import InstructorList from 'src/InstructorList';
export default class SearchList extends React.Component {
render() {
return (
<div>
<InstructorList instructors={this.props.instructors} />
<CourseList courses={this.props.courses} />
</div>
);
}
}
import React from 'react';
import Cart from 'src/Cart';
import Stars from 'src/Stars';
export default class Section extends React.Component {
constructor(props) {
super(props);
this.state = { inCart: Cart.includesCrn(this.props.crn) };
}
onClick = e => {
e.stopPropagation();
if (this.props.readOnly) return;
console.log(e.target.tagName);
if (e.target.tagName === 'A') return; // if we clicked on a link, don't add the section to the cart
Cart.toggleCrn(this.props.crn);
this.setState({ inCart: Cart.includesCrn(this.props.crn) });
this.props.onClick && this.props.onClick(this.props.crn);
};
render() {
const {
name,
title,
crn,
readOnly,
instructor_name,
instructor_url,
teaching_rating,
location,
days,
start_time,
end_time,
} = this.props;
const { inCart } = this.state;
const percent = teaching_rating ? (
<Stars percent={(teaching_rating[0] / 5) * 100} />
) : null;
const remove = (
<span
className="float-right text-center add-remove-btn"
style={inCart ? {} : { display: 'none' }}>
<i id="icon" className="fas fa-minus" />
<br />
<span className="text">Remove</span>
</span>
);
const add = (
<span
className="float-right text-center add-remove-btn"
style={inCart ? { display: 'none' } : {}}>
<i id="icon" className="fas fa-plus" />
<br />
<span className="text">Add</span>
</span>
);
return (
<li className="list-group-item section-item" onClick={this.onClick}>
<p>
<b>{name}</b>: {title}{' '}
<em>
(#
{crn})
</em>
</p>
{!readOnly && (
<div>
{remove}
{add}
</div>
)}
<i className="fas fa-chalkboard-teacher" />{' '}
{instructor_name !== 'TBA' ? (
<span>
<a href={instructor_url}>{instructor_name}</a> {percent}
</span>
) : (
<span>{instructor_name}</span>
)}
<br />
<i className="fas fa-map-marker-alt" /> {location} <br />
<i className="fas fa-clock" /> {days}, {start_time} - {end_time} <br />
</li>
);
}
}
import React from 'react';
import Chevron from 'src/Chevron';
import Section from 'src/Section';
export default class SectionList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{this.props.expandable ? <Chevron open={this.props.expanded} /> : null}
{this.props.expanded ? (
<div className="d-flex list-group list-group-flush sections">
{this.props.sections.map(section => (
<Section key={section.id} onClick={this.props.onClick} readOnly={this.props.readOnly} {...section} />
))}
</div>
) : (
<div />
)}
</div>
);
}
}
import React from 'react';
const ShareModal = props => {
return (
<div
className="modal fade"
id="shareModal"
tabindex="-1"
role="dialog"
aria-labelledby="shareModalLabel"
aria-hidden="true">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Share your schedule!</h5>
</div>
<div className="modal-body">
The following link contains a copy of your schedule: <br />
<a id="shareLink" href={props.link}>
{props.link}
</a>
</div>
</div>
</div>
</div>
);
};
export default ShareModal;
import React from 'react';
export default class Stars extends React.Component {
render() {
return (
<div className="star-rating">
<div className="back-stars">
<i className="fas fa-star" aria-hidden="true" />
<i className="fas fa-star" aria-hidden="true" />
<i className="fas fa-star" aria-hidden="true" />
<i className="fas fa-star" aria-hidden="true" />
<i className="fas fa-star" aria-hidden="true" />
<div className="front-stars" style={{ width: `${this.props.percent}%` }}>
<i className="fa fa-star" aria-hidden="true" />
<i className="fa fa-star" aria-hidden="true" />
<i className="fa fa-star" aria-hidden="true" />
<i className="fa fa-star" aria-hidden="true" />
<i className="fa fa-star" aria-hidden="true" />
</div>
</div>
</div>
);
}
}
import React from 'react';
import BigCalendar from 'react-big-calendar';
import Toolbar from 'react-big-calendar/lib/Toolbar';
import '!style-loader!css-loader!react-big-calendar/lib/css/react-big-calendar.css';
import withSizes from 'react-sizes';
class CustomToolbar extends Toolbar {
render() {
const { label, isMobile } = this.props;
if (isMobile && label === '') {
this.view('day');
}
return (
<div className="rbc-toolbar d-flex justify-content-between">
{!isMobile && (
<div>
<span className="rbc-btn-group">
<button type="button" onClick={() => this.view('day')}>
Day
</button>
<button type="button" onClick={() => this.view('week')}>
Week
</button>
</span>
</div>
)}
<span className="rbc-toolbar-label">{this.props.label}</span>
{this.props.view === 'day' && (
<span className="rbc-btn-group">
{this.props.label !== 'Sun' && (
<button type="button" onClick={() => this.navigate('PREV')}>
Back
</button>
)}
{this.props.label !== 'Sat' && (
<button type="button" onClick={() => this.navigate('NEXT')}>
Next
</button>
)}
</span>
)}
</div>
);
}
navigate = action => {
console.log(action);
this.props.onNavigate(action);
};
view = action => {
this.props.onView(action);
};
}
const mapSizesToProps = ({ width }) => ({
isMobile: width < 1000,
});
export default withSizes(mapSizesToProps)(CustomToolbar);
import React from 'react';
import Calendar from 'src/Calendar';
import Cart from 'src/Cart';
import SectionList from 'src/SectionList';
import ExportModal from 'src/ExportModal';
import QuickAdd from 'src/QuickAdd';
import moment from 'moment';
import 'url-polyfill';
const params = new URLSearchParams(document.location.search);
const crns = params.get('crns');
export default class CalendarPage extends React.Component {
state = { events: [], sections: [] };
constructor(props) {
super(props);
this.loadEvents();
}
loadEvents = async () => {
const response = await fetch(`/schedule/events?crns=${crns}`);
const json = await response.json();