refactor: finalize PHP handler architecture and API integration
This commit is contained in:
parent
6e2c6644df
commit
c02af434dc
5 changed files with 118 additions and 49 deletions
7
backend/config.php
Normal file
7
backend/config.php
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'recipient_email' => 'dev@andre-kempf.com',
|
||||||
|
'from_email' => 'noreply@andre-kempf.com',
|
||||||
|
'app_name' => 'Portfolio Contact',
|
||||||
|
];
|
||||||
67
backend/sendMail.php
Normal file
67
backend/sendMail.php
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$config = require_once __DIR__ . '/config.php';
|
||||||
|
require_once __DIR__ . '/templates/emailTemplate.php';
|
||||||
|
|
||||||
|
switch ($_SERVER['REQUEST_METHOD']) {
|
||||||
|
case ("OPTIONS"): // Allow preflight requests
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header("Access-Control-Allow-Methods: POST");
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type");
|
||||||
|
exit;
|
||||||
|
|
||||||
|
case ("POST"): // Handle and send the email
|
||||||
|
header("Access-Control-Allow-Origin: *");
|
||||||
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
|
|
||||||
|
// Read raw JSON payload
|
||||||
|
$json = file_get_contents('php://input');
|
||||||
|
$params = json_decode($json);
|
||||||
|
|
||||||
|
// 1. Honeypot check for spam protection
|
||||||
|
if (!empty($params->website)) {
|
||||||
|
http_response_code(200);
|
||||||
|
echo json_encode(["status" => "success"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Sanitize inputs & set fallbacks
|
||||||
|
$email = filter_var($params->email ?? '', FILTER_VALIDATE_EMAIL);
|
||||||
|
$name = htmlspecialchars(trim($params->name ?? 'Anonymous'), ENT_QUOTES, 'UTF-8');
|
||||||
|
$text = htmlspecialchars(trim($params->message ?? ''), ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!$email || empty($text)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(["status" => "error", "message" => "Invalid input"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Render template
|
||||||
|
$emailBody = renderEmailTemplate($name, $email, $text, $params->message ?? '');
|
||||||
|
$subject = "{$config['app_name']}: $name";
|
||||||
|
|
||||||
|
// 4. Set headers
|
||||||
|
$headers = [
|
||||||
|
'MIME-Version: 1.0',
|
||||||
|
'Content-type: text/html; charset=utf-8',
|
||||||
|
"From: {$config['from_email']}",
|
||||||
|
"Reply-To: $email"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Send email
|
||||||
|
$success = mail($config['recipient_email'], $subject, $emailBody, implode("\n", $headers));
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
http_response_code(200);
|
||||||
|
echo json_encode(["status" => "success"]);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(["status" => "error"]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // Reject any other HTTP method
|
||||||
|
header("Allow: POST", true, 405);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
35
backend/templates/emailTemplate.php
Normal file
35
backend/templates/emailTemplate.php
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function renderEmailTemplate(string $name, string $email, string $text, string $paramsMessage): string {
|
||||||
|
// Pre-fill reply parameters using rawurlencode (%20 for spaces)
|
||||||
|
$replySubject = rawurlencode("Re: Portfolio Contact from $name");
|
||||||
|
$replyBody = rawurlencode("\n\n---\nOriginal message from $name ($email):\n" . $paramsMessage);
|
||||||
|
$mailtoLink = "mailto:$email?subject=$replySubject&body=$replyBody";
|
||||||
|
|
||||||
|
// Cleaned message body to avoid double breaks
|
||||||
|
$formattedText = nl2br(trim($text));
|
||||||
|
|
||||||
|
return "
|
||||||
|
<div style='width: 100%; height: 100%; background-color: #fff; font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;'>
|
||||||
|
<div style='background-color: #020617; color: #f8fafc; padding: 28px; max-width: 800px; margin: 0 auto; border: 1px solid #1e293b; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5);'>
|
||||||
|
<div style='margin-bottom: 20px;'>
|
||||||
|
<span style='font-family: monospace; color: #3b82f6; font-size: 12px;'>// Portfolio Contact Message</span>
|
||||||
|
<h2 style='margin: 6px 0 0 0; color: #ffffff; font-size: 20px;'>New message from $name</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style='background-color: #0f172a; border: 1px solid #1e293b; padding: 18px; border-radius: 8px; margin-bottom: 24px;'>
|
||||||
|
<div style='font-family: monospace; font-size: 12px; color: #64748b; margin-bottom: 12px;'>
|
||||||
|
<span style='color: #3b82f6;'>From:</span> $name ($email)
|
||||||
|
</div>
|
||||||
|
<div style='font-size: 14px; line-height: 1.6; color: #cbd5e1;'>{$formattedText}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style='border-top: 1px solid #1e293b; padding-top: 20px; text-align: center;'>
|
||||||
|
<a href='$mailtoLink' style='display: inline-block; padding: 12px 24px; background-color: #2563eb; color: #ffffff; font-weight: 500; text-decoration: none; border-radius: 8px; font-size: 13px; font-family: monospace;'>
|
||||||
|
➜ Quick Reply
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
";
|
||||||
|
}
|
||||||
43
sendMail.php
43
sendMail.php
|
|
@ -1,43 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
switch ($_SERVER['REQUEST_METHOD']) {
|
|
||||||
case ("OPTIONS"): // Allow preflighting to take place.
|
|
||||||
header("Access-Control-Allow-Origin: *");
|
|
||||||
header("Access-Control-Allow-Methods: POST");
|
|
||||||
header("Access-Control-Allow-Headers: content-type");
|
|
||||||
exit;
|
|
||||||
|
|
||||||
case ("POST"): // Send the email
|
|
||||||
header("Access-Control-Allow-Origin: *");
|
|
||||||
|
|
||||||
// Payload is sent to php://input as raw JSON text
|
|
||||||
$json = file_get_contents('php://input');
|
|
||||||
$params = json_decode($json);
|
|
||||||
|
|
||||||
// Honeypot check
|
|
||||||
if (!empty($params->website)) {
|
|
||||||
http_response_code(200);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract values
|
|
||||||
$email = $params->email;
|
|
||||||
$name = $params->name;
|
|
||||||
$message = $params->message;
|
|
||||||
|
|
||||||
$recipient = 'dev@andre-kempf.com';
|
|
||||||
$subject = "Contact Form <$email>";
|
|
||||||
$message = "From: $name<br> Email: $email <br><br>$message";
|
|
||||||
|
|
||||||
$headers = array();
|
|
||||||
$headers[] = 'MIME-Version: 1.0';
|
|
||||||
$headers[] = 'Content-type: text/html; charset=utf-8';
|
|
||||||
$headers[] = "From: noreply@andre-kempf.com";
|
|
||||||
|
|
||||||
mail($recipient, $subject, $message, implode("\r\n", $headers));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // Reject non-POST or OPTIONS requests.
|
|
||||||
header("Allow: POST", true, 405);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
@ -34,13 +34,16 @@ export default function Contact() {
|
||||||
setError(false);
|
setError(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("https://andre-kempf.com/sendMail.php", {
|
const response = await fetch(
|
||||||
|
"https://andre-kempf.com/backend/sendMail.php",
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ name, email, message, honeypot }),
|
body: JSON.stringify({ name, email, message, honeypot }),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setSubmitted(true);
|
setSubmitted(true);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue