Files
MichalC d11808b471 fix: standaryzacja i bezpieczeństwo formularzy
- Ujednolicono kontakt/actions.ts (rate limit, escapeHtml, getMailClient, usunięto bezpośrednie Resend)
- Usunięto nieużywany plik lib/recaptcha.ts
- Naprawiono brak obsługi błędów API (Resend error) w zgloszenie-awarii, zamowienie i zapytanie-multisplit
- Dodano logowanie błędów w catch dla tych formularzy
- Posprzątano stare skrypty robocze z roota
2026-08-02 21:10:24 +02:00

106 lines
5.2 KiB
TypeScript

"use server";
import { z } from "zod";
import { headers } from "next/headers";
import { escapeHtml, rateLimit, verifyRecaptcha, baseContactSchema } from "@/lib/security";
import { getMailClient } from "@/lib/mail";
const multisplitFormSchema = baseContactSchema.extend({
city: z.string().min(1, "Wybierz miasto"),
rooms: z.string().optional(),
message: z.string().optional(),
productName: z.string(),
productSku: z.string(),
recaptchaToken: z.string().optional(),
});
export async function submitMultisplitForm(data: Record<string, unknown>) {
try {
const ip = (await headers()).get('x-forwarded-for') || 'unknown';
if (!rateLimit(ip)) {
return { success: false, message: 'Wysłano zbyt wiele zapytań. Spróbuj ponownie później.' };
}
if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY && process.env.RECAPTCHA_SECRET_KEY) {
if (!data.recaptchaToken || typeof data.recaptchaToken !== 'string') {
return { success: false, message: "Weryfikacja bezpieczeństwa nie powiodła się. Odśwież stronę i spróbuj ponownie." };
}
const success = await verifyRecaptcha(data.recaptchaToken);
if (!success) {
return { success: false, message: "Weryfikacja bezpieczeństwa nie powiodła się. Odśwież stronę i spróbuj ponownie." };
}
}
const mailClient = getMailClient();
if (!mailClient) {
return { success: false, message: "Funkcja wysyłania wiadomości jest tymczasowo niedostępna." };
}
// Convert boolean consent to string for base schema compatibility if it comes as boolean
const processData = { ...data };
if (typeof processData.consent === 'boolean') {
processData.consent = processData.consent ? "true" : "false";
}
const validatedData = multisplitFormSchema.parse(processData);
const { data: emailData, error } = await mailClient.resend.emails.send({
from: mailClient.fromEmail,
to: mailClient.toEmail,
replyTo: validatedData.email || undefined,
subject: `Zapytanie o multisplit: ${escapeHtml(validatedData.productName)} (${escapeHtml(validatedData.productSku)})`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden; background-color: #ffffff;">
<div style="background-color: #0F2A47; padding: 20px; text-align: center;">
<h1 style="color: #ffffff; margin: 0; font-size: 24px;">Zapytanie o multisplit</h1>
</div>
<div style="padding: 24px; color: #1e293b;">
<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9; width: 120px;"><strong>Imię:</strong></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.name)}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>Telefon:</strong></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.phone)}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>E-mail:</strong></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.email || 'Brak')}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>Miasto:</strong></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.city)}</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>Produkt:</strong></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.productName)} (${escapeHtml(validatedData.productSku)})</td>
</tr>
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;"><strong>Pokoje:</strong></td>
<td style="padding: 8px 0; border-bottom: 1px solid #f1f5f9;">${escapeHtml(validatedData.rooms || '')}</td>
</tr>
</table>
<h2 style="font-size: 18px; border-bottom: 2px solid #f1f5f9; padding-bottom: 10px;">Dodatkowe informacje:</h2>
<div style="background-color: #f8fafc; padding: 15px; border-radius: 6px; font-size: 15px; line-height: 1.6; white-space: pre-wrap;">
${escapeHtml(validatedData.message || 'Brak')}
</div>
</div>
</div>
`,
});
if (error) {
console.error("Resend Error:", error);
return { success: false, message: "Nie udało się wysłać wiadomości ze względu na błąd serwera pocztowego." };
}
console.log("Wysłano e-mail (ID):", emailData?.id);
return { success: true, message: "Dziękujemy za zapytanie! Skontaktujemy się z Tobą najszybciej jak to możliwe." };
} catch (error) {
console.error("Błąd formularza:", error);
return { success: false, message: "Błąd walidacji lub błąd serwera. Spróbuj ponownie później." };
}
}