Osadz logo w mailach, popraw naglowek i nadaj adresy krokom logowania
Deploy Polska Lokalnie / deploy (push) Successful in 12s

Maile:
- Logo jest zalacznikiem inline (cid:) zamiast obrazka z adresu serwisu.
  Wczesniej w mailach z lokalnego srodowiska (i w programach blokujacych
  obrazki z zewnatrz) w miejscu logo byla ikona bledu. Usuniety tekst
  zastepczy, ktory dublowal napis "Polska Lokalnie".

Naglowek:
- Menu nie nachodzi na logo ani panel przy zadnej szerokosci 1110-1920 px.
  Ponizej 1560 px "Ulubione" to samo serce (nazwa w dymku), mniejsze odstepy
  i przycisk "Dodaj ogloszenie" bez sztywnej szerokosci; w 1101-1270 px
  mniejszy napis logo i tekst przycisku.

Logowanie i rejestracja (standard routingu):
- Kazdy krok ma wlasny adres: /logowanie, /logowanie/nie-pamietam-hasla,
  /rejestracja, /rejestracja/osoba-prywatna, /rejestracja/firma,
  /rejestracja/sprawdz-poczte. Dzialaja F5, Wstecz/Dalej i nowa karta,
  formularz czysci sie takze przy Wstecz/Dalej, a cel powrotu po
  zalogowaniu nie ginie przy przejsciach miedzy krokami.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 11:26:47 +02:00
parent 72af24469a
commit e7402dbfed
8 changed files with 238 additions and 17 deletions
@@ -132,7 +132,7 @@ public class AccountEmailService {
+ "<div style=\"max-width:560px;margin:0 auto;padding:24px 12px;font-family:Arial,Helvetica,sans-serif;color:#24344b;\">"
// Naglowek z logo + nazwa
+ "<div style=\"background:#0f2340;border-radius:14px 14px 0 0;padding:20px 24px;\">"
+ "<img src=\"" + baseUrl + "/favicon.png\" width=\"34\" height=\"34\" alt=\"Polska Lokalnie\""
+ "<img src=\"cid:" + EmailSender.LOGO_CID + "\" width=\"34\" height=\"34\" alt=\"\""
+ " style=\"vertical-align:middle;border-radius:8px;\">"
+ "<span style=\"vertical-align:middle;margin-left:10px;font-size:20px;font-weight:bold;color:#ffffff;\">"
+ "Polska <span style=\"color:#35d38a;\">Lokalnie</span></span>"
@@ -5,6 +5,7 @@ import jakarta.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
@@ -18,6 +19,14 @@ import pl.polskalokalnie.mailconfig.SmtpConfig;
@Component
public class EmailSender {
/**
* Logo serwisu osadzone w samym mailu (obrazek inline). Szablon odwoluje sie do niego przez
* src="cid:...", wiec logo nie zalezy od pobierania z adresu serwisu - dziala tez w mailach
* ze srodowiska lokalnego i w programach, ktore blokuja obrazki z zewnatrz.
*/
public static final String LOGO_CID = "polskalokalnie-logo";
private static final String LOGO_RESOURCE = "mail/logo.png";
public SendResult send(SmtpConfig config, String to, String subject, String htmlBody) {
if (config == null || config.getHost() == null || config.getHost().isBlank()
|| config.getUsername() == null || config.getUsername().isBlank()) {
@@ -26,11 +35,7 @@ public class EmailSender {
try {
JavaMailSenderImpl mailSender = buildMailSender(config);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, false, StandardCharsets.UTF_8.name());
helper.setFrom(fromAddress(config));
helper.setTo(to);
helper.setSubject(subject == null ? "" : subject);
helper.setText(htmlBody, true);
fillMessage(message, fromAddress(config), to, subject, htmlBody);
mailSender.send(message);
String messageId = message.getMessageID();
return SendResult.ok(messageId);
@@ -39,6 +44,20 @@ public class EmailSender {
}
}
// Tresc HTML; gdy szablon uzywa logo (cid:), wiadomosc jest wieloczesciowa z logo jako zalacznikiem inline.
void fillMessage(MimeMessage message, InternetAddress from, String to, String subject, String htmlBody)
throws jakarta.mail.MessagingException {
boolean withLogo = htmlBody != null && htmlBody.contains("cid:" + LOGO_CID);
MimeMessageHelper helper = new MimeMessageHelper(message, withLogo, StandardCharsets.UTF_8.name());
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject == null ? "" : subject);
helper.setText(htmlBody, true);
if (withLogo) {
helper.addInline(LOGO_CID, new ClassPathResource(LOGO_RESOURCE), "image/png");
}
}
private JavaMailSenderImpl buildMailSender(SmtpConfig config) {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(config.getHost());
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

@@ -0,0 +1,79 @@
package pl.polskalokalnie.send;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.mail.BodyPart;
import jakarta.mail.Multipart;
import jakarta.mail.Part;
import jakarta.mail.Session;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.junit.jupiter.api.Test;
class EmailSenderTest {
private final EmailSender sender = new EmailSender();
// Regresja: logo bylo linkiem do adresu serwisu, wiec w mailach z lokalnego srodowiska
// (i w programach blokujacych obrazki z zewnatrz) zamiast logo pojawiala sie ikona bledu.
@Test
void templateWithLogoEmbedsItAsInlineImage() throws Exception {
MimeMessage message = newMessage();
sender.fillMessage(message, new InternetAddress("info@polskalokalnie.pl"), "jan@example.com", "Temat",
"<img src=\"cid:" + EmailSender.LOGO_CID + "\"><p>Tresc</p>");
BodyPart logo = null;
boolean hasHtml = false;
for (BodyPart part : allParts(message)) {
if (part.getHeader("Content-ID") != null) {
logo = part;
}
hasHtml |= part.getContentType().startsWith("text/html");
}
assertThat(logo).as("czesc z logo").isNotNull();
assertThat(logo.getHeader("Content-ID")[0]).isEqualTo("<" + EmailSender.LOGO_CID + ">");
assertThat(logo.getContentType()).startsWith("image/png");
assertThat(logo.getDisposition()).isEqualTo(Part.INLINE);
assertThat(hasHtml).as("tresc HTML").isTrue();
}
@Test
void messageWithoutLogoStaysSinglePart() throws Exception {
MimeMessage message = newMessage();
sender.fillMessage(message, new InternetAddress("info@polskalokalnie.pl"), "jan@example.com", "Temat",
"<p>Kampania bez logo</p>");
assertThat(message.getContent()).isInstanceOf(String.class);
}
private static MimeMessage newMessage() {
return new MimeMessage(Session.getInstance(new Properties()));
}
// Przebudowa przez zapis, zeby czytac wiadomosc tak, jak zobaczy ja serwer poczty.
private static List<BodyPart> allParts(MimeMessage message) throws Exception {
message.saveChanges();
ByteArrayOutputStream raw = new ByteArrayOutputStream();
message.writeTo(raw);
MimeMessage parsed = new MimeMessage(Session.getInstance(new Properties()),
new java.io.ByteArrayInputStream(raw.toByteArray()));
List<BodyPart> result = new ArrayList<>();
collect((Multipart) parsed.getContent(), result);
return result;
}
private static void collect(Multipart multipart, List<BodyPart> result) throws Exception {
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart part = multipart.getBodyPart(i);
if (part.getContent() instanceof Multipart nested) {
collect(nested, result);
} else {
result.add(part);
}
}
}
}