E-Mails jetzt auch schön

This commit is contained in:
2026-03-01 22:08:15 +01:00
parent 6f4150dbb3
commit c922ef6668
31 changed files with 2054 additions and 1897 deletions

View File

@@ -0,0 +1,49 @@
package de.oaa.xxx.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Serves /css/variables.css dynamically from application.properties theme settings.
* All HTML pages load this first, so changing app.theme.* immediately updates the whole UI.
*/
@RestController
public class ThemeController {
@Value("${app.theme.color-bg:#1a1a2e}")
private String colorBg;
@Value("${app.theme.color-card:#16213e}")
private String colorCard;
@Value("${app.theme.color-primary:#e94560}")
private String colorPrimary;
@Value("${app.theme.color-secondary:#0f3460}")
private String colorSecondary;
@Value("${app.theme.color-text:#eeeeee}")
private String colorText;
@Value("${app.theme.color-muted:#888888}")
private String colorMuted;
@Value("${app.theme.color-success:#2ecc71}")
private String colorSuccess;
@GetMapping(value = "/css/variables.css", produces = "text/css")
public String variables() {
return """
:root {
--color-bg: %s;
--color-card: %s;
--color-primary: %s;
--color-secondary: %s;
--color-text: %s;
--color-muted: %s;
--color-success: %s;
}
""".formatted(colorBg, colorCard, colorPrimary, colorSecondary, colorText, colorMuted, colorSuccess);
}
}

View File

