import React, { useState, useEffect, useReducer } from 'react'; import { Link, useParams } from 'react-router-dom'; import './light.css'; import { Label, Button, Container, Dropdown, Form, Header, Icon, Input, Segment, Table } from 'semantic-ui-react'; import moment from 'moment-timezone'; import { apiUrl, isAdmin, getInstructor, BasicTable, requester, useIsMobile } from './utils.js'; import { NotFound } from './Misc.js'; import { InstructorClassDetail, InstructorClassAttendance } from './InstructorClasses.js'; import { PayPalPayNow } from './PayPal.js'; import { PayWithProtocoin } from './Paymaster.js'; import { tags } from './Courses.js'; function ClassTable(props) { const { classes } = props; const isMobile = useIsMobile(); const now = new Date().toISOString(); return (isMobile ? {classes.length ? classes.map(x => {x.course_data.name} {moment.utc(x.datetime).tz('America/Edmonton').format('ddd, ll')} Time: {x.is_cancelled ? 'Cancelled' : moment.utc(x.datetime).tz('America/Edmonton').format('LT')} Cost: {x.cost === '0.00' ? 'Free' : '$'+x.cost} Students: {!!x.max_students ? x.max_students <= x.student_count ? 'Full' : x.student_count + ' / ' + x.max_students : x.student_count } Instructor: {getInstructor(x)} ) : None }
: Name Date Time Instructor Cost Students {classes.length ? classes.map(x =>  {x.course_data.name} {moment.utc(x.datetime).tz('America/Edmonton').format('ddd, ll')} {x.is_cancelled ? 'Cancelled' : moment.utc(x.datetime).tz('America/Edmonton').format('LT')} {getInstructor(x)} {x.cost === '0.00' ? 'Free' : '$'+x.cost} {!!x.max_students ? x.max_students <= x.student_count ? 'Full' : x.student_count + ' / ' + x.max_students : x.student_count } ) : None }
); }; function NewClassTableCourse(props) { const {course, classes, token, user, refreshUser} = props; const [error, setError] = useState(false); const [loading, setLoading] = useState(false); const [interested, setInterested] = useState(course.num_interested || 0); const handleInterest = () => { if (loading) return; setLoading(true); const data = { course: course.id }; requester('/interest/', 'POST', token, data) .then(res => { setError(false); refreshUser(); setInterested(interested+1); }) .catch(err => { console.log(err); setError(true); }); }; const now = new Date().toISOString(); return (
{course.name}
{!!course.tags && course.tags.split(',').map(name => )}
{user &&
{user.interests.filter(x => !x.satisfied_by).map(x => x.course).includes(course.id) ?
{interested} interested
: }
}
{error &&

Error.

} {classes ? Date Cost Students {classes.map(x => {moment.utc(x.datetime).tz('America/Edmonton').format(' MMM Do')} {' - '}{x.is_cancelled ? 'Cancelled' : moment.utc(x.datetime).tz('America/Edmonton').format('LT')} {x.cost === '0.00' ? 'Free' : '$'+x.cost.slice(0,-3)} {!!x.max_students ? x.max_students <= x.student_count ? 'Full' : x.student_count + ' / ' + x.max_students : x.student_count } )}
: <>

No upcoming classes.

}
); } function NewClassTable(props) { const { classes, courses, token, user, refreshUser } = props; let sortedClasses = []; let seenCourseIds = []; if (classes.length && courses.length) { for (const clazz of classes) { const course_data = clazz.course_data; const course = sortedClasses.find(x => x?.course?.id === course_data?.id); if (course) { course.classes.push(clazz); } else { sortedClasses.push({ course: courses.find(x => x.id === course_data.id), classes: [clazz], }); seenCourseIds.push( course_data.id ); } } } return ( <>
{sortedClasses.map(x => )} {courses.filter(x => !seenCourseIds.includes(x.id)).map(x => )}
); }; let classesCache = false; let sortCache = true; let tagFilterCache = false; export function ClassFeed(props) { const [classes, setClasses] = useState(classesCache); useEffect(() => { const get = async() => { requester('/sessions/', 'GET', '') .then(res => { setClasses(res.results); classesCache = res.results; }) .catch(err => { console.log(err); }); }; get(); const interval = setInterval(get, 60000); return () => clearInterval(interval); }, []); const now = new Date().toISOString(); return (

Upcoming Protospace Classes
{classes ? x.datetime > now).sort((a, b) => a.datetime > b.datetime ? 1 : -1)} /> :

Loading...

}

); }; export function Classes(props) { const [classes, setClasses] = useState(classesCache); const [courses, setCourses] = useState(false); const [search, setSearch] = useState(''); const [sortByCourse, setSortByCourse] = useState(sortCache); const [tagFilter, setTagFilter] = useState(tagFilterCache); const { token, user, refreshUser } = props; useEffect(() => { requester('/courses/', 'GET', token) .then(res => { setCourses(res.results); }) .catch(err => { console.log(err); }); }, []); useEffect(() => { requester('/sessions/', 'GET', token) .then(res => { setClasses(res.results); classesCache = res.results; }) .catch(err => { console.log(err); }); }, []); const byTeaching = (x) => x.instructor_id === user.member.id; const byDate = (a, b) => a.datetime > b.datetime ? 1 : -1; const classesByTag = (x) => tagFilter ? x.course_data.tags.includes(tagFilter) : true; const coursesByTag = (x) => tagFilter ? x.tags.includes(tagFilter) : true; const classesBySearch = (x) => search ? x.course_data.name.toLowerCase().includes(search.toLowerCase()) : true; const coursesBySearch = (x) => search ? x.name.toLowerCase().includes(search.toLowerCase()) : true; return (

Class List

View the list of all courses.

{!!user && !!classes.length && !!classes.filter(byTeaching).length && <>
Classes You're Teaching
}
setSearch(event.target.value)} aria-label='search products' style={{ margin: '0.5rem 0.5rem 0.5rem 0' }} /> {!!search.length &&

Filter by tag:

{Object.entries(tags).map(([name, color]) =>
)}
{classes.length && courses.length ? sortByCourse ? : :

Loading...

}
); }; export function ICalButtons(props) { const { token, clazz } = props; const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); const [error, setError] = useState(false); const handleDownload = (e) => { e.preventDefault(); window.location = apiUrl + '/sessions/' + clazz.id + '/download_ical/'; } const handleEmail = (e) => { e.preventDefault(); if (loading) return; setLoading(true); setSuccess(false); requester('/sessions/' + clazz.id + '/email_ical/', 'POST', token, {}) .then(res => { setLoading(false); setSuccess(true); }) .catch(err => { setLoading(false); console.log(err); setError(true); }); }; const addToGoogleCalendar = (e) => { e.preventDefault(); // construct and set the dates format that google calendar links require let starttime = moment(clazz.datetime); let endtime = starttime.clone().add(1, 'hour'); const datestringfmt = 'YYYYMMDDTkkmmss'; let dates = `${starttime.format(datestringfmt)}/${endtime.format(datestringfmt)}` // send user to google calendar window.location = `https://www.google.com/calendar/render?action=TEMPLATE&text=${clazz.course_data.name}&dates=${dates}`; }; const options = [ { key: 'email', icon: 'mail outline', text: 'Email ICS Event', value: 'Email', action: handleEmail }, { key: 'download', icon: 'download', text: 'Download ICS Event', value: 'Download', action: handleDownload }, { key: 'google', icon: 'google', text: 'Add to Google Calendar', value: 'Google', action: addToGoogleCalendar }, ]; // get default option from local storage or default to first item in options list const calendarValue = localStorage.getItem('calendarPreference') || 'Email'; const defaultOption = options.find(x => x.value === calendarValue); const [selectedOption, setOption] = useState(defaultOption); const onChange = (e, data) => { const newOption = options.find(x => x.value === data.value); setOption(newOption); // set the option as users preference localStorage.setItem('calendarPreference', newOption.value); }; return ( <> {success ? Sent! : } selectOnBlur={false} /> } {error &&

Error.

} ); }; export function ClassDetail(props) { const [clazz, setClass] = useState(false); const [refreshCount, refreshClass] = useReducer(x => x + 1, 0); const [error, setError] = useState(false); const [loading, setLoading] = useState(false); const [override, setOverride] = useState(false); const { token, user, refreshUser } = props; const { id } = useParams(); const userTraining = clazz && clazz.students.find(x => x.user === user.id); useEffect(() => { requester('/sessions/'+id+'/', 'GET', token) .then(res => { setClass(res); }) .catch(err => { console.log(err); setError(true); }); }, [refreshCount]); const handleSignup = () => { if (loading) return; setLoading(true); const data = { attendance_status: 'Waiting for payment', session: id }; requester('/training/', 'POST', token, data) .then(res => { refreshClass(); refreshUser(); }) .catch(err => { console.log(err); }); }; const handleToggle = (newStatus) => { if (loading) return; setLoading(true); const data = { attendance_status: newStatus, session: id }; requester('/training/'+userTraining.id+'/', 'PUT', token, data) .then(res => { refreshClass(); refreshUser(); }) .catch(err => { console.log(err); setError(true); }); }; useEffect(() => { setLoading(false); }, [userTraining]); const now = new Date().toISOString(); const signupDisabled = clazz && clazz.datetime < now && !override; return ( {!error ? clazz ?
Class Details
{(isAdmin(user) || clazz.instructor === user.id) && } Name: {clazz.course_data.name} Date: {moment.utc(clazz.datetime).tz('America/Edmonton').format('ll')} Time: {clazz.is_cancelled ? 'Cancelled' : moment.utc(clazz.datetime).tz('America/Edmonton').format('LT')} Instructor: {getInstructor(clazz)} Cost: {clazz.cost === '0.00' ? 'Free' : '$'+clazz.cost} Students: {clazz.student_count} {!!clazz.max_students && '/ '+clazz.max_students} Event:
Course Description
{clazz.course_data.is_old ? clazz.course_data.description.split('\n').map((x, i) =>

{x}

) :
}
Attendance
{(isAdmin(user) || clazz.instructor === user.id) && } {clazz.instructor !== user.id && (userTraining ?

Status: {userTraining.attendance_status}

{userTraining.attendance_status === 'Withdrawn' ? : }

{clazz.cost !== '0.00' && !userTraining.paid_date && userTraining.attendance_status !== 'Withdrawn' &&
{userTraining.attendance_status === 'Waiting for payment' ?

Please pay the course fee of ${clazz.cost} to confirm your attendance:

:

In case you haven't paid the course fee of ${clazz.cost} yet, you can do that here:

}

Current Protocoin balance: ₱ {user.member.protocoin.toFixed(2)}

{ refreshUser(); refreshClass(); }} custom={{ category: 'OnAcct', training: userTraining.id }} />
}
: (clazz.is_cancelled ?

The class is cancelled.

: ((clazz.max_students && clazz.student_count >= clazz.max_students) ?

The class is full.

: <> {clazz.datetime < now && <>

This class has already ran.

setOverride(v.checked)} />

} ) ) ) }
:

Loading...

: } ); };