diff --git a/public/admin/deleteMessage.php b/public/admin/deleteMessage.php new file mode 100755 index 0000000..e5b65f4 --- /dev/null +++ b/public/admin/deleteMessage.php @@ -0,0 +1,37 @@ +prepare("DELETE FROM messages WHERE id = ?"); +if (!$query) { + http_response_code(500); + die("Error: {$conn->error}"); +} +if (!$query->bind_param("i", $id)) { + http_response_code(400); + die("Error: message ID must be an integer."); +} +if (!$query->execute()) { + http_response_code(500); + die("Error {$query->errno}: {$query->error}"); +} + +if ($query->affected_rows === 0) { + http_response_code(400); + die("Error: that message does not exist"); +} else { + die("Removed message $id successfully."); +} diff --git a/public/admin/index.php b/public/admin/index.php index 91d5d87..9a47a40 100755 --- a/public/admin/index.php +++ b/public/admin/index.php @@ -12,60 +12,156 @@ $conn = new mysqli("localhost", "mileslinden", "Daiso@6969", "mileslinden"); $result = $conn->query("SELECT * FROM subscribers"); if (!$result) { http_response_code(500); - die("Error: {$conn->error}"); + die("Error retrieving subscribers: {$conn->error}"); } $subscribers = []; while ($row = $result->fetch_assoc()) { $subscribers[] = $row; } + +$result = $conn->query("SELECT id, full_name, subject, send_date FROM messages ORDER BY send_date DESC"); +if (!$result) { + http_response_code(500); + die("Error retrieving messages: {$conn->error}"); +} + +$messages = []; +while ($row = $result->fetch_assoc()) { + $messages[] = $row; +} ?> Miles Linden for San Jose City Council +

Admin Panel

-
-

Logout

-

Mail All

+
+ Logout + Mail All
-

Subscribers

- - - - - - - - - - - - +
+
+

Subscribers

+
NameEmailPhoneJoin DateActions
+ + + + + + + + + + + - - - - - - - - + + + + + + + + - -
NameEmailPhoneJoin DateActions
- -
- - -
-
+ + +
+ + +
+
+

Messages

+
+ +
+
+ + [] : + +
+
+ +
+
+ +
+
+ + + diff --git a/public/admin/message.php b/public/admin/message.php new file mode 100755 index 0000000..64505be --- /dev/null +++ b/public/admin/message.php @@ -0,0 +1,52 @@ +prepare("SELECT * FROM messages WHERE id = ?"); +if (!$query) { + http_response_code(500); + die("Error: {$conn->error}"); +} +if (!$query->bind_param("i", $id)) { + http_response_code(400); + die("Message ID must be an integer."); +} +if (!$query->execute()) { + http_response_code(500); + die("Error {$query->errno}: {$query->error}"); +} + +$result = $query->get_result(); +if ($result->num_rows === 0) { + http_response_code(404); + die("Message with ID $id not found."); +} + +$message = $result->fetch_assoc(); +?> + + + + + Miles Linden for San Jose City Council + + +

+

From: ()

+

Date:

+
+ + diff --git a/public/admin/unsubscribe.php b/public/admin/unsubscribe.php index b1224b9..1751e80 100755 --- a/public/admin/unsubscribe.php +++ b/public/admin/unsubscribe.php @@ -30,6 +30,5 @@ if ($query->affected_rows === 0) { http_response_code(400); die("Error: that email address does not exist"); } else { - header('Location: /admin'); - die; + die("Removed subscribed user $email successfully."); } diff --git a/public/contact.php b/public/contact.php new file mode 100755 index 0000000..b6b65ae --- /dev/null +++ b/public/contact.php @@ -0,0 +1,79 @@ + 'email', + 'message' => 'Error: A valid email address is required.' + ])); +} +if (!isset($fname)) { + http_response_code(400); + header('Content-Type: application/json'); + die(json_encode([ + 'field' => 'full_name', + 'message' => 'Error: A first and last name are required.' + ])); +} +if (!isset($message)) { + http_response_code(400); + header('Content-Type: application/json'); + die(json_encode([ + 'field' => 'email', + 'message' => 'Error: A message is required.' + ])); +} +if (!isset($subject)) { + $subject = null; +} +$conn = new mysqli("localhost", "mileslinden", "Daiso@6969", "mileslinden"); + +$query = $conn->prepare( + "INSERT INTO messages (`email`, `full_name`, `subject`, `message`, `send_date`) VALUES (?, ?, ?, ?, ?)" +); +if (!$query) { + http_response_code(500); + header('Content-Type: application/json'); + die(json_encode(['message' => $conn->error])); +} +if (!isset($_SESSION['messages'])) { + $_SESSION['messages'] = 0; +} else if ($_SESSION['messages'] >= 5) { + http_response_code(429); + header('Content-Type: application/json'); + die(json_encode(['message' => 'You are sending messages too often.'])); +} +$query->bind_param( + "sssss", + $email, $fname, $subject, $message, + date("Y-m-d H:i:s") +); +if (!$query->execute()) { + http_response_code(500); + header('Content-Type: application/json'); + die(json_encode(['message' => $query->error, 'errno' => $query->errno])); +} +$_SESSION['messages']++; + +?> + + + + + Miles Linden for San Jose City Council + + +

