diff --git a/webclient/src/App.js b/webclient/src/App.js
index 5a57797..d0ddcf5 100644
--- a/webclient/src/App.js
+++ b/webclient/src/App.js
@@ -4,541 +4,10 @@ import './light.css';
import Logo from './logo.svg';
import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
import { requester } from './utils.js';
-
-function LoginForm(props) {
- const [input, setInput] = useState({});
- const [error, setError] = useState({});
- 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) => {
- setLoading(true);
- requester('/rest-auth/login/', 'POST', '', input)
- .then(res => {
- console.log(res);
- setError({});
- props.setTokenCache(res.key);
- })
- .catch(err => {
- setLoading(false);
- console.log(err);
- setError(err.data);
- });
- };
-
- return (
-
-
-
- Login
-
-
- );
-};
-
-function SignupForm(props) {
- const [input, setInput] = useState({});
- const [error, setError] = useState({});
- const [loading, setLoading] = useState(false);
-
- const handleValues = (e, v) => setInput({
- ...input,
- [v.name]: v.value
- });
-
- const handleChange = (e) => handleValues(e, e.currentTarget);
-
- const genUsername = () => (
- input.first_name && input.last_name ?
- (input.first_name + '.' + input.last_name).toLowerCase()
- :
- ''
- );
-
- const handleSubmit = (e) => {
- setLoading(true);
- input.username = genUsername();
- requester('/registration/', 'POST', '', input)
- .then(res => {
- console.log(res);
- setError({});
- props.setTokenCache(res.key);
- })
- .catch(err => {
- setLoading(false);
- console.log(err);
- setError(err.data);
- });
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sign Up
-
-
- );
-};
-
-function DetailsForm(props) {
- const member = props.user.member;
- const [input, setInput] = useState({
- preferred_name: member.preferred_name,
- phone: member.phone,
- emergency_contact_name: member.emergency_contact_name,
- emergency_contact_phone: member.emergency_contact_phone,
- set_details: true,
- });
- const [error, setError] = useState({});
- 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) => {
- setLoading(true);
- requester('/members/' + member.id + '/', 'PATCH', props.token, input)
- .then(res => {
- console.log(res);
- setError({});
- props.setUserCache({...props.user, member: res});
- })
- .catch(err => {
- setLoading(false);
- console.log(err);
- setError(err.data);
- });
- };
-
- return (
-
-
-
-
-
-
- Submit
-
-
- );
-};
-
-function MemberInfo(props) {
- const user = props.user;
- const member = user.member;
-
- const lastTrans = user.transactions && user.transactions.slice(-3).slice().reverse();
- const lastCard = user.cards && user.cards.sort((a, b) => a.last_seen_at < b.last_seen_at)[0];
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {member.first_name} {member.last_name}
-
-
- Preferred Name: {member.preferred_name || '???'}
- Email: {user.email}
- Status: Current
-
-
-
-
-
-
-
- Expiry:
- 2099-01-01
-
-
- Application:
- {member.application_date || '???'}
-
-
- Start:
- {member.current_start_date || '???'}
-
-
- Vetted:
- {member.vetted_date || 'Not vetted'}
-
-
- Monthly:
- ${member.monthly_fees || '???'}
-
-
- Card Number:
- {lastCard && lastCard.card_number || 'None'}
-
-
-
-
-
-
-
- {lastTrans.length ?
- lastTrans.map((x, i) =>
-
- {x.date}
- {x.account_type}
- ${x.amount}
-
- )
- :
- None
- }
-
-
-
- );
-};
-
-function Home(props) {
- const { token, setTokenCache, user, setUserCache } = props;
-
- return (
-
-
-
- {user ?
- user.member.set_details ?
-
- :
-
- :
-
- }
-
-
-
-
- Welcome to the Protospace member portal! Here you can view member info, join classes, and manage your membership.
-
-
- Main Website
- Protospace Wiki
- Discussion Google Group
- Google Drive
-
-
- Next member meeting: Jan 01, 2099
- Next monthly clean: Jan 01, 2099
- Current member count: 200
- Due members: 20
- Expired members: 100
- Bay 108 temperature: 21 C
- Bay 110 temperature: 22 C
-
-
-
-
-
- );
-};
-
-function Transactions(props) {
- const { user } = props;
-
- return (
-
-
-
-
-
-
- Date
- ID
- Amount
- Account
- Memo
-
-
-
-
- {user.transactions.length ?
- user.transactions.slice().reverse().map((x, i) =>
-
- {x.date}
- {x.id}
- ${x.amount}
- {x.account_type}
- {x.memo}
-
- )
- :
- None
- }
-
-
-
-
- );
-}
-
-function TransactionDetail(props) {
- const { user } = props;
- const { id } = useParams();
-
- const t = user.transactions.find(x => x.id == id);
-
- return (
- t ?
-
-
-
-
-
-
- Date:
- {t.date}
-
-
- ID:
- {t.id}
-
-
- Amount:
- ${t.amount}
-
-
- Category:
- {t.category}
-
-
- Account:
- {t.account}
-
-
- Info Source:
- {t.info_source}
-
-
- Reference:
- {t.reference_number}
-
-
- Memo:
- {t.memo}
-
-
-
-
-
- :
-
- );
-};
-
-function Cards(props) {
- const { user } = props;
-
- const cardStatus = (c) => c.active_status === 'card_active' ? 'Yes' : 'No';
- const card = user.cards[0];
-
- return (
-
-
-
- {user.cards.length ?
- user.cards.length > 1 ?
-
-
-
- Number
- Notes
- Last Seen
- Active
-
-
-
-
- {user.cards.map((x, i) =>
-
- {x.card_number}
- {x.notes}
- {x.last_seen_at}
- {cardStatus(x)}
-
- )}
-
-
- :
-
-
-
- Number:
- {card.card_number}
-
-
- Notes:
- {card.notes}
-
-
- Last Seen:
- {card.last_seen_at}
-
-
- Active:
- {cardStatus(card)}
-
-
-
- :
- No cards yet! Ask a director for one after you are vetted.
- }
-
-
- );
-};
-
-function PleaseLogin() {
- return (
-
-
- You must login before you can do that!
- Visit our login page, then try again.
-
-
- );
-};
-
-function NotFound() {
- return (
-
-
- The page you requested can't be found!
- Visit our home page if you are lost.
-
-
- );
-};
+import { Home } from './Home.js';
+import { Transactions, TransactionDetail } from './Transactions.js';
+import { Cards } from './Cards.js';
+import { NotFound, PleaseLogin } from './Misc.js';
function App() {
const [token, setToken] = useState(localStorage.getItem('token', ''));
diff --git a/webclient/src/Cards.js b/webclient/src/Cards.js
new file mode 100644
index 0000000..b92cb70
--- /dev/null
+++ b/webclient/src/Cards.js
@@ -0,0 +1,69 @@
+import React, { useState, useEffect } from 'react';
+import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
+import './light.css';
+import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
+import { requester } from './utils.js';
+import { NotFound, PleaseLogin } from './Misc.js';
+
+export function Cards(props) {
+ const { user } = props;
+
+ const cardStatus = (c) => c.active_status === 'card_active' ? 'Yes' : 'No';
+ const card = user.cards[0];
+
+ return (
+
+
+
+ {user.cards.length ?
+ user.cards.length > 1 ?
+
+
+
+ Number
+ Notes
+ Last Seen
+ Active
+
+
+
+
+ {user.cards.map((x, i) =>
+
+ {x.card_number}
+ {x.notes}
+ {x.last_seen_at}
+ {cardStatus(x)}
+
+ )}
+
+
+ :
+
+
+
+ Number:
+ {card.card_number}
+
+
+ Notes:
+ {card.notes}
+
+
+ Last Seen:
+ {card.last_seen_at}
+
+
+ Active:
+ {cardStatus(card)}
+
+
+
+ :
+ No cards yet! Ask a director for one after you are vetted.
+ }
+
+
+ );
+};
+
diff --git a/webclient/src/Home.js b/webclient/src/Home.js
new file mode 100644
index 0000000..3b63524
--- /dev/null
+++ b/webclient/src/Home.js
@@ -0,0 +1,206 @@
+import React, { useState, useEffect } from 'react';
+import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
+import './light.css';
+import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
+import { requester } from './utils.js';
+import { LoginForm, SignupForm } from './LoginSignup.js';
+
+function DetailsForm(props) {
+ const member = props.user.member;
+ const [input, setInput] = useState({
+ preferred_name: member.preferred_name,
+ phone: member.phone,
+ emergency_contact_name: member.emergency_contact_name,
+ emergency_contact_phone: member.emergency_contact_phone,
+ set_details: true,
+ });
+ const [error, setError] = useState({});
+ 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) => {
+ setLoading(true);
+ requester('/members/' + member.id + '/', 'PATCH', props.token, input)
+ .then(res => {
+ console.log(res);
+ setError({});
+ props.setUserCache({...props.user, member: res});
+ })
+ .catch(err => {
+ setLoading(false);
+ console.log(err);
+ setError(err.data);
+ });
+ };
+
+ return (
+
+
+
+
+
+
+ Submit
+
+
+ );
+};
+
+function MemberInfo(props) {
+ const user = props.user;
+ const member = user.member;
+
+ const lastTrans = user.transactions && user.transactions.slice(-3).slice().reverse();
+ const lastCard = user.cards && user.cards.sort((a, b) => a.last_seen_at < b.last_seen_at)[0];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {member.first_name} {member.last_name}
+
+
+ Preferred Name: {member.preferred_name || '???'}
+ Email: {user.email}
+ Status: Current
+
+
+
+
+
+
+
+ Expiry:
+ 2099-01-01
+
+
+ Application:
+ {member.application_date || '???'}
+
+
+ Start:
+ {member.current_start_date || '???'}
+
+
+ Vetted:
+ {member.vetted_date || 'Not vetted'}
+
+
+ Monthly:
+ ${member.monthly_fees || '???'}
+
+
+ Card Number:
+ {lastCard && lastCard.card_number || 'None'}
+
+
+
+
+
+
+
+ {lastTrans.length ?
+ lastTrans.map((x, i) =>
+
+ {x.date}
+ {x.account_type}
+ ${x.amount}
+
+ )
+ :
+ None
+ }
+
+
+
+ );
+};
+
+export function Home(props) {
+ const { token, setTokenCache, user, setUserCache } = props;
+
+ return (
+
+
+
+ {user ?
+ user.member.set_details ?
+
+ :
+
+ :
+
+ }
+
+
+
+
+ Welcome to the Protospace member portal! Here you can view member info, join classes, and manage your membership.
+
+
+ Main Website
+ Protospace Wiki
+ Discussion Google Group
+ Google Drive
+
+
+ Next member meeting: Jan 01, 2099
+ Next monthly clean: Jan 01, 2099
+ Current member count: 200
+ Due members: 20
+ Expired members: 100
+ Bay 108 temperature: 21 C
+ Bay 110 temperature: 22 C
+
+
+
+
+
+ );
+};
diff --git a/webclient/src/LoginSignup.js b/webclient/src/LoginSignup.js
new file mode 100644
index 0000000..49c0246
--- /dev/null
+++ b/webclient/src/LoginSignup.js
@@ -0,0 +1,163 @@
+import React, { useState, useEffect } from 'react';
+import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
+import './light.css';
+import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
+import { requester } from './utils.js';
+
+export function LoginForm(props) {
+ const [input, setInput] = useState({});
+ const [error, setError] = useState({});
+ 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) => {
+ setLoading(true);
+ requester('/rest-auth/login/', 'POST', '', input)
+ .then(res => {
+ console.log(res);
+ setError({});
+ props.setTokenCache(res.key);
+ })
+ .catch(err => {
+ setLoading(false);
+ console.log(err);
+ setError(err.data);
+ });
+ };
+
+ return (
+
+
+
+ Login
+
+
+ );
+};
+
+export function SignupForm(props) {
+ const [input, setInput] = useState({});
+ const [error, setError] = useState({});
+ const [loading, setLoading] = useState(false);
+
+ const handleValues = (e, v) => setInput({
+ ...input,
+ [v.name]: v.value
+ });
+
+ const handleChange = (e) => handleValues(e, e.currentTarget);
+
+ const genUsername = () => (
+ input.first_name && input.last_name ?
+ (input.first_name + '.' + input.last_name).toLowerCase()
+ :
+ ''
+ );
+
+ const handleSubmit = (e) => {
+ setLoading(true);
+ input.username = genUsername();
+ requester('/registration/', 'POST', '', input)
+ .then(res => {
+ console.log(res);
+ setError({});
+ props.setTokenCache(res.key);
+ })
+ .catch(err => {
+ setLoading(false);
+ console.log(err);
+ setError(err.data);
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sign Up
+
+
+ );
+};
diff --git a/webclient/src/Misc.js b/webclient/src/Misc.js
new file mode 100644
index 0000000..e7e9c3f
--- /dev/null
+++ b/webclient/src/Misc.js
@@ -0,0 +1,27 @@
+import React, { useState, useEffect } from 'react';
+import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
+import './light.css';
+import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
+
+export function PleaseLogin() {
+ return (
+
+
+ You must login before you can do that!
+ Visit our login page, then try again.
+
+
+ );
+};
+
+export function NotFound() {
+ return (
+
+
+ The page you requested can't be found!
+ Visit our home page if you are lost.
+
+
+ );
+};
+
diff --git a/webclient/src/Transactions.js b/webclient/src/Transactions.js
new file mode 100644
index 0000000..b977870
--- /dev/null
+++ b/webclient/src/Transactions.js
@@ -0,0 +1,101 @@
+import React, { useState, useEffect } from 'react';
+import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-router-dom';
+import './light.css';
+import Logo from './logo.svg';
+import { Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
+import { requester } from './utils.js';
+import { NotFound, PleaseLogin } from './Misc.js';
+
+export function Transactions(props) {
+ const { user } = props;
+
+ return (
+
+
+
+
+
+
+ Date
+ ID
+ Amount
+ Account
+ Memo
+
+
+
+
+ {user.transactions.length ?
+ user.transactions.slice().reverse().map((x, i) =>
+
+ {x.date}
+ {x.id}
+ ${x.amount}
+ {x.account_type}
+ {x.memo}
+
+ )
+ :
+ None
+ }
+
+
+
+
+ );
+}
+
+export function TransactionDetail(props) {
+ const { user } = props;
+ const { id } = useParams();
+
+ const t = user.transactions.find(x => x.id == id);
+
+ return (
+ t ?
+
+
+
+
+
+
+ Date:
+ {t.date}
+
+
+ ID:
+ {t.id}
+
+
+ Amount:
+ ${t.amount}
+
+
+ Category:
+ {t.category}
+
+
+ Account:
+ {t.account}
+
+
+ Info Source:
+ {t.info_source}
+
+
+ Reference:
+ {t.reference_number}
+
+
+ Memo:
+ {t.memo}
+
+
+
+
+
+ :
+
+ );
+};
+