From dcfc16d20def8229333e599bc6023c9a995cd8e1 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sun, 16 Feb 2020 04:57:55 +0000 Subject: [PATCH] Move admin components to different files, add backup info --- webclient/src/Admin.js | 580 ++--------------------------- webclient/src/AdminMembers.js | 480 ++++++++++++++++++++++++ webclient/src/AdminTransactions.js | 106 ++++++ webclient/src/App.js | 15 +- webclient/src/Members.js | 3 +- 5 files changed, 630 insertions(+), 554 deletions(-) create mode 100644 webclient/src/AdminMembers.js create mode 100644 webclient/src/AdminTransactions.js diff --git a/webclient/src/Admin.js b/webclient/src/Admin.js index 0fc8bc3..e620be1 100644 --- a/webclient/src/Admin.js +++ b/webclient/src/Admin.js @@ -3,577 +3,53 @@ import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } f import './light.css'; import { Button, Container, Checkbox, Dimmer, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; import moment from 'moment'; -import { statusColor, BasicTable, staticUrl, requester } from './utils.js'; -import { TransactionList, TransactionEditor } from './Transactions.js'; +import { apiUrl, statusColor, BasicTable, staticUrl, requester } from './utils.js'; import { NotFound } from './Misc.js'; -export function AdminReportedTransactions(props) { +export function Admin(props) { const { token, user } = props; - const [transactions, setTransactions] = useState(false); + const [backup, setBackup] = useState(false); const [error, setError] = useState(false); useEffect(() => { - requester('/transactions/', 'GET', token) + requester('/backup/', 'GET', token) .then(res => { - setTransactions(res.results); - setError(false); + setBackup(res.url); }) .catch(err => { console.log(err); - setError(true); }); }, []); return ( -
Reported Transactions
+
Portal Admin
- {!error ? - transactions ? -
- -
- : -

Loading...

+
Backup
+

Spaceport backups are created daily. 14 days are kept on the server.

+ + {backup ? +
+

+ Download link:
+ + {backup} + +

+ +

+ Automate with wget (keep secret, that's your login token): +

+							wget \
+							
--header="Authorization: Token {token}" \ +
{apiUrl}/backup/ +
+

+
: - +

Loading...

}
); }; - -export function AdminTransactions(props) { - const { token, result, refreshResult } = props; - const transactions = result.transactions; - const [open, setOpen] = useState(false); - const [input, setInput] = useState({ date: moment().format('YYYY-MM-DD'), info_source: 'Web' }); - const [error, setError] = useState(false); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - const { id } = useParams(); - - const handleSubmit = (e) => { - if (loading) return; - setLoading(true); - setSuccess(false); - const data = { ...input, member_id: id }; - requester('/transactions/', 'POST', token, data) - .then(res => { - setSuccess(res.id); - setInput({}); - setLoading(false); - setError(false); - refreshResult(); - }) - .catch(err => { - setLoading(false); - console.log(err); - setError(err.data); - }); - }; - - return ( -
-
Edit Member Transactions
- -
-
Add a Transaction
- - - - - Submit - - {success &&

Added! View the transaction.

} - - -
Current Transactions
- - {transactions.length ? - open ? - - : - - : -

None

- } - -
- ); -}; - -function AdminCardDetail(props) { - const { token, result, card } = props; - const [input, setInput] = useState({ ...card }); - const [error, setError] = useState(false); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - const id = card.id; - - const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value }); - const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); - const handleChange = (e) => handleValues(e, e.currentTarget); - const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked }); - - const handleSubmit = (e) => { - if (loading) return; - setLoading(true); - setSuccess(false); - const data = { ...input, member_id: result.member.id }; - requester('/cards/'+id+'/', 'PUT', token, data) - .then(res => { - setLoading(false); - setSuccess(true); - setError(false); - setInput(res); - }) - .catch(err => { - setLoading(false); - console.log(err); - setError(err.data); - }); - }; - - const handleDelete = (e) => { - e.preventDefault(); - - requester('/cards/'+id+'/', 'DELETE', token) - .then(res => { - setInput(false); - }) - .catch(err => { - console.log(err); - }); - }; - - const makeProps = (name) => ({ - name: name, - onChange: handleChange, - value: input[name] || '', - error: error[name], - }); - - const statusOptions = [ - { key: '0', text: 'Card Active', value: 'card_active' }, - { key: '1', text: 'Card Blocked', value: 'card_blocked' }, - { key: '2', text: 'Card Inactive', value: 'card_inactive' }, - { key: '3', text: 'Card Member Blocked', value: 'card_member_blocked' }, - ]; - - return ( - input ? - -
- - - - - - - {success ? 'Saved.' : 'Save'} - - - - Delete - - - - - Notes: {input.notes || 'None'} -
-
- : - - Deleted card: {card.card_number} - - ); -}; - -export function AdminMemberCards(props) { - const { token, result, refreshResult } = props; - const cards = result.cards; - const startDimmed = Boolean(result.member.paused_date && cards.length); - const [dimmed, setDimmed] = useState(startDimmed); - const [input, setInput] = useState({ active_status: 'card_active' }); - const [error, setError] = useState(false); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - const { id } = useParams(); - - useEffect(() => { - const startDimmed = Boolean(result.member.paused_date && cards.length); - setDimmed(startDimmed); - }, [result.member]); - - const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value }); - const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); - const handleChange = (e) => handleValues(e, e.currentTarget); - const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked }); - - const handleSubmit = (e) => { - if (loading) return; - setLoading(true); - setSuccess(false); - const data = { ...input, member_id: result.member.id }; - requester('/cards/', 'POST', token, data) - .then(res => { - setLoading(false); - setSuccess(true); - setError(false); - refreshResult(); - }) - .catch(err => { - setLoading(false); - console.log(err); - setError(err.data); - }); - }; - - const makeProps = (name) => ({ - name: name, - onChange: handleChange, - value: input[name] || '', - error: error[name], - }); - - const statusOptions = [ - { key: '0', text: 'Card Active', value: 'card_active' }, - { key: '1', text: 'Card Blocked', value: 'card_blocked' }, - { key: '2', text: 'Card Inactive', value: 'card_inactive' }, - { key: '3', text: 'Card Member Blocked', value: 'card_member_blocked' }, - ]; - - return ( -
-
Edit Member Cards
-
-
Add a Card
- - - - - - - - - Submit - - {success &&
Success!
} -
- -
Current Cards
- - - {cards.length ? - cards.map(x => - - ) - : -

None

- } - - -

- Member paused, {cards.length} card{cards.length === 1 ? '' : 's'} ignored anyway. -

-

- -

-
-
- -
- ); -}; - -export function AdminMemberPause(props) { - const { token, result, refreshResult } = props; - const [error, setError] = useState(false); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - const [yousure, setYousure] = useState(false); - const { id } = useParams(); - - useEffect(() => { - setLoading(false); - }, [result.member]); - - const handlePause = (e) => { - if (yousure) { - if (loading) return; - setLoading(true); - setSuccess(false); - requester('/members/' + id + '/pause/', 'POST', token, {}) - .then(res => { - setYousure(false); - setSuccess(true); - setError(false); - refreshResult(); - }) - .catch(err => { - setLoading(false); - console.log(err); - setError(true); - }); - } else { - setYousure(true); - } - }; - - const handleUnpause = (e) => { - if (loading) return; - setLoading(true); - setSuccess(false); - requester('/members/' + id + '/unpause/', 'POST', token, {}) - .then(res => { - setSuccess(true); - setError(false); - refreshResult(); - }) - .catch(err => { - setLoading(false); - console.log(err); - setError(true); - }); - }; - - return ( -
-
Pause / Unpause Membership
- -

Pause members who are inactive, former, or on vacation.

- -

- {result.member.paused_date ? - - : - - } -

- - {success &&
Success!
} - {error &&

Error, something went wrong.

} -
- ); -}; - -export function AdminMemberForm(props) { - const { token, result, refreshResult } = props; - const [input, setInput] = useState(result.member); - const [error, setError] = useState(false); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - const { id } = useParams(); - - useEffect(() => { - setInput(result.member); - }, [result.member]); - - const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value }); - const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); - const handleChange = (e) => handleValues(e, e.currentTarget); - const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked }); - - const handleSubmit = (e) => { - if (loading) return; - setLoading(true); - setSuccess(false); - const data = { ...input, email: input.email.toLowerCase() }; - requester('/members/' + id + '/', 'PATCH', token, data) - .then(res => { - setLoading(false); - setSuccess(true); - setError(false); - refreshResult(); - }) - .catch(err => { - setLoading(false); - console.log(err); - setError(err.data); - }); - }; - - const makeProps = (name) => ({ - name: name, - onChange: handleChange, - value: input[name] || '', - error: error[name], - }); - - return ( -
-
-
Edit Member Details
- - - - - - - - - - - - - - - - - - - - - - - Submit - - {success &&
Success!
} - -
- ); -}; - -export function AdminMemberInfo(props) { - const member = props.result.member; - - return ( -
-
Admin Details
- - - - - Name: - {member.first_name} {member.last_name} - - - Status: - - - {member.status || 'Unknown'} - - - - - Expire Date: - {member.expire_date} - - {member.paused_date && - Paused Date: - {member.paused_date} - } - - - Phone: - {member.phone} - - - - Address: - {member.street_address} - - - City: - {member.city} - - - Postal: - {member.postal_code} - - - - Minor: - {member.is_minor ? 'Yes' : 'No'} - - {member.is_minor && - Birthdate: - {member.birthdate} - } - {member.is_minor && - Guardian: - {member.guardian_name} - } - - - Emergency Contact Name: - {member.emergency_contact_name || 'None'} - - - Emergency Contact Phone: - {member.emergency_contact_phone || 'None'} - - - - - {member.member_forms &&

- - View application forms - -

} -
- ); -}; diff --git a/webclient/src/AdminMembers.js b/webclient/src/AdminMembers.js new file mode 100644 index 0000000..e68ebf9 --- /dev/null +++ b/webclient/src/AdminMembers.js @@ -0,0 +1,480 @@ +import React, { useState, useEffect } from 'react'; +import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } from 'react-router-dom'; +import './light.css'; +import { Button, Container, Checkbox, Dimmer, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; +import moment from 'moment'; +import { statusColor, BasicTable, staticUrl, requester } from './utils.js'; +import { NotFound } from './Misc.js'; + +function AdminCardDetail(props) { + const { token, result, card } = props; + const [input, setInput] = useState({ ...card }); + const [error, setError] = useState(false); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const id = card.id; + + const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value }); + const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); + const handleChange = (e) => handleValues(e, e.currentTarget); + const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked }); + + const handleSubmit = (e) => { + if (loading) return; + setLoading(true); + setSuccess(false); + const data = { ...input, member_id: result.member.id }; + requester('/cards/'+id+'/', 'PUT', token, data) + .then(res => { + setLoading(false); + setSuccess(true); + setError(false); + setInput(res); + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(err.data); + }); + }; + + const handleDelete = (e) => { + e.preventDefault(); + + requester('/cards/'+id+'/', 'DELETE', token) + .then(res => { + setInput(false); + }) + .catch(err => { + console.log(err); + }); + }; + + const makeProps = (name) => ({ + name: name, + onChange: handleChange, + value: input[name] || '', + error: error[name], + }); + + const statusOptions = [ + { key: '0', text: 'Card Active', value: 'card_active' }, + { key: '1', text: 'Card Blocked', value: 'card_blocked' }, + { key: '2', text: 'Card Inactive', value: 'card_inactive' }, + { key: '3', text: 'Card Member Blocked', value: 'card_member_blocked' }, + ]; + + return ( + input ? + +
+ + + + + + + {success ? 'Saved.' : 'Save'} + + + + Delete + + + + + Notes: {input.notes || 'None'} +
+
+ : + + Deleted card: {card.card_number} + + ); +}; + +export function AdminMemberCards(props) { + const { token, result, refreshResult } = props; + const cards = result.cards; + const startDimmed = Boolean(result.member.paused_date && cards.length); + const [dimmed, setDimmed] = useState(startDimmed); + const [input, setInput] = useState({ active_status: 'card_active' }); + const [error, setError] = useState(false); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const { id } = useParams(); + + useEffect(() => { + const startDimmed = Boolean(result.member.paused_date && cards.length); + setDimmed(startDimmed); + }, [result.member]); + + const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value }); + const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); + const handleChange = (e) => handleValues(e, e.currentTarget); + const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked }); + + const handleSubmit = (e) => { + if (loading) return; + setLoading(true); + setSuccess(false); + const data = { ...input, member_id: result.member.id }; + requester('/cards/', 'POST', token, data) + .then(res => { + setLoading(false); + setSuccess(true); + setError(false); + refreshResult(); + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(err.data); + }); + }; + + const makeProps = (name) => ({ + name: name, + onChange: handleChange, + value: input[name] || '', + error: error[name], + }); + + const statusOptions = [ + { key: '0', text: 'Card Active', value: 'card_active' }, + { key: '1', text: 'Card Blocked', value: 'card_blocked' }, + { key: '2', text: 'Card Inactive', value: 'card_inactive' }, + { key: '3', text: 'Card Member Blocked', value: 'card_member_blocked' }, + ]; + + return ( +
+
Edit Member Cards
+
+
Add a Card
+ + + + + + + + + Submit + + {success &&
Success!
} +
+ +
Current Cards
+ + + {cards.length ? + cards.map(x => + + ) + : +

None

+ } + + +

+ Member paused, {cards.length} card{cards.length === 1 ? '' : 's'} ignored anyway. +

+

+ +

+
+
+ +
+ ); +}; + +export function AdminMemberPause(props) { + const { token, result, refreshResult } = props; + const [error, setError] = useState(false); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [yousure, setYousure] = useState(false); + const { id } = useParams(); + + useEffect(() => { + setLoading(false); + }, [result.member]); + + const handlePause = (e) => { + if (yousure) { + if (loading) return; + setLoading(true); + setSuccess(false); + requester('/members/' + id + '/pause/', 'POST', token, {}) + .then(res => { + setYousure(false); + setSuccess(true); + setError(false); + refreshResult(); + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(true); + }); + } else { + setYousure(true); + } + }; + + const handleUnpause = (e) => { + if (loading) return; + setLoading(true); + setSuccess(false); + requester('/members/' + id + '/unpause/', 'POST', token, {}) + .then(res => { + setSuccess(true); + setError(false); + refreshResult(); + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(true); + }); + }; + + return ( +
+
Pause / Unpause Membership
+ +

Pause members who are inactive, former, or on vacation.

+ +

+ {result.member.paused_date ? + + : + + } +

+ + {success &&
Success!
} + {error &&

Error, something went wrong.

} +
+ ); +}; + +export function AdminMemberForm(props) { + const { token, result, refreshResult } = props; + const [input, setInput] = useState(result.member); + const [error, setError] = useState(false); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const { id } = useParams(); + + useEffect(() => { + setInput(result.member); + }, [result.member]); + + const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value }); + const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] }); + const handleChange = (e) => handleValues(e, e.currentTarget); + const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked }); + + const handleSubmit = (e) => { + if (loading) return; + setLoading(true); + setSuccess(false); + const data = { ...input, email: input.email.toLowerCase() }; + requester('/members/' + id + '/', 'PATCH', token, data) + .then(res => { + setLoading(false); + setSuccess(true); + setError(false); + refreshResult(); + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(err.data); + }); + }; + + const makeProps = (name) => ({ + name: name, + onChange: handleChange, + value: input[name] || '', + error: error[name], + }); + + return ( +
+
+
Edit Member Details
+ + + + + + + + + + + + + + + + + + + + + + + Submit + + {success &&
Success!
} + +
+ ); +}; + +export function AdminMemberInfo(props) { + const member = props.result.member; + + return ( +
+
Admin Details
+ + + + + Name: + {member.first_name} {member.last_name} + + + Status: + + + {member.status || 'Unknown'} + + + + + Expire Date: + {member.expire_date} + + {member.paused_date && + Paused Date: + {member.paused_date} + } + + + Phone: + {member.phone} + + + + Address: + {member.street_address} + + + City: + {member.city} + + + Postal: + {member.postal_code} + + + + Minor: + {member.is_minor ? 'Yes' : 'No'} + + {member.is_minor && + Birthdate: + {member.birthdate} + } + {member.is_minor && + Guardian: + {member.guardian_name} + } + + + Emergency Contact Name: + {member.emergency_contact_name || 'None'} + + + Emergency Contact Phone: + {member.emergency_contact_phone || 'None'} + + + + + {member.member_forms &&

+ + View application forms + +

} +
+ ); +}; diff --git a/webclient/src/AdminTransactions.js b/webclient/src/AdminTransactions.js new file mode 100644 index 0000000..432d0b8 --- /dev/null +++ b/webclient/src/AdminTransactions.js @@ -0,0 +1,106 @@ +import React, { useState, useEffect } from 'react'; +import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } from 'react-router-dom'; +import './light.css'; +import { Button, Container, Checkbox, Dimmer, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; +import moment from 'moment'; +import { statusColor, BasicTable, staticUrl, requester } from './utils.js'; +import { TransactionList, TransactionEditor } from './Transactions.js'; +import { NotFound } from './Misc.js'; + +export function AdminReportedTransactions(props) { + const { token, user } = props; + const [transactions, setTransactions] = useState(false); + const [error, setError] = useState(false); + + useEffect(() => { + requester('/transactions/', 'GET', token) + .then(res => { + setTransactions(res.results); + setError(false); + }) + .catch(err => { + console.log(err); + setError(true); + }); + }, []); + + return ( + +
Reported Transactions
+ + {!error ? + transactions ? +
+ +
+ : +

Loading...

+ : + + } + +
+ ); +}; + +export function AdminTransactions(props) { + const { token, result, refreshResult } = props; + const transactions = result.transactions; + const [open, setOpen] = useState(false); + const [input, setInput] = useState({ date: moment().format('YYYY-MM-DD'), info_source: 'Web' }); + const [error, setError] = useState(false); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const { id } = useParams(); + + const handleSubmit = (e) => { + if (loading) return; + setLoading(true); + setSuccess(false); + const data = { ...input, member_id: id }; + requester('/transactions/', 'POST', token, data) + .then(res => { + setSuccess(res.id); + setInput({}); + setLoading(false); + setError(false); + refreshResult(); + }) + .catch(err => { + setLoading(false); + console.log(err); + setError(err.data); + }); + }; + + return ( +
+
Edit Member Transactions
+ +
+
Add a Transaction
+ + + + + Submit + + {success &&

Added! View the transaction.

} + + +
Current Transactions
+ + {transactions.length ? + open ? + + : + + : +

None

+ } + +
+ ); +}; diff --git a/webclient/src/App.js b/webclient/src/App.js index 4f9cbb0..b6a380d 100644 --- a/webclient/src/App.js +++ b/webclient/src/App.js @@ -9,7 +9,8 @@ import { Account } from './Account.js'; import { Transactions, TransactionDetail } from './Transactions.js'; import { Cards } from './Cards.js'; import { Training } from './Training.js'; -import { AdminReportedTransactions } from './Admin.js'; +import { AdminReportedTransactions } from './AdminTransactions.js'; +import { Admin } from './Admin.js'; import { Courses, CourseDetail } from './Courses.js'; import { Classes, ClassDetail } from './Classes.js'; import { Members, MemberDetail } from './Members.js'; @@ -132,6 +133,12 @@ function App() { to='/classes' /> + {user && isAdmin(user) && } + {user && isAdmin(user) && + {user && isAdmin(user) && + + + + } + {user && isAdmin(user) && diff --git a/webclient/src/Members.js b/webclient/src/Members.js index 4216c94..b057992 100644 --- a/webclient/src/Members.js +++ b/webclient/src/Members.js @@ -4,7 +4,8 @@ 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 { NotFound, PleaseLogin } from './Misc.js'; -import { AdminMemberInfo, AdminMemberPause, AdminMemberForm, AdminMemberCards, AdminTransactions } from './Admin.js'; +import { AdminMemberInfo, AdminMemberPause, AdminMemberForm, AdminMemberCards } from './AdminMembers.js'; +import { AdminTransactions } from './AdminTransactions.js'; export function MembersDropdown(props) { const { token, name, onChange, value, initial } = props;