Your message was sent successfully.

+ + diff --git a/public/css/main.css b/public/css/main.css index 01c72bf..cb0c9e6 100755 --- a/public/css/main.css +++ b/public/css/main.css @@ -94,13 +94,13 @@ nav a:hover { padding: 1rem; } -.banner form input { +.banner form input, .banner form textarea { border-bottom: 0.25rem solid black; width: 60%; min-width: 250px; } -.banner form input.invalid { +.banner form input.invalid, .banner form textarea.invalid { border-bottom-color: red; } @@ -220,6 +220,14 @@ nav a:hover { text-align: center; } +#contactForm div { + padding: 0.5rem; +} + +#contactForm input { + font-size: 1rem; +} + .read-more { margin-top: 4rem; text-align: center; diff --git a/public/index.php b/public/index.php index da0d649..3b6c00c 100755 --- a/public/index.php +++ b/public/index.php @@ -36,7 +36,7 @@

Join

diff --git a/public/js/app.js b/public/js/app.js index 09a58c7..cdc0121 100755 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,76 +1,118 @@ -document.addEventListener('DOMContentLoaded', function () { - - var fullName = document.querySelector('input[name="full_name"]'); - var email = document.querySelector('input[name="email"]'); - var phone = document.querySelector('input[name="phone"]'); - var submit = document.querySelector('button[type="submit"]'); +function registerFormValidators(formId) { + var form = document.getElementById(formId); + var fullName = form.querySelector('input[name="full_name"]'); + var email = form.querySelector('input[name="email"]'); + var phone = form.querySelector('input[name="phone"]'); + var subject = form.querySelector('input[name="subject"]'); + var message = form.querySelector('textarea[name="message"]'); + var submit = form.querySelector('button[type="submit"]'); const emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; const phoneRegex = /^(\+?0?1\s?)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/; const fullNameRegex = /[^\s.]{2,} [^\s.]{2,}/; - fullName.addEventListener('input', function (e) { - if (! fullName.value.toLowerCase().match(fullNameRegex)) { - fullName.classList.add('invalid'); - } else { - fullName.classList.remove('invalid'); - } - }); - - email.addEventListener('input', function (e) { - if (! email.value.toLowerCase().match(emailRegex)) { - email.classList.add('invalid'); - } else { - email.classList.remove('invalid'); - } - }); - - phone.addEventListener('input', function (e) { - if (! phone.value.match(phoneRegex)) { - phone.classList.add('invalid'); - } else { - phone.classList.remove('invalid'); - } - }); - - submit.addEventListener('click', function (e) { - e.preventDefault(); - - if (! fullName.value.toLowerCase().match(fullNameRegex)) { - alert("Your full name appears invalid."); - return; - } - if (! email.value.toLowerCase().match(emailRegex)) { - alert("Your email address is invalid."); - return; - } - if (! phone.value.match(phoneRegex)) { - alert("Your phone number is invalid."); - return; - } - var fd = new FormData(); - fd.append('full_name', fullName.value); - fd.append('email', email.value); - fd.append('phone', phone.value); - var req = fetch('/subscribe.php', { - method: 'POST', - body: fd - }).then(function (res) { - if (!res.ok) { - res.json().then(function (err) { - if ('field' in err) { - document.querySelector('input[name="'+ err.field + '"]').classList.add('invalid'); - } - alert(err.message); - }); + if (fullName) { + fullName.addEventListener('input', function (e) { + if (! fullName.value.toLowerCase().match(fullNameRegex)) { + fullName.classList.add('invalid'); } else { - alert("You have subscribed successfully."); - fullName.value = ""; - email.value = ""; - phone.value = ""; + fullName.classList.remove('invalid'); } }); - }); + } + if (email) { + email.addEventListener('input', function (e) { + if (! email.value.toLowerCase().match(emailRegex)) { + email.classList.add('invalid'); + } else { + email.classList.remove('invalid'); + } + }); + } + + if (phone) { + phone.addEventListener('input', function (e) { + if (! phone.value.match(phoneRegex)) { + phone.classList.add('invalid'); + } else { + phone.classList.remove('invalid'); + } + }); + } + + if (message) { + message.addEventListener('input', function (e) { + if (!message.value) { + message.classList.add('invalid'); + } else { + message.classList.remove('invalid'); + } + }); + } + + if (submit) { + submit.addEventListener('click', function (e) { + e.preventDefault(); + + if (fullName && !fullName.value.toLowerCase().match(fullNameRegex)) { + alert("Your full name appears invalid."); + return; + } + if (email && !email.value.toLowerCase().match(emailRegex)) { + alert("Your email address is invalid."); + return; + } + if (phone && !phone.value.match(phoneRegex)) { + alert("Your phone number is invalid."); + return; + } + if (message && !message.value) { + alert("Your message is invalid."); + return; + } + var fd = new FormData(); + if (fullName) + fd.append('full_name', fullName.value); + if (email) + fd.append('email', email.value); + if (phone) + fd.append('phone', phone.value); + if (message) + fd.append('message', message.value); + if (subject) + fd.append('subject', subject.value); + var req = fetch(form.action, { + method: 'POST', + body: fd + }).then(function (res) { + if (!res.ok) { + res.json().then(function (err) { + if ('field' in err) { + form.querySelector('input[name="'+ err.field + '"]').classList.add('invalid'); + } + alert(err.message); + }); + } else { + alert("Submitted successfully."); + if (fullName) + fullName.value = ""; + if (email) + email.value = ""; + if (phone) + phone.value = ""; + if (message) + message.value = ""; + if (subject) + subject.value = ""; + } + }); + }); + } +} + +document.addEventListener('DOMContentLoaded', function () { + registerFormValidators('subscribeForm'); + registerFormValidators('contactForm'); }); diff --git a/public/subscribe.php b/public/subscribe.php index efde88e..0182fef 100755 --- a/public/subscribe.php +++ b/public/subscribe.php @@ -6,10 +6,18 @@ session_start(); $email = $_POST['email']; $fname = $_POST['full_name']; $phone = $_POST['phone']; -$gender = isset($_POST['gender']) && $_POST['gender']; +$gender = $_POST['gender']; $pattern = '/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iD'; +if (!isset($phone)) { + http_response_code(400); + header('Content-Type: application/json'); + die(json_encode([ + 'field' => 'phone', + 'message' => 'Error: A phone number is required.' + ])); +} //eliminate every char except 0-9 $phone_num = preg_replace("/[^0-9]/", '', $phone); @@ -17,19 +25,35 @@ $phone_num = preg_replace("/[^0-9]/", '', $phone); if (strlen($phone_num) == 11) $phone_num = preg_replace("/^1/", '', $phone_num); -if (!$email || preg_match($pattern, $email) !== 1 || !$fname || strlen($phone_num) !== 10) { +if (!isset($email) || preg_match($pattern, $email) !== 1) { http_response_code(400); header('Content-Type: application/json'); die(json_encode([ 'field' => 'email', - 'message' => 'Error: An email address, first name, and last name are required.' + 'message' => 'Error: A valid email address is required.' + ])); +} +if (!isset($fname)) { + http_response_code(400); + header('Content-Type: application/json'); + die(json_encode([ + 'field' => 'full_name', + 'message' => 'Error: A first and last name are required.' + ])); +} +if (strlen($phone_num) !== 10) { + http_response_code(400); + header('Content-Type: application/json'); + die(json_encode([ + 'field' => 'phone', + 'message' => 'Error: This phone number is invalid.' ])); } if ($gender && $gender !== 'm' && $gender !== 'f') { http_response_code(400); header('Content-Type: application/json'); die(json_encode([ - 'field' => 'full_name', + 'field' => 'gender', 'message' => 'Error: An invalid gender was given.' ])); }