Merge branch 'master' of github.com:Protospace/spaceport into webgl-footer
This commit is contained in:
@@ -21,7 +21,8 @@
|
||||
"react-to-print": "~2.5.1",
|
||||
"recharts": "~1.8.5",
|
||||
"semantic-ui-react": "~0.88.2",
|
||||
"three": "^0.119.1"
|
||||
"three": "^0.119.1",
|
||||
"serialize-javascript": "^3.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
BIN
webclient/public/wikilogo.png
Normal file
BIN
webclient/public/wikilogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@@ -123,7 +123,7 @@ export function AdminMemberCards(props) {
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [viewCard, setViewCard] = useState(false);
|
||||
const [cardPhoto, setCardPhoto] = useState(false);
|
||||
const { id } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -155,6 +155,18 @@ export function AdminMemberCards(props) {
|
||||
});
|
||||
};
|
||||
|
||||
const getCardPhoto = (e) => {
|
||||
e.preventDefault();
|
||||
requester('/members/' + id + '/card_photo/', 'GET', token)
|
||||
.then(res => res.blob())
|
||||
.then(res => {
|
||||
setCardPhoto(URL.createObjectURL(res));
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
const makeProps = (name) => ({
|
||||
name: name,
|
||||
onChange: handleChange,
|
||||
@@ -176,17 +188,17 @@ export function AdminMemberCards(props) {
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Header size='small'>Add a Card</Header>
|
||||
|
||||
{result.member.card_photo ?
|
||||
{result.member.photo_large ?
|
||||
<p>
|
||||
<Button onClick={() => setViewCard(true)}>View card image</Button>
|
||||
<Button onClick={(e) => getCardPhoto(e)}>View card image</Button>
|
||||
</p>
|
||||
:
|
||||
<p>No card image, member photo missing!</p>
|
||||
}
|
||||
|
||||
{viewCard && <>
|
||||
{cardPhoto && <>
|
||||
<p>
|
||||
<Image rounded size='medium' src={staticUrl + '/' + result.member.card_photo} />
|
||||
<Image rounded size='medium' src={cardPhoto} />
|
||||
</p>
|
||||
|
||||
<Header size='small'>How to Print a Card</Header>
|
||||
@@ -221,7 +233,15 @@ export function AdminMemberCards(props) {
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Button loading={loading} error={error.non_field_errors}>
|
||||
<Form.Checkbox
|
||||
label='Confirmed that the member has been given a tour and knows the alarm code'
|
||||
required
|
||||
{...makeProps('given_tour')}
|
||||
onChange={handleCheck}
|
||||
checked={input.given_tour}
|
||||
/>
|
||||
|
||||
<Form.Button disabled={!input.given_tour} loading={loading} error={error.non_field_errors}>
|
||||
Submit
|
||||
</Form.Button>
|
||||
{success && <div>Success!</div>}
|
||||
@@ -498,6 +518,12 @@ export function AdminMemberInfo(props) {
|
||||
<Table.Cell>Emergency Contact Phone:</Table.Cell>
|
||||
<Table.Cell>{member.emergency_contact_phone || 'None'}</Table.Cell>
|
||||
</Table.Row>
|
||||
|
||||
<Table.Row>
|
||||
<Table.Cell>On Spaceport:</Table.Cell>
|
||||
<Table.Cell>{member.user ? 'Yes' : 'No'}</Table.Cell>
|
||||
</Table.Row>
|
||||
|
||||
<Table.Row>
|
||||
<Table.Cell>Public Bio:</Table.Cell>
|
||||
</Table.Row>
|
||||
@@ -541,6 +567,7 @@ export function AdminCert(props) {
|
||||
|
||||
const handleCert = (e) => {
|
||||
e.preventDefault();
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
let data = Object();
|
||||
data[field] = moment.utc().tz('America/Edmonton').format('YYYY-MM-DD');
|
||||
@@ -555,6 +582,7 @@ export function AdminCert(props) {
|
||||
|
||||
const handleUncert = (e) => {
|
||||
e.preventDefault();
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
let data = Object();
|
||||
data[field] = null;
|
||||
@@ -646,6 +674,18 @@ export function AdminMemberCertifications(props) {
|
||||
<Table.Cell><Link to='/courses/259'>Tormach: CAM and Tormach Intro</Link></Table.Cell>
|
||||
<Table.Cell><AdminCert name='CNC' field='cnc_cert_date' {...props} /></Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Rabbit Laser</Table.Cell>
|
||||
<Table.Cell>{member.rabbit_cert_date ? 'Yes, ' + member.rabbit_cert_date : 'No'}</Table.Cell>
|
||||
<Table.Cell><Link to='/courses/247'>Laser: Cutting and Engraving</Link></Table.Cell>
|
||||
<Table.Cell><AdminCert name='Rabbit' field='rabbit_cert_date' {...props} /></Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Trotec Laser</Table.Cell>
|
||||
<Table.Cell>{member.trotec_cert_date ? 'Yes, ' + member.trotec_cert_date : 'No'}</Table.Cell>
|
||||
<Table.Cell><Link to='/courses/321'>Laser: Trotec Course</Link></Table.Cell>
|
||||
<Table.Cell><AdminCert name='Trotec' field='trotec_cert_date' {...props} /></Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
|
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useReducer, useContext } from 'react';
|
||||
import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } from 'react-router-dom';
|
||||
import './semantic-ui/semantic.min.css';
|
||||
import './light.css';
|
||||
import './dark.css';
|
||||
import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
|
||||
import Darkmode from 'darkmode-js';
|
||||
import { isAdmin, requester } from './utils.js';
|
||||
@@ -19,6 +20,7 @@ import { Courses, CourseDetail } from './Courses.js';
|
||||
import { Classes, ClassDetail } from './Classes.js';
|
||||
import { Members, MemberDetail } from './Members.js';
|
||||
import { Charts } from './Charts.js';
|
||||
import { Auth } from './Auth.js';
|
||||
import { PasswordReset, ConfirmReset } from './PasswordReset.js';
|
||||
import { NotFound, PleaseLogin } from './Misc.js';
|
||||
import { Footer } from './Footer.js';
|
||||
@@ -107,6 +109,10 @@ function App() {
|
||||
<img src='/logo-long.svg' className='logo-long' />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{window.location.hostname !== 'my.protospace.ca' &&
|
||||
<p style={{ background: 'yellow' }}>~~~~~ Development site ~~~~~</p>
|
||||
}
|
||||
</Container>
|
||||
|
||||
<Menu>
|
||||
@@ -216,6 +222,10 @@ function App() {
|
||||
<Charts />
|
||||
</Route>
|
||||
|
||||
<Route path='/auth'>
|
||||
<Auth user={user} />
|
||||
</Route>
|
||||
|
||||
{user && user.member.set_details ?
|
||||
<Switch>
|
||||
<Route path='/account'>
|
||||
|
132
webclient/src/Auth.js
Normal file
132
webclient/src/Auth.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import React, { useState, useEffect, useReducer } from 'react';
|
||||
import { BrowserRouter as Router, Switch, Route, Link, useParams, useLocation } from 'react-router-dom';
|
||||
import moment from 'moment-timezone';
|
||||
import './light.css';
|
||||
import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Popup, Segment, Table } from 'semantic-ui-react';
|
||||
import { statusColor, BasicTable, staticUrl, requester, isAdmin } from './utils.js';
|
||||
|
||||
export function AuthForm(props) {
|
||||
const { user } = props;
|
||||
const username = user ? user.username : '';
|
||||
const [input, setInput] = useState({ username: username });
|
||||
const [error, setError] = useState({});
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value });
|
||||
const handleChange = (e) => handleValues(e, e.currentTarget);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
if (input.username.includes('@')) {
|
||||
setError({ username: 'Username, not email.' });
|
||||
} else {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
const data = { ...input, username: input.username.toLowerCase() };
|
||||
requester('/spaceport-auth/login/', 'POST', '', data)
|
||||
.then(res => {
|
||||
setSuccess(true);
|
||||
setError({});
|
||||
})
|
||||
.catch(err => {
|
||||
setLoading(false);
|
||||
console.log(err);
|
||||
setError(err.data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
success ?
|
||||
props.children
|
||||
:
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
warning={error.non_field_errors && error.non_field_errors[0] === 'Unable to log in with provided credentials.'}
|
||||
>
|
||||
<Header size='medium'>Log In to Spaceport</Header>
|
||||
|
||||
{user ?
|
||||
<><Form.Input
|
||||
label='Spaceport Username'
|
||||
name='username'
|
||||
value={user.username}
|
||||
onChange={handleChange}
|
||||
error={error.username}
|
||||
/>
|
||||
<Form.Input
|
||||
label='Spaceport Password'
|
||||
name='password'
|
||||
type='password'
|
||||
onChange={handleChange}
|
||||
error={error.password}
|
||||
autoFocus
|
||||
/></>
|
||||
:
|
||||
<><Form.Input
|
||||
label='Spaceport Username'
|
||||
name='username'
|
||||
placeholder='first.last'
|
||||
onChange={handleChange}
|
||||
error={error.username}
|
||||
autoFocus
|
||||
/>
|
||||
<Form.Input
|
||||
label='Spaceport Password'
|
||||
name='password'
|
||||
type='password'
|
||||
onChange={handleChange}
|
||||
error={error.password}
|
||||
/></>
|
||||
}
|
||||
|
||||
<Form.Button loading={loading} error={error.non_field_errors}>
|
||||
Authorize
|
||||
</Form.Button>
|
||||
|
||||
<Message warning>
|
||||
<Message.Header>Forgot your password?</Message.Header>
|
||||
<p><Link to='/password/reset/'>Click here</Link> to reset it.</p>
|
||||
</Message>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export function AuthWiki(props) {
|
||||
const { user } = props;
|
||||
|
||||
return (
|
||||
<Segment compact padded>
|
||||
<Header size='medium'>
|
||||
<Image src={'/wikilogo.png'} />
|
||||
Protospace Wiki
|
||||
</Header>
|
||||
|
||||
<p>would like to request Spaceport authentication.</p>
|
||||
|
||||
<p>URL: <a href='http://wiki.protospace.ca/Welcome_to_Protospace' target='_blank' rel='noopener noreferrer'>wiki.protospace.ca</a></p>
|
||||
|
||||
<AuthForm user={user}>
|
||||
<Header size='small'>Success!</Header>
|
||||
<p>You can now log into the wiki:</p>
|
||||
<p><a href='http://wiki.protospace.ca/index.php?title=Special:UserLogin&returnto=Welcome+to+Protospace' rel='noopener noreferrer'>Protospace Wiki</a></p>
|
||||
</AuthForm>
|
||||
</Segment>
|
||||
);
|
||||
}
|
||||
|
||||
export function Auth(props) {
|
||||
const { user } = props;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Header size='large'>Spaceport Auth</Header>
|
||||
|
||||
<p>Use this page to link different applications to your Spaceport account.</p>
|
||||
|
||||
<Route path='/auth/wiki'>
|
||||
<AuthWiki user={user} />
|
||||
</Route>
|
||||
</Container>
|
||||
);
|
||||
}
|
@@ -13,6 +13,7 @@ export function Charts(props) {
|
||||
const [memberCount, setMemberCount] = useState(memberCountCache);
|
||||
const [signupCount, setSignupCount] = useState(signupCountCache);
|
||||
const [spaceActivity, setSpaceActivity] = useState(spaceActivityCache);
|
||||
const [fullActivity, setFullActivity] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
requester('/charts/membercount/', 'GET')
|
||||
@@ -47,6 +48,33 @@ export function Charts(props) {
|
||||
<Container>
|
||||
<Header size='large'>Charts</Header>
|
||||
|
||||
<Header size='medium'>Summary</Header>
|
||||
|
||||
{memberCount && signupCount &&
|
||||
<>
|
||||
<p>
|
||||
The total member count is {memberCount.slice().reverse()[0].member_count} members,
|
||||
compared to {memberCount.slice().reverse()[30].member_count} members 30 days ago.
|
||||
</p>
|
||||
<p>
|
||||
The green member count is {memberCount.slice().reverse()[0].green_count} members,
|
||||
compared to {memberCount.slice().reverse()[30].green_count} members 30 days ago.
|
||||
</p>
|
||||
<p>
|
||||
The older than six months member count is {memberCount.slice().reverse()[0].six_month_plus_count} members,
|
||||
compared to {memberCount.slice().reverse()[30].six_month_plus_count} members 30 days ago.
|
||||
</p>
|
||||
<p>
|
||||
The vetted member count is {memberCount.slice().reverse()[0].vetted_count} members,
|
||||
compared to {memberCount.slice().reverse()[30].vetted_count} members 30 days ago.
|
||||
</p>
|
||||
<p>
|
||||
There were {signupCount.slice().reverse()[0].signup_count} signups so far this month,
|
||||
and {signupCount.slice().reverse()[1].signup_count} signups last month.
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
|
||||
<Header size='medium'>Member Counts</Header>
|
||||
|
||||
<p>Daily since March 2nd, 2020.</p>
|
||||
@@ -87,18 +115,99 @@ export function Charts(props) {
|
||||
}
|
||||
</p>
|
||||
|
||||
<p>The Member Count is the amount of Prepaid, Current, Due, and Overdue members on Spaceport.</p>
|
||||
<p>Member Count: number of active paying members on Spaceport.</p>
|
||||
|
||||
<p>The Green Count is the amount of Prepaid and Current members.</p>
|
||||
<p>Green Count: number of Prepaid and Current members.</p>
|
||||
|
||||
<p>
|
||||
{memberCount &&
|
||||
<ResponsiveContainer width='100%' height={300}>
|
||||
<LineChart data={memberCount}>
|
||||
<XAxis dataKey='date' minTickGap={10} />
|
||||
<YAxis />
|
||||
<CartesianGrid strokeDasharray='3 3'/>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
|
||||
<Line
|
||||
type='monotone'
|
||||
dataKey='member_count'
|
||||
name='Member Count'
|
||||
stroke='#8884d8'
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
animationDuration={1000}
|
||||
/>
|
||||
<Line
|
||||
type='monotone'
|
||||
dataKey='six_month_plus_count'
|
||||
name='Six Months+'
|
||||
stroke='red'
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
animationDuration={1500}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
}
|
||||
</p>
|
||||
|
||||
<p>Member Count: same as above.</p>
|
||||
|
||||
<p>Six Months+: number of active memberships older than six months.</p>
|
||||
|
||||
<p>
|
||||
{memberCount &&
|
||||
<ResponsiveContainer width='100%' height={300}>
|
||||
<LineChart data={memberCount}>
|
||||
<XAxis dataKey='date' minTickGap={10} />
|
||||
<YAxis />
|
||||
<CartesianGrid strokeDasharray='3 3'/>
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
|
||||
<Line
|
||||
type='monotone'
|
||||
dataKey='member_count'
|
||||
name='Member Count'
|
||||
stroke='#8884d8'
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
animationDuration={1000}
|
||||
/>
|
||||
<Line
|
||||
type='monotone'
|
||||
dataKey='vetted_count'
|
||||
name='Vetted Count'
|
||||
stroke='purple'
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
animationDuration={1500}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
}
|
||||
</p>
|
||||
|
||||
<p>Member Count: same as above.</p>
|
||||
|
||||
<p>Vetted Count: number of active vetted members.</p>
|
||||
|
||||
<Header size='medium'>Space Activity</Header>
|
||||
|
||||
<p>Daily since March 7th, 2020, updates hourly.</p>
|
||||
{fullActivity ?
|
||||
<p>Daily since March 7th, 2020, updates hourly.</p>
|
||||
:
|
||||
<p>
|
||||
Last four weeks, updates hourly.
|
||||
{' '}<Button size='tiny' onClick={() => setFullActivity(true)} >View All</Button>
|
||||
</p>
|
||||
}
|
||||
|
||||
<p>
|
||||
{spaceActivity &&
|
||||
<ResponsiveContainer width='100%' height={300}>
|
||||
<BarChart data={spaceActivity}>
|
||||
<BarChart data={fullActivity ? spaceActivity : spaceActivity.slice(-28)}>
|
||||
<XAxis dataKey='date' minTickGap={10} />
|
||||
<YAxis />
|
||||
<CartesianGrid strokeDasharray='3 3'/>
|
||||
@@ -111,14 +220,14 @@ export function Charts(props) {
|
||||
name='Card Scans'
|
||||
fill='#8884d8'
|
||||
maxBarSize={20}
|
||||
animationDuration={1000}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
}
|
||||
</p>
|
||||
|
||||
<p>Cards Scans is the number of individual members who have scanned to enter the space.</p>
|
||||
<p>Cards Scans: number of individual members who have scanned to enter the space.</p>
|
||||
|
||||
<Header size='medium'>Signup Count</Header>
|
||||
|
||||
@@ -146,14 +255,14 @@ export function Charts(props) {
|
||||
type='monotone'
|
||||
dataKey='vetted_count'
|
||||
fill='#80b3d3'
|
||||
name='Vetted Count'
|
||||
name='Later Vetted Count'
|
||||
maxBarSize={20}
|
||||
animationDuration={1200}
|
||||
/>
|
||||
<Bar
|
||||
type='monotone'
|
||||
dataKey='retain_count'
|
||||
name='Retain Count'
|
||||
name='Retained Count'
|
||||
fill='#82ca9d'
|
||||
maxBarSize={20}
|
||||
animationDuration={1400}
|
||||
@@ -163,11 +272,11 @@ export function Charts(props) {
|
||||
}
|
||||
</p>
|
||||
|
||||
<p>The Signup Count is the number of brand new account registrations that month.</p>
|
||||
<p>Signup Count: number of brand new account registrations that month.</p>
|
||||
|
||||
<p>The Vetted Count is the number of those signups who eventually got vetted (at a later date).</p>
|
||||
<p>Later Vetted Count: number of those signups who eventually got vetted (at a later date).</p>
|
||||
|
||||
<p>The Retain Count is the number of those signups who are still a member currently.</p>
|
||||
<p>Retained Count: number of those signups who are still a member currently.</p>
|
||||
|
||||
</Container>
|
||||
);
|
||||
|
@@ -72,13 +72,19 @@ export function Classes(props) {
|
||||
<Header size='large'>Class List</Header>
|
||||
|
||||
<Header size='medium'>Upcoming</Header>
|
||||
|
||||
<p>Ordered by nearest date.</p>
|
||||
|
||||
{classes ?
|
||||
<ClassTable classes={classes.filter(x => x.datetime > now)} />
|
||||
<ClassTable classes={classes.filter(x => x.datetime > now).sort((a, b) => a.datetime > b.datetime ? 1 : -1)} />
|
||||
:
|
||||
<p>Loading...</p>
|
||||
}
|
||||
|
||||
<Header size='medium'>Recent</Header>
|
||||
|
||||
<p>Ordered by nearest date.</p>
|
||||
|
||||
{classes ?
|
||||
<ClassTable classes={classes.filter(x => x.datetime < now)} />
|
||||
:
|
||||
@@ -92,6 +98,7 @@ 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 { token, user, refreshUser } = props;
|
||||
const { id } = useParams();
|
||||
const userTraining = clazz && clazz.students.find(x => x.user == user.id);
|
||||
@@ -108,6 +115,8 @@ export function ClassDetail(props) {
|
||||
}, [refreshCount]);
|
||||
|
||||
const handleSignup = () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
const data = { attendance_status: 'Waiting for payment', session: id };
|
||||
requester('/training/', 'POST', token, data)
|
||||
.then(res => {
|
||||
@@ -120,6 +129,8 @@ export function ClassDetail(props) {
|
||||
};
|
||||
|
||||
const handleToggle = (newStatus) => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
const data = { attendance_status: newStatus, session: id };
|
||||
requester('/training/'+userTraining.id+'/', 'PUT', token, data)
|
||||
.then(res => {
|
||||
@@ -132,6 +143,10 @@ export function ClassDetail(props) {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(false);
|
||||
}, [userTraining]);
|
||||
|
||||
// TODO: calculate yesterday and lock signups
|
||||
|
||||
return (
|
||||
@@ -198,11 +213,11 @@ export function ClassDetail(props) {
|
||||
<p>Status: {userTraining.attendance_status}</p>
|
||||
<p>
|
||||
{userTraining.attendance_status === 'Withdrawn' ?
|
||||
<Button onClick={() => handleToggle('Waiting for payment')}>
|
||||
<Button loading={loading} onClick={() => handleToggle('Waiting for payment')}>
|
||||
Sign back up
|
||||
</Button>
|
||||
:
|
||||
<Button onClick={() => handleToggle('Withdrawn')}>
|
||||
<Button loading={loading} onClick={() => handleToggle('Withdrawn')}>
|
||||
Withdraw from Class
|
||||
</Button>
|
||||
}
|
||||
@@ -226,7 +241,7 @@ export function ClassDetail(props) {
|
||||
((clazz.max_students && clazz.student_count >= clazz.max_students) ?
|
||||
<p>The class is full.</p>
|
||||
:
|
||||
<Button onClick={handleSignup}>
|
||||
<Button loading={loading} onClick={handleSignup}>
|
||||
Sign me up!
|
||||
</Button>
|
||||
)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useReducer } from 'react';
|
||||
import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Switch, Route, Link, useParams, useLocation } from 'react-router-dom';
|
||||
import moment from 'moment-timezone';
|
||||
import './light.css';
|
||||
import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Popup, Segment, Table } from 'semantic-ui-react';
|
||||
@@ -128,12 +128,15 @@ function MemberInfo(props) {
|
||||
};
|
||||
|
||||
export function Home(props) {
|
||||
const { user } = props;
|
||||
const { user, token } = props;
|
||||
const [stats, setStats] = useState(JSON.parse(localStorage.getItem('stats', 'false')));
|
||||
const [refreshCount, refreshStats] = useReducer(x => x + 1, 0);
|
||||
const location = useLocation();
|
||||
|
||||
const bypass_code = location.hash.replace('#', '');
|
||||
|
||||
useEffect(() => {
|
||||
requester('/stats/', 'GET')
|
||||
requester('/stats/', 'GET', token)
|
||||
.then(res => {
|
||||
setStats(res);
|
||||
localStorage.setItem('stats', JSON.stringify(res));
|
||||
@@ -142,17 +145,21 @@ export function Home(props) {
|
||||
console.log(err);
|
||||
setStats(false);
|
||||
});
|
||||
}, [refreshCount]);
|
||||
}, [refreshCount, token]);
|
||||
|
||||
const getStat = (x) => stats && stats[x] ? stats[x] : '?';
|
||||
const getZeroStat = (x) => stats && stats[x] ? stats[x] : '0';
|
||||
const getDateStat = (x) => stats && stats[x] ? moment.utc(stats[x]).tz('America/Edmonton').format('ll') : '?';
|
||||
|
||||
const mcPlayers = stats && stats['minecraft_players'] ? stats['minecraft_players'] : [];
|
||||
const mumbleUsers = stats && stats['mumble_users'] ? stats['mumble_users'] : [];
|
||||
|
||||
const getTrackStat = (x) => stats && stats.track && stats.track[x] ? moment().unix() - stats.track[x] > 60 ? 'Free' : 'In Use' : '?';
|
||||
const getTrackLast = (x) => stats && stats.track && stats.track[x] ? moment.unix(stats.track[x]).tz('America/Edmonton').format('llll') : 'Unknown';
|
||||
const getTrackAgo = (x) => stats && stats.track && stats.track[x] ? moment.unix(stats.track[x]).tz('America/Edmonton').fromNow() : '';
|
||||
const getTrackStat = (x) => stats && stats.track && stats.track[x] ? moment().unix() - stats.track[x]['time'] > 60 ? 'Free' : 'In Use' : '?';
|
||||
const getTrackLast = (x) => stats && stats.track && stats.track[x] ? moment.unix(stats.track[x]['time']).tz('America/Edmonton').format('llll') : 'Unknown';
|
||||
const getTrackAgo = (x) => stats && stats.track && stats.track[x] ? moment.unix(stats.track[x]['time']).tz('America/Edmonton').fromNow() : '';
|
||||
const getTrackName = (x) => stats && stats.track && stats.track[x] && stats.track[x]['username'] ? stats.track[x]['username'] : 'Unknown';
|
||||
|
||||
const alarmStat = () => stats && stats.alarm && moment().unix() - stats.alarm['time'] < 300 ? stats.alarm['data'] > 200 ? 'Armed' : 'Disarmed' : 'Unknown';
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@@ -172,9 +179,18 @@ export function Home(props) {
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<LoginForm {...props} />
|
||||
{bypass_code ?
|
||||
<Message warning>
|
||||
<Message.Header>Outside Registration</Message.Header>
|
||||
<p>This page allows you to sign up from outside of Protospace.</p>
|
||||
</Message>
|
||||
:
|
||||
<>
|
||||
<LoginForm {...props} />
|
||||
|
||||
<Divider section horizontal>Or</Divider>
|
||||
<Divider section horizontal>Or</Divider>
|
||||
</>
|
||||
}
|
||||
|
||||
<SignupForm {...props} />
|
||||
</div>
|
||||
@@ -201,11 +217,10 @@ export function Home(props) {
|
||||
<p>Next monthly clean: {getDateStat('next_clean')}</p>
|
||||
<p>Member count: {getStat('member_count')} <Link to='/charts'>[more]</Link></p>
|
||||
<p>Green members: {getStat('green_count')}</p>
|
||||
<p>Old members: {getStat('paused_count')}</p>
|
||||
<p>Card scans today: {getZeroStat('card_scans')}</p>
|
||||
|
||||
<p>
|
||||
Minecraft players: {mcPlayers.length} <Popup content={
|
||||
Minecraft players: {mcPlayers.length} {mcPlayers.length > 5 && '🔥'} <Popup content={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
Server IP:<br />
|
||||
@@ -217,6 +232,22 @@ export function Home(props) {
|
||||
</p>
|
||||
</React.Fragment>
|
||||
} trigger={<a>[more]</a>} />
|
||||
{' '}<a href='http://games.protospace.ca:8123/?worldname=world&mapname=flat&zoom=3&x=74&y=64&z=354' target='_blank'>[map]</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Mumble users: {mumbleUsers.length} <Popup content={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
Server IP:<br />
|
||||
mumble.protospace.ca
|
||||
</p>
|
||||
<p>
|
||||
Users:<br />
|
||||
{mumbleUsers.length ? mumbleUsers.map(x => <React.Fragment>{x}<br /></React.Fragment>) : 'None'}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
} trigger={<a>[more]</a>} />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -225,7 +256,8 @@ export function Home(props) {
|
||||
<p>
|
||||
Last use:<br />
|
||||
{getTrackLast('TROTECS300')}<br />
|
||||
{getTrackAgo('TROTECS300')}
|
||||
{getTrackAgo('TROTECS300')}<br />
|
||||
by {getTrackName('TROTECS300')}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
} trigger={<a>[more]</a>} />
|
||||
@@ -237,11 +269,14 @@ export function Home(props) {
|
||||
<p>
|
||||
Last use:<br />
|
||||
{getTrackLast('FRICKIN-LASER')}<br />
|
||||
{getTrackAgo('FRICKIN-LASER')}
|
||||
{getTrackAgo('FRICKIN-LASER')}<br />
|
||||
by {getTrackName('FRICKIN-LASER')}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
} trigger={<a>[more]</a>} />
|
||||
</p>
|
||||
|
||||
{user && user.member.vetted_date && <p>Alarm status: {alarmStat()}</p>}
|
||||
</div>
|
||||
|
||||
</Segment>
|
||||
|
@@ -68,8 +68,12 @@ class AttendanceSheet extends React.Component {
|
||||
function AttendanceRow(props) {
|
||||
const { student, token, refreshClass } = props;
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleMark = (newStatus) => {
|
||||
if (loading) return;
|
||||
if (student.attendance_status == newStatus) return;
|
||||
setLoading(newStatus);
|
||||
const data = { ...student, attendance_status: newStatus };
|
||||
requester('/training/'+student.id+'/', 'PATCH', token, data)
|
||||
.then(res => {
|
||||
@@ -86,11 +90,19 @@ function AttendanceRow(props) {
|
||||
onClick: () => handleMark(name),
|
||||
toggle: true,
|
||||
active: student.attendance_status === name,
|
||||
loading: loading === name,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(false);
|
||||
}, [student.attendance_status]);
|
||||
|
||||
return (
|
||||
<div className='attendance-row'>
|
||||
<p>{student.student_name}:</p>
|
||||
<p>
|
||||
<Link to={'/members/'+student.student_id}>{student.student_name}</Link>
|
||||
{student.attendance_status === 'Waiting for payment' && ' (Waiting for payment)'}:
|
||||
</p>
|
||||
|
||||
<Button {...makeProps('Withdrawn')}>
|
||||
Withdrawn
|
||||
@@ -118,9 +130,11 @@ function AttendanceRow(props) {
|
||||
);
|
||||
}
|
||||
|
||||
let attendanceOpenCache = false;
|
||||
|
||||
export function InstructorClassAttendance(props) {
|
||||
const { clazz, token, refreshClass, user } = props;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(attendanceOpenCache);
|
||||
const [input, setInput] = useState({});
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -201,7 +215,7 @@ export function InstructorClassAttendance(props) {
|
||||
</Form>
|
||||
</div>
|
||||
:
|
||||
<Button onClick={() => setOpen(true)}>
|
||||
<Button onClick={() => {setOpen(true); attendanceOpenCache = true;}}>
|
||||
Edit Attendance
|
||||
</Button>
|
||||
}
|
||||
@@ -321,7 +335,7 @@ export function InstructorClassDetail(props) {
|
||||
export function InstructorClassList(props) {
|
||||
const { course, setCourse, token } = props;
|
||||
const [open, setOpen] = useState(false);
|
||||
const [input, setInput] = useState({});
|
||||
const [input, setInput] = useState({ max_students: null });
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
@@ -110,7 +110,7 @@ export function SignupForm(props) {
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Header size='medium'>Sign Up from Protospace</Header>
|
||||
<Header size='medium'>Sign Up to Spaceport</Header>
|
||||
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
|
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useReducer } from 'react';
|
||||
import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
|
||||
import './light.css';
|
||||
import { Button, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Input, Item, Menu, Message, Segment, Table } from 'semantic-ui-react';
|
||||
import { statusColor, isAdmin, BasicTable, staticUrl, requester } from './utils.js';
|
||||
import { statusColor, isAdmin, isInstructor, BasicTable, staticUrl, requester } from './utils.js';
|
||||
import { NotFound, PleaseLogin } from './Misc.js';
|
||||
import { AdminMemberInfo, AdminMemberPause, AdminMemberForm, AdminMemberCards, AdminMemberTraining, AdminMemberCertifications } from './AdminMembers.js';
|
||||
import { AdminMemberTransactions } from './AdminTransactions.js';
|
||||
@@ -107,7 +107,7 @@ export function Members(props) {
|
||||
{x.member.preferred_name} {x.member.last_name}
|
||||
</Item.Header>
|
||||
<Item.Description>Status: {x.member.status || 'Unknown'}</Item.Description>
|
||||
<Item.Description>Joined: {x.member.current_start_date || 'Unknown'}</Item.Description>
|
||||
<Item.Description>Joined: {x.member.application_date || 'Unknown'}</Item.Description>
|
||||
</Item.Content>
|
||||
</Item>
|
||||
)
|
||||
@@ -154,7 +154,7 @@ export function MemberDetail(props) {
|
||||
<Header size='large'>{member.preferred_name} {member.last_name}</Header>
|
||||
|
||||
<Grid stackable columns={2}>
|
||||
<Grid.Column>
|
||||
<Grid.Column width={isAdmin(user) ? 8 : 5}>
|
||||
<p>
|
||||
<Image rounded size='medium' src={member.photo_large ? staticUrl + '/' + member.photo_large : '/nophoto.png'} />
|
||||
</p>
|
||||
@@ -174,7 +174,7 @@ export function MemberDetail(props) {
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Joined:</Table.Cell>
|
||||
<Table.Cell>{member.current_start_date || 'Unknown'}</Table.Cell>
|
||||
<Table.Cell>{member.application_date || 'Unknown'}</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Public Bio:</Table.Cell>
|
||||
@@ -189,7 +189,11 @@ export function MemberDetail(props) {
|
||||
}
|
||||
</Grid.Column>
|
||||
|
||||
<Grid.Column>
|
||||
<Grid.Column width={isAdmin(user) ? 8 : 11}>
|
||||
{isInstructor(user) && !isAdmin(user) && <Segment padded>
|
||||
<AdminMemberTraining result={result} refreshResult={refreshResult} {...props} />
|
||||
</Segment>}
|
||||
|
||||
{isAdmin(user) && <Segment padded>
|
||||
<AdminMemberForm result={result} refreshResult={refreshResult} {...props} />
|
||||
</Segment>}
|
||||
|
@@ -37,7 +37,7 @@ function ResetForm() {
|
||||
});
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form onSubmit={handleSubmit} error={error.email == 'Not found.'}>
|
||||
<Form.Input
|
||||
label='Email'
|
||||
name='email'
|
||||
@@ -45,10 +45,16 @@ function ResetForm() {
|
||||
error={error.email}
|
||||
/>
|
||||
|
||||
<Message
|
||||
error
|
||||
header='Email not found in Spaceport'
|
||||
content='You can only use this form if you have an account with this new member portal.'
|
||||
/>
|
||||
|
||||
<Form.Button loading={loading} error={error.non_field_errors}>
|
||||
Submit
|
||||
</Form.Button>
|
||||
{success && <div>Success!</div>}
|
||||
{success && <div>Success! Be sure to check your spam folder.</div>}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
@@ -13,6 +13,8 @@ export function Paymaster(props) {
|
||||
const [locker, setLocker] = useState('5.00');
|
||||
const [donate, setDonate] = useState('20.00');
|
||||
|
||||
const monthly_fees = user.member.monthly_fees || 55;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Header size='large'>Paymaster</Header>
|
||||
@@ -62,27 +64,27 @@ export function Paymaster(props) {
|
||||
<Header size='medium'>Member Dues</Header>
|
||||
<Grid stackable padded columns={3}>
|
||||
<Grid.Column>
|
||||
<p>Pay ${user.member.monthly_fees}.00 once:</p>
|
||||
<p>Pay ${monthly_fees}.00 once:</p>
|
||||
<PayPalPayNow
|
||||
amount={user.member.monthly_fees}
|
||||
amount={monthly_fees}
|
||||
name='Protospace Membership'
|
||||
custom={JSON.stringify({ member: user.member.id })}
|
||||
/>
|
||||
</Grid.Column>
|
||||
|
||||
<Grid.Column>
|
||||
<p>Subscribe ${user.member.monthly_fees}.00 / month:</p>
|
||||
<p>Subscribe ${monthly_fees}.00 / month:</p>
|
||||
<PayPalSubscribe
|
||||
amount={user.member.monthly_fees}
|
||||
amount={monthly_fees}
|
||||
name='Protospace Membership'
|
||||
custom={JSON.stringify({ member: user.member.id })}
|
||||
/>
|
||||
</Grid.Column>
|
||||
|
||||
<Grid.Column>
|
||||
<p>Pay ${user.member.monthly_fees * 11}.00 for a year:</p>
|
||||
<p>Pay ${monthly_fees * 11}.00 for a year:</p>
|
||||
<PayPalPayNow
|
||||
amount={user.member.monthly_fees * 11}
|
||||
amount={monthly_fees * 11}
|
||||
name='Protospace Membership'
|
||||
custom={JSON.stringify({ deal: 12, member: user.member.id })}
|
||||
/>
|
||||
|
@@ -57,6 +57,16 @@ export function CertList(props) {
|
||||
<Table.Cell>{member.cnc_cert_date ? 'Yes, ' + member.cnc_cert_date : 'No'}</Table.Cell>
|
||||
<Table.Cell><Link to='/courses/259'>Tormach: CAM and Tormach Intro</Link></Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Rabbit Laser</Table.Cell>
|
||||
<Table.Cell>{member.rabbit_cert_date ? 'Yes, ' + member.rabbit_cert_date : 'No'}</Table.Cell>
|
||||
<Table.Cell><Link to='/courses/247'>Laser: Cutting and Engraving</Link></Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>Trotec Laser</Table.Cell>
|
||||
<Table.Cell>{member.trotec_cert_date ? 'Yes, ' + member.trotec_cert_date : 'No'}</Table.Cell>
|
||||
<Table.Cell><Link to='/courses/321'>Laser: Trotec Course</Link></Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
);
|
||||
|
@@ -23,14 +23,14 @@ export function TransactionEditor(props) {
|
||||
});
|
||||
|
||||
const accountOptions = [
|
||||
{ key: '0', text: 'Cash (CAD Lock Box)', value: 'Cash' },
|
||||
{ key: '0', text: 'Cash (Lock Box)', value: 'Cash' },
|
||||
{ key: '1', text: 'Interac (Email) Transfer (TD)', value: 'Interac' },
|
||||
{ key: '2', text: 'Square (Credit)', value: 'Square Pmt' },
|
||||
{ key: '3', text: 'Dream Payments (Debit/Credit)', value: 'Dream Pmt' },
|
||||
{ key: '4', text: 'Deposit to TD (Not Interac)', value: 'TD Chequing' },
|
||||
{ key: '5', text: 'PayPal', value: 'PayPal' },
|
||||
{ key: '6', text: 'Member Balance / Protocash', value: 'Member' },
|
||||
{ key: '7', text: 'Supense (Clearing) Acct / Membership Adjustment', value: 'Clearing' },
|
||||
{ key: '2', text: 'Square (Credit Card)', value: 'Square Pmt' },
|
||||
//{ key: '3', text: 'Dream Payments (Debit/Credit)', value: 'Dream Pmt' },
|
||||
{ key: '4', text: 'Cheque / Deposit to TD', value: 'TD Chequing' },
|
||||
//{ key: '5', text: 'Member Balance / Protocash', value: 'Member' },
|
||||
{ key: '6', text: 'Membership Adjustment / Clearing', value: 'Clearing' },
|
||||
{ key: '7', text: 'PayPal', value: 'PayPal' },
|
||||
];
|
||||
|
||||
const sourceOptions = [
|
||||
@@ -53,9 +53,9 @@ export function TransactionEditor(props) {
|
||||
{ key: '1', text: 'Payment On Account (ie. Course Fee)', value: 'OnAcct' },
|
||||
{ key: '2', text: 'Snack / Pop / Coffee', value: 'Snacks' },
|
||||
{ key: '3', text: 'Donations', value: 'Donation' },
|
||||
{ key: '4', text: 'Consumables (Specify which in memo)', value: 'Consumables' },
|
||||
{ key: '4', text: 'Consumables (Explain in memo)', value: 'Consumables' },
|
||||
{ key: '5', text: 'Purchase of Locker / Goods / Merch / Stock', value: 'Purchases' },
|
||||
{ key: '6', text: 'Auction, Garage Sale, Nearly Free Shelf', value: 'Garage Sale' },
|
||||
//{ key: '6', text: 'Auction, Garage Sale, Nearly Free Shelf', value: 'Garage Sale' },
|
||||
{ key: '7', text: 'Reimbursement (Enter a negative value)', value: 'Reimburse' },
|
||||
{ key: '8', text: 'Other (Explain in memo)', value: 'Other' },
|
||||
];
|
||||
@@ -94,14 +94,14 @@ export function TransactionEditor(props) {
|
||||
/>
|
||||
|
||||
<Form.Select
|
||||
label='Account'
|
||||
label='Payment Method / Account'
|
||||
fluid
|
||||
options={accountOptions}
|
||||
{...makeProps('account_type')}
|
||||
onChange={handleValues}
|
||||
/>
|
||||
|
||||
<Form.Group widths='equal'>
|
||||
{/* <Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label='Payment Method'
|
||||
fluid
|
||||
@@ -114,7 +114,7 @@ export function TransactionEditor(props) {
|
||||
{...makeProps('info_source')}
|
||||
onChange={handleValues}
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form.Group> */}
|
||||
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
@@ -124,7 +124,7 @@ export function TransactionEditor(props) {
|
||||
/>
|
||||
|
||||
<Form.Input
|
||||
label='# Membership Months'
|
||||
label='Number of Membership Months'
|
||||
fluid
|
||||
{...makeProps('number_of_membership_months')}
|
||||
/>
|
||||
@@ -349,10 +349,10 @@ class TransactionTable extends React.Component {
|
||||
<Table.Cell>Account:</Table.Cell>
|
||||
<Table.Cell>{transaction.account_type}</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
{/* <Table.Row>
|
||||
<Table.Cell>Payment Method:</Table.Cell>
|
||||
<Table.Cell>{transaction.payment_method}</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Row> */}
|
||||
<Table.Row>
|
||||
<Table.Cell>Info Source:</Table.Cell>
|
||||
<Table.Cell>{transaction.info_source}</Table.Cell>
|
||||
|
57
webclient/src/dark.css
Normal file
57
webclient/src/dark.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.darkmode-layer, .darkmode-toggle {
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
.darkmode--activated .ui.image {
|
||||
mix-blend-mode: difference;
|
||||
filter: brightness(75%);
|
||||
}
|
||||
|
||||
.darkmode--activated i.green.circle.icon {
|
||||
mix-blend-mode: difference;
|
||||
color: #21ba4582 !important;
|
||||
|
||||
}
|
||||
|
||||
.darkmode--activated i.yellow.circle.icon {
|
||||
mix-blend-mode: difference;
|
||||
color: #fbbd0882 !important;
|
||||
}
|
||||
|
||||
.darkmode--activated i.red.circle.icon {
|
||||
mix-blend-mode: difference;
|
||||
color: #db282882 !important;
|
||||
}
|
||||
|
||||
.darkmode--activated .footer {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.darkmode--activated .ql-toolbar.ql-snow,
|
||||
.darkmode--activated .ql-container.ql-snow,
|
||||
.darkmode--activated .ui.segment,
|
||||
.darkmode--activated .ui.form .field input,
|
||||
.darkmode--activated .ui.form .field .selection.dropdown,
|
||||
.darkmode--activated .ui.form .field .ui.checkbox label::before,
|
||||
.darkmode--activated .ui.form .field textarea {
|
||||
border: 1px solid rgba(34,36,38,.50) !important;
|
||||
}
|
||||
|
||||
.darkmode--activated .ui.basic.table tbody tr {
|
||||
border-bottom: 1px solid rgba(34,36,38,.50) !important;
|
||||
}
|
||||
|
||||
.darkmode--activated .ui.button {
|
||||
background: #c9c9c9 !important;
|
||||
}
|
||||
|
||||
.darkmode--activated .ui.red.button {
|
||||
mix-blend-mode: difference;
|
||||
background: #db282882 !important;
|
||||
}
|
||||
|
||||
.darkmode--activated .ui.green.button,
|
||||
.darkmode--activated .ui.button.toggle.active {
|
||||
mix-blend-mode: difference;
|
||||
background: #21ba4582 !important;
|
||||
}
|
@@ -70,7 +70,17 @@ export const requester = (route, method, token, data) => {
|
||||
if (!response.ok) {
|
||||
throw customError(response);
|
||||
}
|
||||
return method === 'DELETE' ? {} : response.json();
|
||||
|
||||
if (method === 'DELETE') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.indexOf('application/json') !== -1) {
|
||||
return response.json();
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
const code = error.data ? error.data.status : null;
|
||||
|
@@ -3664,7 +3664,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
|
||||
debug@^3.1.1, debug@^3.2.5:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
@@ -4376,9 +4376,9 @@ eventemitter3@^2.0.3:
|
||||
integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
|
||||
|
||||
eventemitter3@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
|
||||
integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
|
||||
version "4.0.7"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
||||
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
||||
|
||||
events@^3.0.0:
|
||||
version "3.1.0"
|
||||
@@ -4767,11 +4767,9 @@ flush-write-stream@^1.0.0:
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.10.0.tgz#01f5263aee921c6a54fb91667f08f4155ce169eb"
|
||||
integrity sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==
|
||||
dependencies:
|
||||
debug "^3.0.0"
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
|
||||
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
|
||||
|
||||
for-in@^0.1.3:
|
||||
version "0.1.8"
|
||||
@@ -5335,9 +5333,9 @@ http-proxy-middleware@0.19.1:
|
||||
micromatch "^3.1.10"
|
||||
|
||||
http-proxy@^1.17.0:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"
|
||||
integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==
|
||||
version "1.18.1"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
|
||||
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
|
||||
dependencies:
|
||||
eventemitter3 "^4.0.0"
|
||||
follow-redirects "^1.0.0"
|
||||
@@ -8842,7 +8840,7 @@ raf@^3.4.0, raf@^3.4.1:
|
||||
dependencies:
|
||||
performance-now "^2.1.0"
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
|
||||
@@ -9754,6 +9752,13 @@ serialize-javascript@^2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
|
||||
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
|
||||
|
||||
serialize-javascript@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea"
|
||||
integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serve-index@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
|
||||
|
Reference in New Issue
Block a user