E-Mails jetzt auch schön
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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=/
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user