Restore contact form and make it functional with email sending

- Replace mailto link with original contact form (name, email, message fields)
- Add contact API endpoint that sends email via mail() and saves to messages.json
- Restore .contact-form CSS styles and translation keys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 11:28:26 +02:00
parent 5dfbbacf39
commit dcc1205244
4 changed files with 85 additions and 12 deletions

29
api.php
View File

@@ -909,6 +909,35 @@ switch ($action) {
} }
ok(['loggedIn' => false]); ok(['loggedIn' => false]);
// ─── Yhteydenotto ──────────────────────────────────────────
case 'contact':
$name = trim($body['name'] ?? '');
$email = trim($body['email'] ?? '');
$message = trim($body['message'] ?? '');
if (!$name || !$email || !$message) err('Täytä kaikki kentät.');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) err('Sähköpostiosoite ei kelpaa.');
if (mb_strlen($message) > 5000) err('Viesti on liian pitkä.');
// Tallenna varmuuskopio
$messages = readData('messages.json', []);
$messages[] = [
'name' => htmlspecialchars($name, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
'email' => htmlspecialchars($email, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
'message' => htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
'date' => date('Y-m-d H:i:s'),
];
writeData('messages.json', $messages);
// Lähetä sähköposti
$to = 'info@tykkaa.fi';
$subject = "tykkää.fi: viesti käyttäjältä $name";
$body = "Nimi: $name\nSähköposti: $email\n\nViesti:\n$message";
$headers = "From: noreply@tykkaa.fi\r\nReply-To: $email\r\nContent-Type: text/plain; charset=UTF-8";
@mail($to, $subject, $body, $headers);
ok(['sent' => true]);
default: default:
err('Unknown action'); err('Unknown action');
} }

View File

@@ -64,7 +64,12 @@
<div class="container"> <div class="container">
<h2 data-i18n="contact_title"></h2> <h2 data-i18n="contact_title"></h2>
<p data-i18n="contact_desc"></p> <p data-i18n="contact_desc"></p>
<a href="mailto:info@tykkaa.fi" class="contact-email">info@tykkaa.fi</a> <form class="contact-form" onsubmit="handleSubmit(event)">
<input type="text" id="contact-name" data-i18n-ph="name_ph" required />
<input type="email" id="contact-email" data-i18n-ph="email_ph" required />
<textarea id="contact-msg" data-i18n-ph="msg_ph" rows="4" required></textarea>
<button type="submit" class="btn" data-i18n="send_btn"></button>
</form>
</div> </div>
</section> </section>

View File

