init
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
.vscode
|
||||||
|
nachtreffen
|
||||||
|
target
|
||||||
|
/application.properties
|
||||||
|
/namen.csv
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>de.champonthis</groupId>
|
||||||
|
<artifactId>abi</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<name>abi</name>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.5.6</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-text</artifactId>
|
||||||
|
<version>1.14.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mariadb.jdbc</groupId>
|
||||||
|
<artifactId>mariadb-java-client</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package de.champonthis.abi;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication()
|
||||||
|
public class AbiApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AbiApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package de.champonthis.abi.buisinesslogic;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import de.champonthis.abi.entity.Contact;
|
||||||
|
import de.champonthis.abi.entity.ContactData;
|
||||||
|
import de.champonthis.abi.repository.ContactDataRepository;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ContactDataManager {
|
||||||
|
|
||||||
|
private final ContactDataRepository contactDataRepository;
|
||||||
|
private final EmailService mailService;
|
||||||
|
|
||||||
|
public ContactDataManager(ContactDataRepository contactDataRepository, EmailService mailService) {
|
||||||
|
this.contactDataRepository = contactDataRepository;
|
||||||
|
this.mailService = mailService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactData findByContactAndReportedBy(Long contact, Long reportedBy) {
|
||||||
|
return contactDataRepository.findByContactAndReportedBy(contact, reportedBy).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactData findByinviteToken(String inviteToken) {
|
||||||
|
return contactDataRepository.findByinviteToken(inviteToken).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(ContactData contactData) {
|
||||||
|
contactDataRepository.save(contactData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendInviteMail(Contact contact, ContactData contactData, Contact reportedBy) {
|
||||||
|
if (StringUtils.isNoneEmpty(contactData.getEmail()) && StringUtils.isNoneEmpty(contactData.getInviteToken())) {
|
||||||
|
Map<String, Object> variables = new HashMap<>();
|
||||||
|
variables.put("contact", contact);
|
||||||
|
variables.put("reportedBy", reportedBy);
|
||||||
|
variables.put("inviteToken", contactData.getInviteToken());
|
||||||
|
|
||||||
|
String to = (StringUtils.isNoneEmpty(contact.getUpdatedName()) ? contact.getUpdatedName()
|
||||||
|
: contact.getName()) + " <" + contactData.getEmail() + ">";
|
||||||
|
|
||||||
|
mailService.sendTemplateEmail(to, "Du wurdest zum Abi-Treffen eingeladen",
|
||||||
|
"invite", variables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package de.champonthis.abi.buisinesslogic;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.text.similarity.LevenshteinDistance;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import de.champonthis.abi.entity.Contact;
|
||||||
|
import de.champonthis.abi.entity.ContactData;
|
||||||
|
import de.champonthis.abi.repository.ContactRepository;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ContactManager {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ContactManager.class);
|
||||||
|
|
||||||
|
private final ContactRepository contactRepository;
|
||||||
|
private final EmailService mailService;
|
||||||
|
private LevenshteinDistance levenshteinDistance = LevenshteinDistance.getDefaultInstance();
|
||||||
|
|
||||||
|
public ContactManager(ContactRepository contactRepository, EmailService mailService) {
|
||||||
|
this.contactRepository = contactRepository;
|
||||||
|
this.mailService = mailService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void loadCsv() {
|
||||||
|
try {
|
||||||
|
File resource = new File("namen.csv");
|
||||||
|
if (resource.exists() && resource.isFile()) {
|
||||||
|
try (BufferedReader reader = new BufferedReader(
|
||||||
|
new InputStreamReader(new FileInputStream(resource), StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (StringUtils.isNoneEmpty(line) && !line.startsWith("#")
|
||||||
|
&& contactRepository.findByName(line).isEmpty()) {
|
||||||
|
Contact contact = new Contact();
|
||||||
|
contact.setName(line);
|
||||||
|
save(contact);
|
||||||
|
logger.info("Created contact: #" + contact.getId() + ": " + contact.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("No names.csv found!");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Could not read names.csv!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Contact findById(Long id) {
|
||||||
|
return contactRepository.findById(id).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Contact findByName(String name) {
|
||||||
|
return contactRepository.findByName(name).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Contact findByToken(String token) {
|
||||||
|
return contactRepository.findByToken(token).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Contact findByLevenshteinDistance(String name) {
|
||||||
|
return findByLevenshteinDistance(name, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Contact findByLevenshteinDistance(String name, int threshold) {
|
||||||
|
Contact result = null;
|
||||||
|
for (Contact contact : contactRepository.findAll()) {
|
||||||
|
int distance = levenshteinDistance.apply(name.toLowerCase(), contact.getName().toLowerCase());
|
||||||
|
if (distance <= threshold) {
|
||||||
|
result = contact;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(Contact contact) {
|
||||||
|
contactRepository.save(contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendTokenMail(Contact contact, ContactData contactData) {
|
||||||
|
if (StringUtils.isNoneEmpty(contact.getToken()) && StringUtils.isNoneEmpty(contactData.getEmail())) {
|
||||||
|
Map<String, Object> variables = new HashMap<>();
|
||||||
|
variables.put("contact", contact);
|
||||||
|
variables.put("token", contact.getToken());
|
||||||
|
|
||||||
|
String to = (StringUtils.isNoneEmpty(contact.getUpdatedName()) ? contact.getUpdatedName()
|
||||||
|
: contact.getName()) + " <" + contactData.getEmail() + ">";
|
||||||
|
|
||||||
|
mailService.sendTemplateEmail(to, "Dein persönlicher Link für das Abi-Treffen",
|
||||||
|
"confirmation", variables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder().withoutPadding();
|
||||||
|
|
||||||
|
public static String generateToken() {
|
||||||
|
return generateToken(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateToken(int byteLength) {
|
||||||
|
byte[] randomBytes = new byte[byteLength];
|
||||||
|
secureRandom.nextBytes(randomBytes);
|
||||||
|
return base64Encoder.encodeToString(randomBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package de.champonthis.abi.buisinesslogic;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.thymeleaf.TemplateEngine;
|
||||||
|
import org.thymeleaf.context.Context;
|
||||||
|
|
||||||
|
import jakarta.mail.MessagingException;
|
||||||
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmailService {
|
||||||
|
|
||||||
|
private final JavaMailSender mailSender;
|
||||||
|
private final TemplateEngine templateEngine;
|
||||||
|
|
||||||
|
@Value("${app.url:}")
|
||||||
|
private String url;
|
||||||
|
@Value("${app.mail.from:}")
|
||||||
|
private String from;
|
||||||
|
@Value("${app.mail.fromMail:}")
|
||||||
|
private String fromMail;
|
||||||
|
|
||||||
|
public EmailService(JavaMailSender mailSender, TemplateEngine templateEngine) {
|
||||||
|
this.mailSender = mailSender;
|
||||||
|
this.templateEngine = templateEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendTemplateEmail(String to, String subject, String templateName, Map<String, Object> variables) {
|
||||||
|
Context context = new Context();
|
||||||
|
variables.put("url", url);
|
||||||
|
context.setVariables(variables);
|
||||||
|
|
||||||
|
String htmlContent = templateEngine.process("email/" + templateName, context);
|
||||||
|
|
||||||
|
MimeMessage message = mailSender.createMimeMessage();
|
||||||
|
try {
|
||||||
|
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
|
||||||
|
|
||||||
|
helper.setFrom(from + " <" + fromMail + ">");
|
||||||
|
helper.setTo(to);
|
||||||
|
helper.setSubject(subject);
|
||||||
|
helper.setText(htmlContent, true);
|
||||||
|
|
||||||
|
mailSender.send(message);
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package de.champonthis.abi.controller;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import de.champonthis.abi.buisinesslogic.ContactDataManager;
|
||||||
|
import de.champonthis.abi.buisinesslogic.ContactManager;
|
||||||
|
import de.champonthis.abi.controller.request.ContactRequest;
|
||||||
|
import de.champonthis.abi.entity.Contact;
|
||||||
|
import de.champonthis.abi.entity.ContactData;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/contact")
|
||||||
|
public class ContactController {
|
||||||
|
|
||||||
|
private final ContactManager contactManager;
|
||||||
|
private final ContactDataManager contactDataManager;
|
||||||
|
|
||||||
|
public ContactController(ContactManager contactManager, ContactDataManager contactDataManager) {
|
||||||
|
this.contactManager = contactManager;
|
||||||
|
this.contactDataManager = contactDataManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping()
|
||||||
|
public Contact find(@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String authToken,
|
||||||
|
@RequestParam String name) {
|
||||||
|
Contact contact = contactManager.findByLevenshteinDistance(name);
|
||||||
|
if (contact == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNoneEmpty(contact.getToken()) && !contact.getToken().equals(authToken)) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.ALREADY_REPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping()
|
||||||
|
public void update(@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String authToken,
|
||||||
|
@Valid @RequestBody ContactRequest request) {
|
||||||
|
Contact contact = contactManager.findByName(request.getName());
|
||||||
|
if (contact == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNoneEmpty(contact.getToken()) && !contact.getToken().equals(authToken)) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.ALREADY_REPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
contact.setUpdatedName(request.getUpdatedName());
|
||||||
|
|
||||||
|
boolean sendToken = false;
|
||||||
|
|
||||||
|
if (request.getCommitted() != null) {
|
||||||
|
contact.setCommitted(request.getCommitted());
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(contact.getToken())) {
|
||||||
|
String token = ContactManager.generateToken();
|
||||||
|
while (contactManager.findByToken(token) != null) {
|
||||||
|
token = ContactManager.generateToken();
|
||||||
|
}
|
||||||
|
contact.setToken(token);
|
||||||
|
sendToken = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contactManager.save(contact);
|
||||||
|
|
||||||
|
if (sendToken) {
|
||||||
|
ContactData contactData = contactDataManager.findByContactAndReportedBy(contact.getId(), contact.getId());
|
||||||
|
if (contactData != null) {
|
||||||
|
contactManager.sendTokenMail(contact, contactData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package de.champonthis.abi.controller;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import de.champonthis.abi.buisinesslogic.ContactDataManager;
|
||||||
|
import de.champonthis.abi.buisinesslogic.ContactManager;
|
||||||
|
import de.champonthis.abi.controller.request.ContactDataRequest;
|
||||||
|
import de.champonthis.abi.entity.Contact;
|
||||||
|
import de.champonthis.abi.entity.ContactData;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/contact/data")
|
||||||
|
public class ContactDataController {
|
||||||
|
|
||||||
|
private final ContactManager contactManager;
|
||||||
|
private final ContactDataManager contactDataManager;
|
||||||
|
|
||||||
|
public ContactDataController(ContactManager contactManager, ContactDataManager contactDataManager) {
|
||||||
|
this.contactManager = contactManager;
|
||||||
|
this.contactDataManager = contactDataManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<ContactData> create(@Valid @RequestBody ContactDataRequest request) {
|
||||||
|
Contact contact = contactManager.findByName(request.getName());
|
||||||
|
Contact reportedBy = contactManager.findByName(request.getReportedBy());
|
||||||
|
|
||||||
|
if (contact == null || reportedBy == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactData contactData = contactDataManager.findByContactAndReportedBy(contact.getId(), reportedBy.getId());
|
||||||
|
|
||||||
|
HttpStatus status = HttpStatus.OK;
|
||||||
|
boolean sendToken = false;
|
||||||
|
|
||||||
|
if (contactData == null) {
|
||||||
|
contactData = new ContactData();
|
||||||
|
contactData.setContact(contact.getId());
|
||||||
|
contactData.setReportedBy(reportedBy.getId());
|
||||||
|
status = HttpStatus.CREATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contact.getId().equals(reportedBy.getId())
|
||||||
|
&& StringUtils.isNotEmpty(contact.getToken())
|
||||||
|
&& (status.isSameCodeAs(HttpStatus.CREATED) || !request.getEmail().equals(contactData.getEmail()))) {
|
||||||
|
sendToken = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
contactData.setEmail(request.getEmail());
|
||||||
|
contactData.setPhone(request.getPhone());
|
||||||
|
|
||||||
|
if (!contact.getId().equals(reportedBy.getId())
|
||||||
|
&& (contactData.getInviteToken() == null || StringUtils.isEmpty(contactData.getInviteToken()))) {
|
||||||
|
String inviteToken = ContactManager.generateToken();
|
||||||
|
while (contactDataManager.findByinviteToken(inviteToken) != null) {
|
||||||
|
inviteToken = ContactManager.generateToken();
|
||||||
|
}
|
||||||
|
contactData.setInviteToken(inviteToken);
|
||||||
|
contactDataManager.sendInviteMail(contact, contactData, reportedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
contactDataManager.save(contactData);
|
||||||
|
|
||||||
|
if (sendToken) {
|
||||||
|
contactManager.sendTokenMail(contact, contactData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.status(status.value()).body(contactData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package de.champonthis.abi.controller;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
import de.champonthis.abi.buisinesslogic.ContactDataManager;
|
||||||
|
import de.champonthis.abi.buisinesslogic.ContactManager;
|
||||||
|
import de.champonthis.abi.entity.Contact;
|
||||||
|
import de.champonthis.abi.entity.ContactData;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class FrontendController {
|
||||||
|
|
||||||
|
private final ContactManager contactManager;
|
||||||
|
private final ContactDataManager contactDataManager;
|
||||||
|
|
||||||
|
public FrontendController(ContactManager contactManager, ContactDataManager contactDataManager) {
|
||||||
|
this.contactManager = contactManager;
|
||||||
|
this.contactDataManager = contactDataManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index(
|
||||||
|
@RequestParam(required = false) String token,
|
||||||
|
@RequestParam(required = false) String invite,
|
||||||
|
Model model) {
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
Contact contact = contactManager.findByToken(token);
|
||||||
|
if (contact != null) {
|
||||||
|
model.addAttribute("token", token);
|
||||||
|
model.addAttribute("userName", contact.getName());
|
||||||
|
model.addAttribute("altName", contact.getUpdatedName());
|
||||||
|
model.addAttribute("altName", contact.getUpdatedName());
|
||||||
|
|
||||||
|
ContactData contactData = contactDataManager.findByContactAndReportedBy(contact.getId(),
|
||||||
|
contact.getId());
|
||||||
|
if (contactData != null) {
|
||||||
|
model.addAttribute("email", contactData.getEmail());
|
||||||
|
model.addAttribute("phone", contactData.getPhone());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (invite != null && !invite.isEmpty()) {
|
||||||
|
ContactData contactData = contactDataManager.findByinviteToken(invite);
|
||||||
|
|
||||||
|
if (contactData != null) {
|
||||||
|
Contact contact = contactManager.findById(contactData.getContact());
|
||||||
|
if (contact != null && StringUtils.isEmpty(contact.getToken())) {
|
||||||
|
model.addAttribute("userName", contact.getName());
|
||||||
|
model.addAttribute("altName", contact.getUpdatedName());
|
||||||
|
model.addAttribute("email", contactData.getEmail());
|
||||||
|
model.addAttribute("phone", contactData.getPhone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "index";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/imprint")
|
||||||
|
public String imprint() {
|
||||||
|
return "imprint";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package de.champonthis.abi.controller.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ContactDataRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "name must not be empty")
|
||||||
|
private String name;
|
||||||
|
@NotBlank(message = "email must not be empty")
|
||||||
|
@Email(message = "Invalid email format")
|
||||||
|
private String email;
|
||||||
|
// Accept empty or valid phone number
|
||||||
|
@Pattern(regexp = "^$|^(\\+49|0)[1-9][0-9]{7,14}$", message = "Invalid phone number")
|
||||||
|
private String phone;
|
||||||
|
@NotBlank(message = "reportedBy must not be empty")
|
||||||
|
private String reportedBy;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package de.champonthis.abi.controller.request;
|
||||||
|
|
||||||
|
import de.champonthis.abi.entity.Commitment;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ContactRequest {
|
||||||
|
|
||||||
|
@NotBlank(message = "name must not be empty")
|
||||||
|
private String name;
|
||||||
|
private String updatedName;
|
||||||
|
private Commitment committed;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package de.champonthis.abi.entity;
|
||||||
|
|
||||||
|
public enum Commitment {
|
||||||
|
UNKNOWN, YES, NO
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package de.champonthis.abi.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
public class Contact {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id", nullable = false)
|
||||||
|
@JsonIgnore
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private String updatedName;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private Commitment committed = Commitment.UNKNOWN;
|
||||||
|
@JsonIgnore
|
||||||
|
private String token;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package de.champonthis.abi.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
public class ContactData {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id", nullable = false)
|
||||||
|
private Long id;
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long contact;
|
||||||
|
private String email;
|
||||||
|
private String phone;
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long reportedBy;
|
||||||
|
@JsonIgnore
|
||||||
|
private String inviteToken;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.champonthis.abi.repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.champonthis.abi.entity.ContactData;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ContactDataRepository extends JpaRepository<ContactData, Long> {
|
||||||
|
|
||||||
|
Optional<ContactData> findByContactAndReportedBy(Long contact, Long reportedBy);
|
||||||
|
|
||||||
|
Optional<ContactData> findByinviteToken(String inviteToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.champonthis.abi.repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.champonthis.abi.entity.Contact;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ContactRepository extends JpaRepository<Contact, Long> {
|
||||||
|
|
||||||
|
Optional<Contact> findByName(String name);
|
||||||
|
|
||||||
|
Optional<Contact> findByToken(String token);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# project version
|
||||||
|
build.version=@project.version@
|
||||||
|
|
||||||
|
# === Thymeleaf ===
|
||||||
|
spring.thymeleaf.cache=false
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
:root {
|
||||||
|
--bs-primary: #093e9a;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #fffcf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override Bootstrap primary color classes */
|
||||||
|
.btn-primary,
|
||||||
|
.btn-primary:active,
|
||||||
|
.btn-primary:focus,
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #093e9a !important;
|
||||||
|
border-color: #093e9a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary {
|
||||||
|
background-color: #093e9a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-primary {
|
||||||
|
border-color: #093e9a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
color: #093e9a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 673px;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 16px auto;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 1.25rem;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-flex.gap-2>.btn.flex-fill,
|
||||||
|
.d-flex.gap-2>.btn {
|
||||||
|
flex: 1 1 0 !important;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
+3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Hallo <span th:text="${contact.updatedName != null ? contact.updatedName : contact.name}"></span>!</h1>
|
||||||
|
<div>
|
||||||
|
<p th:if="${contact.committed != null and contact.committed.name() == 'YES'}">
|
||||||
|
Super, dass du für unser 20-Jahres-Abitreffen am 13.11.2027 zugesagt hast - wir freuen uns sehr, dich dort wiederzusehen!
|
||||||
|
</p>
|
||||||
|
<p th:if="${contact.committed != null and contact.committed.name() == 'NO'}">
|
||||||
|
Schade, dass du im Moment nicht dabei bist. Du kannst deine Meinung aber jederzeit ändern.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Über diesen Link kannst du deine Kontaktdaten (und auch deine Zu-/Absage) jederzeit aktualisieren:
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p><a th:href="${url + '?token=' + token}" th:text="${url + '?token=' + token}"></a></p>
|
||||||
|
<p>Lukas, Michi & Max</p>
|
||||||
|
<p style="font-size:0.9em;color:#555;">
|
||||||
|
Falls die Mail unerwartet bei dir gelandet ist oder etwas nicht stimmt, antworte einfach kurz darauf.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Hallo <span th:text="${contact.updatedName != null ? contact.updatedName : contact.name}"></span>!</h1>
|
||||||
|
<p>
|
||||||
|
<span th:text="${reportedBy.updatedName != null ? reportedBy.updatedName : reportedBy.name}"></span> hat dich
|
||||||
|
eingeladen, dich zu unserem geplanten 20-Jahres-Abitreffen im Jahr 2027 zurückzumelden.
|
||||||
|
Damit wir besser planen können, trag bitte deine aktuellen Kontaktdaten ein und gib uns kurz Bescheid, ob du
|
||||||
|
voraussichtlich dabei sein möchtest oder eher nicht.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Über deinen persönlichen Einladungslink kannst du jetzt deine Angaben (Name, Kontaktdaten, Zu-/Absage) einmalig
|
||||||
|
eintragen.
|
||||||
|
Nachdem du deine Daten abgeschickt hast, erhältst du automatisch nochmal eine Bestätigungs-E-Mail.
|
||||||
|
</p>
|
||||||
|
<p><a th:href="${url + '?invite=' + inviteToken}" th:text="${url + '?invite=' + inviteToken}"></a></p>
|
||||||
|
<p>Lukas, Michi & Max</p>
|
||||||
|
<p style="font-size:0.85em;color:#555;">
|
||||||
|
Falls du schon deine Daten schon ausgefüllt hast und versuchst über den Link deine Daten zu aktualisieren, nutze bitte den neuen Link
|
||||||
|
aus deiner Bestätigungs-Mail.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>20 Jahre Abitur 2007</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="stylesheet" href="/lib/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2>Impressum</h2>
|
||||||
|
<p>
|
||||||
|
Lukas Haubaum<br>
|
||||||
|
Adresse aus Datenschutzgründen nur auf Nachfrage.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Kontakt:</strong><br>
|
||||||
|
E-Mail: abi2007@8lh.de
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Haftung für Inhalte</h4>
|
||||||
|
<p>
|
||||||
|
Ich habe die Inhalte dieser Seite mit größter Sorgfalt erstellt. Für die Richtigkeit,
|
||||||
|
Vollständigkeit und
|
||||||
|
Aktualität kann ich aber keine Gewähr übernehmen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Haftung für Links</h4>
|
||||||
|
<p>
|
||||||
|
Auf dieser Seite findest Du Links zu externen Webseiten Dritter, auf deren Inhalte ich keinen
|
||||||
|
Einfluss habe.
|
||||||
|
Deshalb kann ich für diese fremden Inhalte auch keine Gewähr übernehmen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Urheberrecht</h4>
|
||||||
|
<p>
|
||||||
|
Die von mir erstellten Inhalte und Werke auf dieser Seite unterliegen dem deutschen Urheberrecht.
|
||||||
|
Beiträge
|
||||||
|
von anderen sind als solche gekennzeichnet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Datenschutzerklärung</h2>
|
||||||
|
<p>
|
||||||
|
Der Schutz Deiner persönlichen Daten ist mir ein besonderes Anliegen. Ich verarbeite Deine Daten
|
||||||
|
daher
|
||||||
|
ausschließlich auf Grundlage der gesetzlichen Bestimmungen (DSGVO, TMG).
|
||||||
|
</p>
|
||||||
|
<h4>Erhebung und Speicherung personenbezogener Daten</h4>
|
||||||
|
<p>
|
||||||
|
Auf diesem Server werden keinerlei personenbezogene Daten gespeichert, außer den Kontaktdaten, die
|
||||||
|
Du
|
||||||
|
freiwillig angibst (z.B. Name, E-Mail-Adresse).
|
||||||
|
</p>
|
||||||
|
<h4>Verwendung der Kontaktdaten</h4>
|
||||||
|
<p>
|
||||||
|
Deine angegebenen Kontaktdaten werden ausschließlich genutzt, um Dich im Rahmen des Abiturjahrgangs
|
||||||
|
2007 zu
|
||||||
|
kontaktieren und werden nicht an Dritte weitergegeben.
|
||||||
|
</p>
|
||||||
|
<h4>E-Mail-Bestätigungen und Einladungen</h4>
|
||||||
|
<p>
|
||||||
|
Wenn du deine E-Mail-Adresse angibst, bekommst du von mir Bestätigungen zu deiner Anmeldung und Einladungen oder Infos rund ums Abi-Treffen per E-Mail. Deine Adresse wird nicht für Werbung genutzt und nicht weitergegeben.
|
||||||
|
</p>
|
||||||
|
<h4>Serverdaten</h4>
|
||||||
|
<p>Aus technischen Gründen werden bei deinem Besuch auf dieser Seite normalerweise Daten wie Browsertyp, Betriebssystem, die Seite, von der du kommst (Referrer URL), besuchte Seiten, Datum und Uhrzeit sowie deine IP-Adresse an den Server übermittelt. Ich erhebe diese Daten aber grundsätzlich nicht(!), weil mir dein Datenschutz wichtig ist. Nur falls es technisch nötig ist, kann es sein, dass solche Daten kurzfristig gespeichert werden, zum Beispiel zur Verbesserung, Stabilität, Funktionalität oder Sicherheit der Seite. Rechtsgrundlage dafür ist Art. 6 Abs. 1 lit. f) DSGVO.</p>
|
||||||
|
<p>Falls solche Daten doch mal in sogenannten Server-Logfiles landen, werden sie nicht mit anderen Daten von dir zusammengeführt und spätestens nach 14 Tagen gelöscht.</p>
|
||||||
|
|
||||||
|
<h4>Kontaktanfragen / Kontaktmöglichkeit</h4>
|
||||||
|
<p>Wenn du mir per Kontaktformular oder E-Mail schreibst, nutze ich die von dir angegebenen Daten nur, um deine Anfrage zu bearbeiten. Ohne diese Angaben kann ich dir leider nicht oder nur eingeschränkt antworten.</p>
|
||||||
|
<p>Rechtsgrundlage dafür ist Art. 6 Abs. 1 lit. b) DSGVO.</p>
|
||||||
|
|
||||||
|
<h4>Deine Rechte</h4>
|
||||||
|
<p>
|
||||||
|
Du hast grundsätzlich das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung,
|
||||||
|
Datenübertragbarkeit,
|
||||||
|
Widerruf und Widerspruch. Wenn Du glaubst, dass die Verarbeitung Deiner Daten gegen das
|
||||||
|
Datenschutzrecht
|
||||||
|
verstößt, kannst Du Dich bei der Aufsichtsbehörde beschweren.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/" type="button" class="btn btn-outline-secondary">
|
||||||
|
Zurück
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,464 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>20 Jahre Abitur 2007</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<script src="/lib/axios.min.js"></script>
|
||||||
|
<script defer src="/lib/alpinejs.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/lib/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="container pt-5" x-data="app()">
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="text-center p-4" style>
|
||||||
|
<div class="d-flex justify-content-center align-items-center mb-2 flex-wrap">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
aria-hidden="true" class="text-primary me-2">
|
||||||
|
<path
|
||||||
|
d="M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z">
|
||||||
|
</path>
|
||||||
|
<path d="M22 10v6"></path>
|
||||||
|
<path d="M6 12.5V16a6 3 0 0 0 12 0v-3.5"></path>
|
||||||
|
</svg>
|
||||||
|
<h1 class="h3 mb-0 fw-bold text-primary" style="vertical-align:middle; display:inline-block;">
|
||||||
|
20 Jahre Abitur 2007
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<p class="text-secondary mb-0">
|
||||||
|
Wir wollen 2027 ein Abi-Treffen organisieren und brauchen dafür deine Kontaktdaten – und sag uns
|
||||||
|
bitte kurz, ob du dabei bist oder nicht.<br><br>Lukas, Michi & Max
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Stepper -->
|
||||||
|
<div class="d-flex justify-content-center align-items-center mb-4 flex-wrap">
|
||||||
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
|
<div class="rounded-circle border border-2 d-flex justify-content-center align-items-center"
|
||||||
|
:class="step === 1 ? 'bg-primary text-white border-primary' : 'bg-light text-secondary border-secondary'"
|
||||||
|
style="width:32px;height:32px;font-weight:600;">1</div>
|
||||||
|
<div class="mx-1">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m9 18 6-6-6-6"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-circle border border-2 d-flex justify-content-center align-items-center"
|
||||||
|
:class="step === 2 ? 'bg-primary text-white border-primary' : 'bg-light text-secondary border-secondary'"
|
||||||
|
style="width:32px;height:32px;font-weight:600;">2</div>
|
||||||
|
<div class="mx-1">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m9 18 6-6-6-6"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-circle border border-2 d-flex justify-content-center align-items-center"
|
||||||
|
:class="step === 3 ? 'bg-primary text-white border-primary' : 'bg-light text-secondary border-secondary'"
|
||||||
|
style="width:32px;height:32px;font-weight:600;">3</div>
|
||||||
|
<div class="mx-1">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="m9 18 6-6-6-6"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-circle border border-2 d-flex justify-content-center align-items-center"
|
||||||
|
:class="step === 4 ? 'bg-primary text-white border-primary' : 'bg-light text-secondary border-secondary'"
|
||||||
|
style="width:32px;height:32px;font-weight:600;">4</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Step 1 -->
|
||||||
|
<div id="step1" x-show="step === 1" x-transition>
|
||||||
|
<h2 class="h5 mb-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" aria-hidden="true" class="text-primary me-2">
|
||||||
|
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
|
||||||
|
<path d="M16 3.128a4 4 0 0 1 0 7.744"></path>
|
||||||
|
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
|
||||||
|
<circle cx="9" cy="7" r="4"></circle>
|
||||||
|
</svg> Schritt 1: Dein Name
|
||||||
|
</h2>
|
||||||
|
<div x-show="!userName">
|
||||||
|
<form @submit.prevent="findSelf" class="d-flex flex-column gap-3 mb-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">
|
||||||
|
Dein Name (wie auf dem Abi-T-Shirt)
|
||||||
|
</label>
|
||||||
|
<input type="text" x-model="yourName" required autocomplete="off"
|
||||||
|
class="form-control" autofocus />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary flex-fill">Suchen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="step1Msg" class="alert py-2" :class="'alert-' + step1MsgType" x-text="step1Msg"
|
||||||
|
x-show="step1Msg"></div>
|
||||||
|
<div x-show="userName">
|
||||||
|
<form @submit.prevent="continueStep1">
|
||||||
|
<p>
|
||||||
|
Hallo <span x-text="altName || userName"></span>! Falls sich dein Name geändert hat,
|
||||||
|
kannst du ihn hier anpassen – oder einfach direkt weitermachen.
|
||||||
|
</p>
|
||||||
|
<div id="altNameCheckboxDiv" class="form-check mt-3" x-show="userName" x-transition>
|
||||||
|
<input type="checkbox" x-model="altNameChecked" class="form-check-input"
|
||||||
|
id="altNameCheckbox" style="width:18px;height:18px;"
|
||||||
|
@change="$nextTick(() => { if(altNameChecked) $refs.altNameInput && $refs.altNameInput.focus(); else $refs.continueBtn && $refs.continueBtn.focus(); })" />
|
||||||
|
<label class="form-check-label" for="altNameCheckbox" style="font-size:1rem;">
|
||||||
|
Ich möchte meinen Namen aktualisieren.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="altNameDiv" class="mt-2" x-show="altNameChecked && userName" x-transition>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Mein aktueller Name:</label>
|
||||||
|
<input type="text" x-model="altName" autocomplete="off" class="form-control"
|
||||||
|
x-ref="altNameInput" @keydown.enter.prevent="$refs.continueBtn.click()" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="step1ContinueDiv" class="d-flex gap-2 mt-3 justify-content-center"
|
||||||
|
:class="userName ? '' : 'd-none'" x-transition>
|
||||||
|
<button id="step1ContinueBtn" type="submit" class="btn btn-primary flex-fill"
|
||||||
|
x-ref="continueBtn">
|
||||||
|
Weiter <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||||||
|
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="m9 18 6-6-6-6"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Step 2 -->
|
||||||
|
<div id="step2" x-show="step === 2" x-transition>
|
||||||
|
<h2 class="h5 mb-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" aria-hidden="true" class="text-primary me-2">
|
||||||
|
<path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"></path>
|
||||||
|
<rect x="2" y="4" width="20" height="16" rx="2"></rect>
|
||||||
|
</svg> Schritt 2: Deine Kontaktdaten
|
||||||
|
</h2>
|
||||||
|
<div id="step2Msg" class="alert py-2" :class="'alert-' + step2MsgType" x-text="step2Msg"
|
||||||
|
x-show="step2Msg"></div>
|
||||||
|
<form @submit.prevent="submitContactData" class="mb-3" autocomplete="off">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">E-Mail:</label>
|
||||||
|
<input type="email" x-model="email" required autocomplete="off" class="form-control"
|
||||||
|
autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Handynummer (optional):</label>
|
||||||
|
<input type="text" x-model="phone" autocomplete="off" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="button" class="btn btn-outline-secondary flex-fill" @click="step = 1">
|
||||||
|
Zurück
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary flex-fill" autofocus>Weiter <svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="m9 18 6-6-6-6"></path>
|
||||||
|
</svg></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- Step 3 -->
|
||||||
|
<div id="step3" x-show="step === 3" x-transition>
|
||||||
|
<h2 class="h5 mb-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" aria-hidden="true" class="text-primary me-2">
|
||||||
|
<path
|
||||||
|
d="M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384">
|
||||||
|
</path>
|
||||||
|
</svg> Schritt 3: Kennst du noch jemanden?
|
||||||
|
</h2>
|
||||||
|
<form @submit.prevent="findKnownContact" class="mb-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Name auf dem Abi-T-Shirt:</label>
|
||||||
|
<input type="text" x-model="knownContactName" required autocomplete="off"
|
||||||
|
class="form-control" autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-outline-dark flex-fill" autofocus>Suchen</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="step3Msg" class="alert py-2" :class="'alert-' + step3MsgType" x-text="step3Msg"
|
||||||
|
x-show="step3Msg"></div>
|
||||||
|
<div id="knownContactDataDiv" x-show="showKnownContactData" x-transition>
|
||||||
|
<form @submit.prevent="submitKnownContactData" class="mb-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">E-Mail:</label>
|
||||||
|
<input type="email" x-model="knownContactEmail" required autocomplete="off"
|
||||||
|
class="form-control" autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Handynummer (optional):</label>
|
||||||
|
<input type="text" x-model="knownContactPhone" autocomplete="off"
|
||||||
|
class="form-control" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-outline-success flex-fill"
|
||||||
|
autofocus>Speichern</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group mb-3" id="enteredContacts">
|
||||||
|
<template x-for="c in enteredContacts" :key="c.name">
|
||||||
|
<li class="list-group-item"
|
||||||
|
x-text="`${c.name} (${c.email}${c.phone ? ', ' + c.phone : ''})`"></li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
<div class="d-flex gap-2 mt-3 justify-content-center">
|
||||||
|
<button type="button" class="btn btn-outline-secondary flex-fill" @click="step = 2">
|
||||||
|
Zurück
|
||||||
|
</button>
|
||||||
|
<button id="step3NextBtn" class="btn btn-primary flex-fill" @click="step = 4">Weiter <svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="m9 18 6-6-6-6"></path>
|
||||||
|
</svg></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Step 4 -->
|
||||||
|
<div id="step4" x-show="step === 4" x-transition>
|
||||||
|
<h2 class="h5 mb-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" aria-hidden="true" class="text-primary me-2">
|
||||||
|
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
||||||
|
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||||||
|
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||||||
|
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||||||
|
</svg> Schritt 4: Bist du beim 20-Jahrestreffen am 13.11.2027 dabei?
|
||||||
|
</h2>
|
||||||
|
<div id="step4Msg" class="alert py-2" :class="'alert-' + step4MsgType" x-text="step4Msg"
|
||||||
|
x-show="step4Msg"></div>
|
||||||
|
<p>
|
||||||
|
Hast du Lust, am Samstag, den 13.11.2027, beim Nachtreffen dabei zu sein? Mehr Infos gibt's
|
||||||
|
dann hier und per Mail.
|
||||||
|
</p>
|
||||||
|
<div class="d-flex gap-2 mb-3">
|
||||||
|
<button id="commitYes" class="btn btn-success flex-fill" @click="submitCommit('YES')"
|
||||||
|
autofocus>Ja, ich bin dabei!</button>
|
||||||
|
<button id="commitNo" class="btn btn-danger flex-fill" @click="submitCommit('NO')">Nein, ich
|
||||||
|
kann leider nicht</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex mt-2">
|
||||||
|
<button type="button" class="btn btn-outline-secondary flex-fill" @click="step = 3">
|
||||||
|
Zurück
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="text-center py-4 bg-light border-top">
|
||||||
|
<a href="/imprint" class="text-secondary" style="text-decoration: none;">Impressum & Datenschutz</a>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
function app() {
|
||||||
|
return {
|
||||||
|
// Step state
|
||||||
|
step: 1,
|
||||||
|
// Step 1
|
||||||
|
yourName: '',
|
||||||
|
step1Msg: '',
|
||||||
|
step1MsgType: '',
|
||||||
|
altNameChecked: false,
|
||||||
|
altName: '[[${altName}]]',
|
||||||
|
showStep1Continue: false,
|
||||||
|
userName: '[[${userName}]]',
|
||||||
|
userToken: '[[${token}]]',
|
||||||
|
pendingUpdatedName: null,
|
||||||
|
// Step 2
|
||||||
|
email: '[[${email}]]',
|
||||||
|
phone: '[[${phone}]]',
|
||||||
|
step2Msg: '',
|
||||||
|
step2MsgType: '',
|
||||||
|
// Step 3
|
||||||
|
knownContactName: '',
|
||||||
|
knownContactEmail: '',
|
||||||
|
knownContactPhone: '',
|
||||||
|
showKnownContactData: false,
|
||||||
|
enteredContacts: [],
|
||||||
|
step3Msg: '',
|
||||||
|
step3MsgType: '',
|
||||||
|
foundKnownContactName: null, // <-- add this line
|
||||||
|
// Step 4
|
||||||
|
step4Msg: '',
|
||||||
|
step4MsgType: '',
|
||||||
|
// Methods
|
||||||
|
async findSelf() {
|
||||||
|
this.step1Msg = '';
|
||||||
|
this.step1MsgType = '';
|
||||||
|
this.altNameChecked = false;
|
||||||
|
this.altName = '';
|
||||||
|
this.pendingUpdatedName = null;
|
||||||
|
try {
|
||||||
|
const resp = await axios.get(`/api/v1/contact`, { params: { name: this.yourName.trim() }, validateStatus: () => true });
|
||||||
|
if (resp.status === 200) {
|
||||||
|
this.userName = resp.data.name;
|
||||||
|
this.userToken = resp.data.token || null;
|
||||||
|
} else if (resp.status === 404) {
|
||||||
|
this.step1Msg = 'Sorry, wir konnten dich nicht finden. Schreib deinen Namen bitte genau so, wie er auf dem Abi-T-Shirt stand!';
|
||||||
|
this.step1MsgType = 'warning';
|
||||||
|
} else if (resp.status === 208) {
|
||||||
|
this.step1Msg = 'Du hast deine Daten schon eingetragen. Danke dir!';
|
||||||
|
this.step1MsgType = 'info';
|
||||||
|
// Prevent showing greeting/alt name UI
|
||||||
|
this.userName = null;
|
||||||
|
} else {
|
||||||
|
this.step1Msg = 'Uups, da ist was schiefgelaufen. Versuch es gleich nochmal!';
|
||||||
|
this.step1MsgType = 'danger';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.step1Msg = 'Uups, da ist was schiefgelaufen. Versuch es gleich nochmal!';
|
||||||
|
this.step1MsgType = 'danger';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async continueStep1() {
|
||||||
|
this.pendingUpdatedName = this.altNameChecked ? this.altName.trim() : null;
|
||||||
|
if (this.pendingUpdatedName && !this.userToken) {
|
||||||
|
await axios.post('/api/v1/contact', {
|
||||||
|
name: this.userName,
|
||||||
|
updatedName: this.pendingUpdatedName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.step = 2;
|
||||||
|
},
|
||||||
|
async submitContactData() {
|
||||||
|
this.step2Msg = '';
|
||||||
|
this.step2MsgType = '';
|
||||||
|
try {
|
||||||
|
const resp = await axios.post('/api/v1/contact/data', {
|
||||||
|
name: this.userName,
|
||||||
|
reportedBy: this.userName,
|
||||||
|
email: this.email.trim(),
|
||||||
|
phone: this.phone.trim()
|
||||||
|
}, { validateStatus: () => true });
|
||||||
|
if (resp.status === 200 || resp.status === 201) {
|
||||||
|
this.step = 3;
|
||||||
|
} else if (resp.status === 400) {
|
||||||
|
this.step2Msg = 'Bitte schau nochmal über deine Angaben – da stimmt was nicht.';
|
||||||
|
this.step2MsgType = 'warning';
|
||||||
|
} else {
|
||||||
|
this.step2Msg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||||||
|
this.step2MsgType = 'danger';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.step2Msg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||||||
|
this.step2MsgType = 'danger';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async findKnownContact() {
|
||||||
|
this.step3Msg = '';
|
||||||
|
this.step3MsgType = '';
|
||||||
|
this.showKnownContactData = false;
|
||||||
|
this.knownContactEmail = '';
|
||||||
|
this.knownContactPhone = '';
|
||||||
|
this.foundKnownContactName = null; // reset
|
||||||
|
try {
|
||||||
|
const resp = await axios.get('/api/v1/contact', {
|
||||||
|
params: { name: this.knownContactName.trim() },
|
||||||
|
validateStatus: () => true
|
||||||
|
});
|
||||||
|
if (resp.status === 200) {
|
||||||
|
this.foundKnownContactName = resp.data.name.trim();
|
||||||
|
if (this.foundKnownContactName == this.yourName) {
|
||||||
|
this.step3Msg = 'Das bist du selbst – deine Daten hast du ja schon eingetragen!';
|
||||||
|
this.step3MsgType = 'warning';
|
||||||
|
this.showKnownContactData = false;
|
||||||
|
this.knownContactName = ''
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.knownContactName = this.foundKnownContactName;
|
||||||
|
this.step3Msg = 'Super, trag bitte die Kontaktdaten ein!';
|
||||||
|
this.step3MsgType = 'success';
|
||||||
|
this.showKnownContactData = true;
|
||||||
|
} else if (resp.status === 404) {
|
||||||
|
this.step3Msg = 'Diesen Namen gibt es leider nicht in unserer Liste.';
|
||||||
|
this.step3MsgType = 'warning';
|
||||||
|
} else if (resp.status === 208) {
|
||||||
|
this.step3Msg = 'Für diese Person haben wir schon Kontaktdaten. Danke trotzdem fürs Mitmachen!';
|
||||||
|
this.step3MsgType = 'info';
|
||||||
|
this.showKnownContactData = false;
|
||||||
|
} else {
|
||||||
|
this.step3Msg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||||||
|
this.step3MsgType = 'danger';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.step3Msg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||||||
|
this.step3MsgType = 'danger';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submitKnownContactData() {
|
||||||
|
this.step3Msg = '';
|
||||||
|
this.step3MsgType = '';
|
||||||
|
try {
|
||||||
|
const resp = await axios.post('/api/v1/contact/data', {
|
||||||
|
name: this.foundKnownContactName,
|
||||||
|
reportedBy: this.userName,
|
||||||
|
email: this.knownContactEmail.trim(),
|
||||||
|
phone: this.knownContactPhone.trim()
|
||||||
|
}, { validateStatus: () => true });
|
||||||
|
if (resp.status === 200 || resp.status === 201) {
|
||||||
|
this.enteredContacts = this.enteredContacts.filter((c) => c.name !== this.foundKnownContactName);
|
||||||
|
this.enteredContacts.push({
|
||||||
|
name: this.foundKnownContactName,
|
||||||
|
email: this.knownContactEmail.trim(),
|
||||||
|
phone: this.knownContactPhone.trim()
|
||||||
|
});
|
||||||
|
this.step3Msg = 'Danke, die Kontaktdaten sind gespeichert! Du kannst noch mehr eintragen oder einfach weitermachen.';
|
||||||
|
this.step3MsgType = 'success';
|
||||||
|
this.showKnownContactData = false;
|
||||||
|
this.knownContactName = '';
|
||||||
|
this.foundKnownContactName = null;
|
||||||
|
} else if (resp.status === 400) {
|
||||||
|
this.step3Msg = 'Bitte prüfe die Angaben nochmal – da stimmt was nicht.';
|
||||||
|
this.step3MsgType = 'warning';
|
||||||
|
} else {
|
||||||
|
this.step3Msg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||||||
|
this.step3MsgType = 'danger';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.step3Msg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||||||
|
this.step3MsgType = 'danger';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submitCommit(committed) {
|
||||||
|
try {
|
||||||
|
await axios.post('/api/v1/contact', {
|
||||||
|
name: this.userName,
|
||||||
|
updatedName: this.pendingUpdatedName,
|
||||||
|
token: this.userToken,
|
||||||
|
committed: committed
|
||||||
|
}, {
|
||||||
|
headers: this.userToken ? { 'Authorization': this.userToken } : {}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (committed == 'YES') {
|
||||||
|
this.step4Msg = 'Danke für deine Rückmeldung! Schön, dass du dabei bist! Wir melden uns rechtzeitig mit allen weiteren Infos.' + (this.userToken ? '' : ' Du solltest schon automatisch eine E-Mail mit einem Link bekommen haben, über den du deine Daten jederzeit anpassen kannst.');
|
||||||
|
this.step4MsgType = 'success';
|
||||||
|
} else {
|
||||||
|
this.step4Msg = 'Danke für deine Rückmeldung! Schade, dass du nicht dabei bist.' + (this.userToken ? '' : ' Du solltest trotzdem automatisch eine E-Mail mit einem Link bekommen haben, über den du deine Daten jederzeit anpassen kannst und auch noch zusagen kannst, solltest du doch noch kommen wollen.');
|
||||||
|
this.step4MsgType = 'info';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.step4Msg = 'Da ist was schiefgelaufen. Versuch es bitte nochmal!';
|
||||||
|
this.step4MsgType = 'danger';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user