@@ -0,0 +1,87 @@
package de.oaa.xxx.mail;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* Generates HTML email bodies with inline styles derived from app.theme.* properties.
* Changing theme colors in application.properties automatically updates email appearance.
* (Email clients don't support external CSS or CSS variables, so styles must be inlined.)
*/
@Service
public class MailTemplateService {
@Value("${app.theme.color-bg:#1a1a2e}")
private String colorBg;
@Value("${app.theme.color-card:#16213e}")
private String colorCard;
@Value("${app.theme.color-primary:#e94560}")
private String colorPrimary;
@Value("${app.theme.color-secondary:#0f3460}")
private String colorSecondary;
@Value("${app.theme.color-text:#eeeeee}")
private String colorText;
@Value("${app.theme.color-muted:#888888}")
private String colorMuted;
@Value("${app.theme.color-success:#2ecc71}")
private String colorSuccess;
public String buildActivationMail(String name, String activationLink, String activatePageUrl, String uuid) {
return """
<!DOCTYPE html>
<html lang="de">
<body style="margin:0; padding:2rem; background:%s; font-family:'Segoe UI',Arial,sans-serif; color:%s;">
<div style="max-width:460px; margin:0 auto; background:%s; border:1px solid %s; border-radius:12px; padding:2.5rem; box-shadow:0 8px 32px rgba(0,0,0,0.5);">
<h1 style="color:%s; text-align:center; margin:0 0 1.5rem 0; font-size:1.6rem;">XXX The Game</h1>
<p style="color:%s; margin:0 0 0.75rem 0;">Moin %s,</p>
<p style="color:%s; margin:0 0 0.5rem 0;">Vielen Dank für deine Anmeldung!</p>
<p style="color:%s; margin:0 0 2rem 0;">Klick auf den Button, um deine E-Mail-Adresse zu bestätigen:</p>
<div style="text-align:center; margin:0 0 2rem 0;">
<a href="%s"
style="display:inline-block; padding:0.75rem 2.5rem; background:%s; color:#ffffff;
border-radius:6px; text-decoration:none; font-weight:600; font-size:1rem;">
E-Mail bestätigen
</a>
</div>
<hr style="border:none; border-top:1px solid %s; margin:0 0 1.5rem 0;">
<p style="color:%s; font-size:0.9em; margin:0 0 0.5rem 0;">
Falls der Button nicht funktioniert, öffne
<a href="%s" style="color:%s;">%s</a>
und gib folgenden Code ein:
</p>
<p style="font-family:monospace; font-size:1.1em; background:%s; color:%s;
padding:0.5rem 1rem; border-radius:4px; display:inline-block; margin:0.5rem 0 1.5rem 0;">
%s
</p>
<p style="color:%s; margin:0;">Viel Spaß beim Spiel!</p>
</div>
</body>
</html>
""".formatted(
colorBg, colorText,
colorCard, colorSecondary,
colorPrimary,
colorText, name,
colorText,
colorText,
activationLink, colorPrimary,
colorSecondary,
colorMuted, activatePageUrl, colorPrimary, activatePageUrl,
colorSecondary, colorText,
uuid,
colorMuted
);
}
}

View File

@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController;
import de.oaa.xxx.mail.Email;
import de.oaa.xxx.mail.MailService;
import de.oaa.xxx.mail.MailTemplateService;
import de.oaa.xxx.user.UserRepository;
@RestController
@@ -25,12 +26,14 @@ public class RegistrationController {
private final RegistrationRepository registrationRepository;
private final UserRepository userRepository;
private final MailService mailService;
private final MailTemplateService mailTemplateService;
public RegistrationController(RegistrationRepository registrationRepository, UserRepository userRepository,
MailService mailService) {
MailService mailService, MailTemplateService mailTemplateService) {
this.registrationRepository = registrationRepository;
this.userRepository = userRepository;
this.mailService = mailService;
this.mailTemplateService = mailTemplateService;
}
@PostMapping
@@ -43,12 +46,14 @@ public class RegistrationController {
}
RegistrationEntity entity = RegistrationEntity.create(registration);
registrationRepository.save(entity);
registration.setId(entity.getRegistrationId());
Email email = new Email();
email.setTitel("Bitte bestätige deine E-Mail Adresse");
email.setEmailAdresse(registration.getEmail());
email.setText(getMailText(registration));
String uuid = entity.getRegistrationId().toString();
String activationLink = baseUrl + "/activation/" + uuid;
String activatePageUrl = baseUrl + "/activate.html";
email.setText(mailTemplateService.buildActivationMail(registration.getName(), activationLink, activatePageUrl, uuid));
if (!mailService.send(email)) {
registrationRepository.delete(entity);
@@ -57,20 +62,5 @@ public class RegistrationController {
return ResponseEntity.status(202).build();
}
private String getMailText(Registration registration) {
String uuid = registration.getId().toString();
return """
<html>
<h1>Moin %s</h1>
<p>Vielen Dank für deine Anmeldung bei XXX-BDSM Game!</p>
<p>Klicke <a href="%s/activation/%s">hier</a> um deine E-Mail-Adresse zu bestätigen.</p>
<hr style="margin:1.5rem 0; border:none; border-top:1px solid #ddd;">
<p style="color:#666; font-size:0.9em;">
Falls der Link nicht funktioniert, kannst du deine E-Mail-Adresse auch manuell bestätigen.<br>
Öffne dazu <a href="%s/activate.html">%s/activate.html</a> und gib folgenden Code ein:
</p>
<p style="font-family:monospace; font-size:1.1em; background:#f4f4f4; padding:0.5rem 1rem; border-radius:4px; display:inline-block;">%s</p>
<p>Viel Spaß beim Spiel!</p>
</html>""".formatted(registration.getName(), baseUrl, uuid, baseUrl, baseUrl, uuid);
}
}

View File

@@ -31,6 +31,18 @@ jwt.keystore.path=classpath:xxx.jks
jwt.keystore.password=${JWT_KEYSTORE_PASSWORD:XUR!Rv&f$j3UsqD&}
jwt.keystore.alias=xxx
# App
app.base-url=http://localhost:8080
# Theme alle Farben hier ändern, Email-Style passt sich automatisch an
app.theme.color-bg=#1a1a2e
app.theme.color-card=#16213e
app.theme.color-primary=#e94560
app.theme.color-secondary=#0f3460
app.theme.color-text=#eeeeee
app.theme.color-muted=#888888
app.theme.color-success=#2ecc71
# Server
server.port=8080
server.servlet.context-path=/

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XXX The Game Aktivierung</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>

View File

@@ -12,25 +12,25 @@ body {
flex-direction: column;
align-items: center;
justify-content: center;
background: #1a1a2e;
background: var(--color-bg);
font-family: 'Segoe UI', sans-serif;
color: #eee;
color: var(--color-text);
gap: 2rem;
}
h1 {
color: #e94560;
color: var(--color-primary);
}
p {
color: #888;
color: var(--color-muted);
font-size: 1rem;
}
/* ── Card ── */
.card {
background: #16213e;
border: 1px solid #0f3460;
background: var(--color-card);
border: 1px solid var(--color-secondary);
border-radius: 12px;
padding: 2.5rem;
width: 100%;
@@ -48,7 +48,7 @@ p {
.subtitle {
text-align: center;
font-size: 0.85rem;
color: #888;
color: var(--color-muted);
margin-bottom: 2rem;
}
@@ -64,24 +64,24 @@ label {
input {
width: 100%;
padding: 0.65rem 0.9rem;
border: 1px solid #0f3460;
border: 1px solid var(--color-secondary);
border-radius: 6px;
background: #0f3460;
color: #eee;
background: var(--color-secondary);
color: var(--color-text);
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
input:focus {
border-color: #e94560;
border-color: var(--color-primary);
}
/* ── Buttons ── */
button, .btn {
display: inline-block;
padding: 0.75rem 2.5rem;
background: #e94560;
background: var(--color-primary);
color: #fff;
border: none;
border-radius: 6px;
@@ -108,7 +108,7 @@ button.full-width {
}
button.secondary {
background: #0f3460;
background: var(--color-secondary);
font-weight: normal;
padding: 0.3rem 0.7rem;
font-size: 0.75rem;
@@ -132,14 +132,14 @@ button.secondary:hover {
.message.error {
background: #3d0f1a;
border: 1px solid #e94560;
color: #e94560;
border: 1px solid var(--color-primary);
color: var(--color-primary);
}
.message.success {
background: #0f3d1a;
border: 1px solid #2ecc71;
color: #2ecc71;
border: 1px solid var(--color-success);
color: var(--color-success);
}
/* ── Token box ── */
@@ -147,7 +147,7 @@ button.secondary:hover {
margin-top: 1.25rem;
padding: 0.65rem 0.9rem;
background: #0f1e3d;
border: 1px solid #0f3460;
border: 1px solid var(--color-secondary);
border-radius: 6px;
font-size: 0.75rem;
color: #aaa;

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XXX The Game</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XXX The Game Login</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XXX The Game Registrierung</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XXX The Game</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>