diff --git a/src/Components/Navbar/AdminNavbar.js b/src/Components/Navbar/AdminNavbar.js index e18b75c14..9920a9411 100644 --- a/src/Components/Navbar/AdminNavbar.js +++ b/src/Components/Navbar/AdminNavbar.js @@ -134,6 +134,16 @@ export default function UserNavBar(props) { ) }, + { + title: 'Cash Payments', + route: '/cash-payments', + icon: ( + + + + + ) + }, ]; const renderRoutesForNavbar = (navbarLinks) => { diff --git a/src/Pages/CashPayments/CashPayments.js b/src/Pages/CashPayments/CashPayments.js new file mode 100644 index 000000000..7d1148b7f --- /dev/null +++ b/src/Pages/CashPayments/CashPayments.js @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import config from '../../config/config.json'; + +const MIN_AMOUNT = 20; + +export default function CashPaymentsPage() { + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [amount, setAmount] = useState(''); + const [message, setMessage] = useState(''); + const [messageIsError, setMessageIsError] = useState(false); + const [submitting, setSubmitting] = useState(false); + + const parsedAmount = parseFloat(amount); + const isValidName = name.trim().length > 0; + const isValidEmail = email.trim().length > 0; + const isValidAmount = !Number.isNaN(parsedAmount) && parsedAmount >= MIN_AMOUNT; + const canSubmit = isValidName && isValidEmail && isValidAmount; + + async function handleSubmit(event) { + event.preventDefault(); + if (!canSubmit || submitting) { + return; + } + + const payload = { + payerName: name, + payerEmail: email, + amount: parsedAmount, + note: `SCE Membership: ${email}`, + transactionId: 'N/A - cash payment', + }; + + setSubmitting(true); + setMessage(''); + setMessageIsError(false); + + try { + const response = await fetch('/api/MembershipPayment/storePayment', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': config.MEMBERSHIP_PAYMENT_API_KEY, + }, + body: JSON.stringify(payload), + }); + const responseMessage = await response.text(); + setMessage(responseMessage); + setMessageIsError(!response.ok); + if (response.ok) { + setName(''); + setEmail(''); + setAmount(''); + } + } catch { + setMessage('Failed to submit payment.'); + setMessageIsError(true); + } finally { + setSubmitting(false); + } + } + + function maybeRenderAmountError() { + if (amount === '') { + return null; + } + if (Number.isNaN(parsedAmount)) { + return

Please enter a valid number

; + } + if (parsedAmount < MIN_AMOUNT) { + return ( +

+ Amount must be at least ${MIN_AMOUNT.toFixed(2)} +

+ ); + } + } + + return ( +
+

+ Cash Payments +

+
+
+ +
+
+ +
+
+ +
+ + {message && ( +

+ {message} +

+ )} +
+
+ ); +} diff --git a/src/Routes.js b/src/Routes.js index bc42c2671..e8bfdb780 100644 --- a/src/Routes.js +++ b/src/Routes.js @@ -24,6 +24,7 @@ import CreateEventPage from './Pages/Events/CreateEventPage.js'; import EventRegistration from './Pages/Events/EventsRegistration.js'; import EditEventPage from './Pages/Events/EditEventPage.js'; import EventAttendeesDashboard from './Pages/Events/EventAttendeesDashboard.js'; +import CashPaymentsPage from './Pages/CashPayments/CashPayments.js'; // Declare an enum for permission check export const allowedIf = { @@ -206,6 +207,14 @@ export const officerOrAdminRoutes = [ inAdminNavbar: true, hideFromShortcutSuggestions: true }, + { + Component: CashPaymentsPage, + path: '/cash-payments', + pageName: 'Cash Payments', + allowedIf: allowedIf.OFFICER_OR_ADMIN, + redirect: '/', + inAdminNavbar: true + }, ...memberRoutes, ]; diff --git a/src/config/config.example.json b/src/config/config.example.json index 1afbb2902..ea374b3ff 100644 --- a/src/config/config.example.json +++ b/src/config/config.example.json @@ -4,5 +4,6 @@ "GOOGLE_API_CLIENT_ID": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com", "SCEvents": { "BASE_URL": "/api/scevents" - } + }, + "MEMBERSHIP_PAYMENT_API_KEY": "GO_AWAY_LOL" }