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 to Spaceport
- - - - 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
- - - - - - - - - - - - - - - - - - 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 ( -
-
Enter Member Details
- - - - - - - 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

-
-
- -
Details
- - - - 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'} - - -
- -
Latest Transactions
- - - {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 ? - - : - - : -
- - - Or - - -
- } -
- - -
Portal
-

Welcome to the Protospace member portal! Here you can view member info, join classes, and manage your membership.

- -
Quick Links
-

Main Website

-

Protospace Wiki

-

Discussion Google Group

-

Google Drive

- -
Protospace Stats
-

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 ( - -
Transactions
- - - - - 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 ? - -
Transaction Receipt
- - - - - 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 ( - -
Member Cards
- - {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 ( + +
Member Cards
+ + {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 ( +
+
Enter Member Details
+ + + + + + + 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

+
+
+ +
Details
+ + + + 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'} + + +
+ +
Latest Transactions
+ + + {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 ? + + : + + : +
+ + + Or + + +
+ } +
+ + +
Portal
+

Welcome to the Protospace member portal! Here you can view member info, join classes, and manage your membership.

+ +
Quick Links
+

Main Website

+

Protospace Wiki

+

Discussion Google Group

+

Google Drive

+ +
Protospace Stats
+

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 to Spaceport
+ + + + 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
+ + + + + + + + + + + + + + + + + + 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 ( + +
Transactions
+ + + + + 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 ? + +
Transaction Receipt
+ + + + + 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} + + +
+ +
+ : + + ); +}; +