Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Components/Navbar/AdminNavbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ export default function UserNavBar(props) {
</svg>
)
},
{
title: 'Cash Payments',
route: '/cash-payments',
icon: (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" />
<path fill="currentColor" fill-rule="evenodd" d="M12.052 1.25h-.104c-.899 0-1.648 0-2.242.08c-.628.084-1.195.27-1.65.725c-.456.456-.642 1.023-.726 1.65c-.057.427-.074 1.446-.078 2.32c-2.022.067-3.237.303-4.08 1.147C2 8.343 2 10.229 2 14s0 5.657 1.172 6.828S6.229 22 10 22h4c3.771 0 5.657 0 6.828-1.172S22 17.771 22 14s0-5.657-1.172-6.828c-.843-.844-2.058-1.08-4.08-1.146c-.004-.875-.02-1.894-.078-2.32c-.084-.628-.27-1.195-.726-1.65c-.455-.456-1.022-.642-1.65-.726c-.594-.08-1.344-.08-2.242-.08m3.196 4.752c-.005-.847-.019-1.758-.064-2.097c-.063-.461-.17-.659-.3-.789s-.328-.237-.79-.3c-.482-.064-1.13-.066-2.094-.066s-1.612.002-2.095.067c-.461.062-.659.169-.789.3s-.237.327-.3.788c-.045.34-.06 1.25-.064 2.097Q9.34 5.999 10 6h4q.662 0 1.248.002M12 9.25a.75.75 0 0 1 .75.75v.01c1.089.274 2 1.133 2 2.323a.75.75 0 0 1-1.5 0c0-.384-.426-.916-1.25-.916s-1.25.532-1.25.916s.426.917 1.25.917c1.385 0 2.75.96 2.75 2.417c0 1.19-.911 2.048-2 2.323V18a.75.75 0 0 1-1.5 0v-.01c-1.089-.274-2-1.133-2-2.323a.75.75 0 0 1 1.5 0c0 .384.426.916 1.25.916s1.25-.532 1.25-.916s-.426-.917-1.25-.917c-1.385 0-2.75-.96-2.75-2.417c0-1.19.911-2.049 2-2.323V10a.75.75 0 0 1 .75-.75" clip-rule="evenodd" />
</svg>
)
},
];

const renderRoutesForNavbar = (navbarLinks) => {
Expand Down
147 changes: 147 additions & 0 deletions src/Pages/CashPayments/CashPayments.js
Original file line number Diff line number Diff line change
@@ -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 <p className="text-red-500">Please enter a valid number</p>;
}
if (parsedAmount < MIN_AMOUNT) {
return (
<p className="text-red-500">
Amount must be at least ${MIN_AMOUNT.toFixed(2)}
</p>
);
}
}

return (
<div className="m-10">
<h1 className="text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white">
Cash Payments
</h1>
<form className="relative overflow-x-auto" onSubmit={handleSubmit}>
<div className="py-6">
<label className="w-full form-control">
<div className="label">
<span className="label-text text-md">Name</span>
</div>
<input
className="w-full text-sm input input-bordered sm:text-base"
type="text"
placeholder="Payer name"
value={name}
onChange={(event) => setName(event.target.value)}
/>
</label>
</div>
<div className="py-6">
<label className="w-full form-control">
<div className="label">
<span className="label-text text-md">Email</span>
</div>
<input
className="w-full text-sm input input-bordered sm:text-base"
type="email"
placeholder="Payer email"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
</label>
</div>
<div className="py-6">
<label className="w-full form-control">
<div className="label">
<span className="label-text text-md">
Amount (minimum ${MIN_AMOUNT.toFixed(2)})
</span>
</div>
<input
className="w-full text-sm input input-bordered sm:text-base"
type="number"
min={MIN_AMOUNT}
step="0.01"
placeholder="0.00"
value={amount}
onChange={(event) => setAmount(event.target.value)}
/>
{maybeRenderAmountError()}
</label>
</div>
<button
type="submit"
className="text-sm btn btn-primary sm:text-base"
disabled={!canSubmit || submitting}
>
Submit Payment
</button>
{message && (
<p className={`mt-4 ${messageIsError ? 'text-red-500' : 'text-green-600 dark:text-green-400'}`}>
{message}
</p>
)}
</form>
</div>
);
}
9 changes: 9 additions & 0 deletions src/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
];

Expand Down
3 changes: 2 additions & 1 deletion src/config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"GOOGLE_API_CLIENT_ID": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com",
"SCEvents": {
"BASE_URL": "/api/scevents"
}
},
"MEMBERSHIP_PAYMENT_API_KEY": "GO_AWAY_LOL"
}
Loading