@@ -15,7 +15,9 @@ const T = {
about_title: 'Mikä tykkää.fi?', about_title: 'Mikä tykkää.fi?',
about_text: 'tykkää.fi on avoin yhteisö, jonne kuka tahansa voi tulla jakamaan asioita joista tykkää. Reseptejä, neulomisohjeita, käsityöideoita tai ihan mitä muuta kivaa — tärkeintä on jakamisen ilo. Lisää oma julkaisusi yläkulman napista!', about_text: 'tykkää.fi on avoin yhteisö, jonne kuka tahansa voi tulla jakamaan asioita joista tykkää. Reseptejä, neulomisohjeita, käsityöideoita tai ihan mitä muuta kivaa — tärkeintä on jakamisen ilo. Lisää oma julkaisusi yläkulman napista!',
contact_title: 'Ota yhteyttä', contact_title: 'Ota yhteyttä',
contact_desc: 'Kysyttävää tai ehdotuksia? Lähetä meille sähköpostia!', contact_desc: 'Kysyttävää tai ehdotuksia? Lähetä meille viestiä!',
name_ph: 'Nimesi', email_ph: 'Sähköpostisi', msg_ph: 'Viestisi...',
send_btn: 'Lähetä viesti', msg_sent: 'Viesti lähetetty! ✓', msg_error: 'Virhe lähetyksessä. Yritä uudelleen.',
footer: 'Avoin yhteisö kaikille', footer: 'Avoin yhteisö kaikille',
modal_by: 'Kirjoittanut', modal_by: 'Kirjoittanut',
modal_ingredients: 'Ainekset', modal_steps: 'Ohjeet', modal_ingredients: 'Ainekset', modal_steps: 'Ohjeet',
@@ -47,7 +49,9 @@ const T = {
about_title: 'What is tykkää.fi?', about_title: 'What is tykkää.fi?',
about_text: 'tykkää.fi is an open community where anyone can share things they love. Recipes, knitting patterns, craft ideas or anything fun — the joy of sharing is what matters. Add your own post using the button in the top corner!', about_text: 'tykkää.fi is an open community where anyone can share things they love. Recipes, knitting patterns, craft ideas or anything fun — the joy of sharing is what matters. Add your own post using the button in the top corner!',
contact_title: 'Get in Touch', contact_title: 'Get in Touch',
contact_desc: 'Questions or suggestions? Send us an email!', contact_desc: 'Questions or suggestions? Send us a message!',
name_ph: 'Your name', email_ph: 'Your email', msg_ph: 'Your message...',
send_btn: 'Send Message', msg_sent: 'Message Sent! ✓', msg_error: 'Error sending. Please try again.',
footer: 'Open community for everyone', footer: 'Open community for everyone',
modal_by: 'By', modal_by: 'By',
modal_ingredients: 'Ingredients', modal_steps: 'Instructions', modal_ingredients: 'Ingredients', modal_steps: 'Instructions',
@@ -638,6 +642,29 @@ async function submitPublicPost() {
// =========================== // ===========================
// CONTACT FORM // CONTACT FORM
// =========================== // ===========================
// ===========================
// CONTACT FORM
// ===========================
async function handleSubmit(e) {
e.preventDefault();
const btn = e.target.querySelector('button[type="submit"]');
const name = document.getElementById('contact-name').value.trim();
const email = document.getElementById('contact-email').value.trim();
const message = document.getElementById('contact-msg').value.trim();
btn.disabled = true;
try {
await apiPost('contact', { name, email, message });
btn.textContent = t('msg_sent');
btn.style.background = '#5c8a4a';
e.target.reset();
setTimeout(() => { btn.textContent = t('send_btn'); btn.style.background = ''; btn.disabled = false; }, 4000);
} catch {
btn.textContent = t('msg_error');
btn.style.background = '#c04040';
setTimeout(() => { btn.textContent = t('send_btn'); btn.style.background = ''; btn.disabled = false; }, 4000);
}
}
// =========================== // ===========================
// INIT // INIT
// =========================== // ===========================

View File

@@ -324,19 +324,31 @@ nav a:hover { color: #fff; }
.contact h2 { font-size: 1.9rem; color: var(--warm-brown); margin-bottom: 8px; } .contact h2 { font-size: 1.9rem; color: var(--warm-brown); margin-bottom: 8px; }
.contact > .container > p { color: var(--text-light); margin-bottom: 28px; } .contact > .container > p { color: var(--text-light); margin-bottom: 28px; }
.contact-email { .contact-form {
font-size: 1.3rem; display: flex;
font-family: 'Georgia', serif; flex-direction: column;
color: var(--accent); gap: 14px;
text-decoration: none; max-width: 520px;
transition: color 0.2s; margin: 0 auto;
} }
.contact-email:hover { .contact-form input,
color: #a0522d; .contact-form textarea {
text-decoration: underline; padding: 12px 18px;
border: 2px solid var(--border);
border-radius: 10px;
font-size: 1rem;
font-family: 'Georgia', serif;
background: #fff;
color: var(--text);
outline: none;
transition: border-color 0.2s;
resize: vertical;
} }
.contact-form input:focus,
.contact-form textarea:focus { border-color: var(--accent); }
/* ===================== /* =====================
FOOTER FOOTER
===================== */ ===================== */