Erweiterungen

This commit is contained in:
2025-08-12 12:25:10 +02:00
parent 30f2b793c2
commit ae2743d8e8
25 changed files with 1363 additions and 169 deletions

28
pom.xml
View File

@@ -12,6 +12,9 @@
<properties> <properties>
<java.version>21</java.version> <java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<vaadin.version>24.7.0</vaadin.version> <vaadin.version>24.7.0</vaadin.version>
<archunit.version>1.3.0</archunit.version> <archunit.version>1.3.0</archunit.version>
</properties> </properties>
@@ -67,10 +70,16 @@
<artifactId>spring-boot-starter-data-mongodb</artifactId> <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency> </dependency>
<!-- Security Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>1.18.30</version> <version>1.18.38</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -82,6 +91,23 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
<release>21</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin> <plugin>
<groupId>com.diffplug.spotless</groupId> <groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId> <artifactId>spotless-maven-plugin</artifactId>

View File

@@ -0,0 +1,88 @@
package de.assecutor.votianlt.config;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Set;
@Component
public class DataInitializer implements CommandLineRunner {
private static final Logger log = LoggerFactory.getLogger(DataInitializer.class);
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
public void run(String... args) throws Exception {
initializeTestUsers();
}
private void initializeTestUsers() {
log.info("Initializing test users...");
// Admin User
if (!userRepository.existsByEmail("admin@votianlt.de")) {
User adminUser = new User();
adminUser.setEmail("admin@votianlt.de");
adminUser.setPassword(passwordEncoder.encode("admin123"));
adminUser.setName("Administrator");
adminUser.setFirstname("Admin");
adminUser.setIsActivated((byte) 1);
adminUser.setIsEmailConfirmed((byte) 1);
adminUser.setCreatedAt(LocalDateTime.now());
adminUser.setUpdatedAt(LocalDateTime.now());
adminUser.setRoles(Set.of("USER", "ADMIN"));
userRepository.save(adminUser);
log.info("Created admin user: admin@votianlt.de / admin123");
}
// Test User
if (!userRepository.existsByEmail("test@votianlt.de")) {
User testUser = new User();
testUser.setEmail("test@votianlt.de");
testUser.setPassword(passwordEncoder.encode("test123"));
testUser.setName("Testmann");
testUser.setFirstname("Test");
testUser.setIsActivated((byte) 1);
testUser.setIsEmailConfirmed((byte) 1);
testUser.setCreatedAt(LocalDateTime.now());
testUser.setUpdatedAt(LocalDateTime.now());
testUser.setRoles(Set.of("USER"));
userRepository.save(testUser);
log.info("Created test user: test@votianlt.de / test123");
}
// Demo User
if (!userRepository.existsByEmail("demo@votianlt.de")) {
User demoUser = new User();
demoUser.setEmail("demo@votianlt.de");
demoUser.setPassword(passwordEncoder.encode("demo123"));
demoUser.setName("Demouser");
demoUser.setFirstname("Demo");
demoUser.setIsActivated((byte) 1);
demoUser.setIsEmailConfirmed((byte) 1);
demoUser.setCreatedAt(LocalDateTime.now());
demoUser.setUpdatedAt(LocalDateTime.now());
demoUser.setRoles(Set.of("USER"));
userRepository.save(demoUser);
log.info("Created demo user: demo@votianlt.de / demo123");
}
log.info("Test users initialization completed.");
}
}

View File

@@ -6,12 +6,12 @@ import org.bson.types.ObjectId;
@Data @Data
public class Company public class Company
{ {
public ObjectId id; private ObjectId id;
public String name; private String name;
public String street; private String street;
public String houseNumber; private String houseNumber;
public String addressAddition; private String addressAddition;
public String zip; private String zip;
public String city; private String city;
} }

View File

@@ -7,17 +7,16 @@ import org.bson.types.ObjectId;
public class Customer public class Customer
{ {
private ObjectId id; private ObjectId id;
private String title;
public String title; private String companyName;
public String companyName; private String firstname;
public String firstname; private String lastName;
public String lastName; private String telephone;
public String telephone; private String fax;
public String fax; private String mail;
public String mail; private String street;
public String street; private String houseNumber;
public String houseNumber; private String addressAddition;
public String addressAddition; private String zip;
public String zip; private String city;
public String city;
} }

View File

@@ -0,0 +1,42 @@
package de.assecutor.votianlt.model;
import lombok.Data;
import org.bson.types.ObjectId;
@Data
public class Job {
private ObjectId id;
// Auftraggeber/Rechnungsempfänger
private String customerSelection; // Kunde01 | KOTVor K01Nach
// Abholadresse
private String pickupCompany;
private String pickupSalutation;
private String pickupFirstName;
private String pickupLastName;
private String pickupPhone;
private String pickupStreet;
private String pickupHouseNumber;
private String pickupAddressAddition;
private String pickupZip;
private String pickupCity;
private boolean savePickupAddress;
// Lieferadresse
private String deliveryCompany;
private String deliverySalutation;
private String deliveryFirstName;
private String deliveryLastName;
private String deliveryPhone;
private String deliveryStreet;
private String deliveryHouseNumber;
private String deliveryAddressAddition;
private String deliveryZip;
private String deliveryCity;
private boolean saveDeliveryAddress;
// Digitale Abwicklung per App
private boolean digitalProcessing;
private String appUser;
}

View File

@@ -1,28 +1,45 @@
package de.assecutor.votianlt.model; package de.assecutor.votianlt.model;
import lombok.Data; import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.index.Indexed;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Set;
@Data @Data
public class User @Document(collection = "users")
{ public class User {
public int usrId;
public int hqId; @Id
public short type; private String id;
public String title;
public String name; private int usrId;
public String firstname; private int hqId;
public java.sql.Date birthdate; private short type;
public String email; private String title;
public String invitationEmail; private String name;
public String phone; private String firstname;
public String phone2; private LocalDate birthdate;
public String fax;
public String password; @Indexed(unique = true)
public byte isActivated; private String email;
public String activationCode;
public byte isEmailConfirmed; private String invitationEmail;
public byte isPasswordLost; private String phone;
public String passwordCode; private String phone2;
public int passwordTimestamp; private String fax;
public long activationDate; private String password;
private byte isActivated;
private String activationCode;
private byte isEmailConfirmed;
private byte isPasswordLost;
private String passwordCode;
private LocalDateTime passwordTimestamp;
private LocalDateTime activationDate;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private Set<String> roles;
} }

View File

@@ -16,11 +16,13 @@ import de.assecutor.votianlt.model.Company;
import de.assecutor.votianlt.pages.add_company.service.AddCompanyService; import de.assecutor.votianlt.pages.add_company.service.AddCompanyService;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import jakarta.annotation.security.RolesAllowed;
import java.time.Clock; import java.time.Clock;
@Route("add_company") @Route(value = "add_company", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@PageTitle("Neuen Firma anlegen") @PageTitle("Neuen Firma anlegen")
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neue Firma anlegen") @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neue Firma anlegen")
@RolesAllowed("USER")
public class AddCompanyView extends Main { public class AddCompanyView extends Main {
private final AddCompanyService addCompanyService; private final AddCompanyService addCompanyService;
@@ -82,7 +84,7 @@ public class AddCompanyView extends Main {
private void submit() { private void submit() {
Company company = new Company(); Company company = new Company();
company.name = companyName.getValue(); company.setName(companyName.getValue());
try { try {
binder.writeBean(company); binder.writeBean(company);

View File

@@ -16,11 +16,13 @@ import de.assecutor.votianlt.model.Customer;
import de.assecutor.votianlt.pages.add_customer.service.AddCustomerService; import de.assecutor.votianlt.pages.add_customer.service.AddCustomerService;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import jakarta.annotation.security.RolesAllowed;
import java.time.Clock; import java.time.Clock;
@Route("add_customer") @Route(value = "add_customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@PageTitle("Neuen Kunden anlegen") @PageTitle("Neuen Kunden anlegen")
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neuen Kunden anlegen") @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neuen Kunden anlegen")
@RolesAllowed("USER")
public class AddCustomerView extends Main { public class AddCustomerView extends Main {
private final AddCustomerService addCustomerService; private final AddCustomerService addCustomerService;
@@ -82,7 +84,7 @@ public class AddCustomerView extends Main {
private void submit() { private void submit() {
Customer customer = new Customer(); Customer customer = new Customer();
customer.companyName = companyName.getValue(); customer.setCompanyName(companyName.getValue());
try { try {
binder.writeBean(customer); binder.writeBean(customer);

View File

@@ -0,0 +1,13 @@
package de.assecutor.votianlt.pages.add_job.service;
import de.assecutor.votianlt.model.Job;
import org.springframework.stereotype.Service;
@Service
public class AddJobService {
public void addJob(Job job) {
// TODO: Implement job persistence logic
System.out.println("Job would be saved: " + job.toString());
}
}

View File

@@ -0,0 +1,353 @@
package de.assecutor.votianlt.pages.add_job.ui.view;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility;
import de.assecutor.votianlt.model.Job;
import de.assecutor.votianlt.pages.add_job.service.AddJobService;
import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar;
import jakarta.annotation.security.RolesAllowed;
@Route(value = "add_job", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@PageTitle("Neuen Auftrag anlegen")
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neuen Auftrag anlegen")
@RolesAllowed("USER")
public class AddJobView extends Main {
private final AddJobService addJobService;
// Customer selection
private ComboBox<String> customerSelection;
private Button preloadAddressButton;
// Required fields notice
private Span requiredFieldsNotice;
// Pickup address fields
private TextField pickupCompany;
private ComboBox<String> pickupSalutation;
private TextField pickupFirstName;
private TextField pickupLastName;
private TextField pickupPhone;
private TextField pickupStreet;
private TextField pickupHouseNumber;
private TextField pickupAddressAddition;
private TextField pickupZip;
private TextField pickupCity;
private Checkbox savePickupAddress;
// Delivery address fields
private TextField deliveryCompany;
private ComboBox<String> deliverySalutation;
private TextField deliveryFirstName;
private TextField deliveryLastName;
private TextField deliveryPhone;
private TextField deliveryStreet;
private TextField deliveryHouseNumber;
private TextField deliveryAddressAddition;
private TextField deliveryZip;
private TextField deliveryCity;
private Checkbox saveDeliveryAddress;
// Digital processing
private Checkbox digitalProcessing;
private ComboBox<String> appUser;
// Submit button
private Button submitButton;
private final Binder<Job> binder = new Binder<>(Job.class);
public AddJobView(AddJobService addJobService) {
this.addJobService = addJobService;
initializeComponents();
setupLayout();
setupValidation();
}
private void initializeComponents() {
// Customer selection
customerSelection = new ComboBox<>("Auftraggeber/Rechnungsempfänger");
customerSelection.setItems("Kunde01 | KOTVor K01Nach");
customerSelection.setValue("Kunde01 | KOTVor K01Nach");
customerSelection.setWidthFull();
preloadAddressButton = new Button("Vorbelegte Adressfelder leeren");
preloadAddressButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
preloadAddressButton.setIcon(new Icon(VaadinIcon.QUESTION_CIRCLE));
// Required fields notice
requiredFieldsNotice = new Span("Die mit (*) gekennzeichneten Felder sind Pflichtfelder.");
requiredFieldsNotice.getStyle().set("color", "red");
requiredFieldsNotice.getStyle().set("font-size", "14px");
// Pickup address
pickupCompany = new TextField("Firma");
pickupCompany.setValue("Kunde01");
pickupSalutation = new ComboBox<>("Anrede");
pickupSalutation.setItems("Herr", "Frau", "Divers");
pickupFirstName = new TextField("Vorname*");
pickupFirstName.setValue("K01Vor");
pickupFirstName.setRequiredIndicatorVisible(true);
pickupLastName = new TextField("Nachname*");
pickupLastName.setValue("K01Nach");
pickupLastName.setRequiredIndicatorVisible(true);
pickupPhone = new TextField("Telefonnummer");
pickupPhone.setValue("01");
pickupStreet = new TextField("Straße*");
pickupStreet.setValue("Ottensener Str.");
pickupStreet.setRequiredIndicatorVisible(true);
pickupHouseNumber = new TextField("Hausnr*");
pickupHouseNumber.setValue("8");
pickupHouseNumber.setRequiredIndicatorVisible(true);
pickupAddressAddition = new TextField("Adresszusatz");
pickupZip = new TextField("Postleitzahl*");
pickupZip.setValue("22525");
pickupZip.setRequiredIndicatorVisible(true);
pickupCity = new TextField("Ort*");
pickupCity.setValue("Hamburg");
pickupCity.setRequiredIndicatorVisible(true);
savePickupAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern.");
// Delivery address
deliveryCompany = new TextField("Firma");
deliverySalutation = new ComboBox<>("Anrede");
deliverySalutation.setItems("Herr", "Frau", "Divers");
deliveryFirstName = new TextField("Vorname*");
deliveryFirstName.setRequiredIndicatorVisible(true);
deliveryLastName = new TextField("Nachname*");
deliveryLastName.setRequiredIndicatorVisible(true);
deliveryPhone = new TextField("Telefonnummer");
deliveryStreet = new TextField("Straße*");
deliveryStreet.setRequiredIndicatorVisible(true);
deliveryHouseNumber = new TextField("Hausnr*");
deliveryHouseNumber.setRequiredIndicatorVisible(true);
deliveryAddressAddition = new TextField("Adresszusatz");
deliveryZip = new TextField("Postleitzahl*");
deliveryZip.setRequiredIndicatorVisible(true);
deliveryCity = new TextField("Ort*");
deliveryCity.setRequiredIndicatorVisible(true);
saveDeliveryAddress = new Checkbox("Die Adresse für zukünftige Aufträge speichern.");
saveDeliveryAddress.setValue(true);
// Digital processing
digitalProcessing = new Checkbox("Digitale Abwicklung per App");
digitalProcessing.setValue(true);
appUser = new ComboBox<>("App-Nutzer");
appUser.setItems("App-Nutzer");
// Submit button
submitButton = new Button("Auftrag anlegen", event -> submit());
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
}
private void setupLayout() {
setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX,
LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM,
LumoUtility.Gap.SMALL);
add(new ViewToolbar("Neuen Auftrag anlegen"));
// Customer selection section
HorizontalLayout customerLayout = new HorizontalLayout();
customerLayout.setWidthFull();
customerLayout.setAlignItems(FlexComponent.Alignment.END);
customerLayout.add(customerSelection, preloadAddressButton);
customerSelection.setWidth("70%");
preloadAddressButton.setWidth("30%");
add(customerLayout);
add(requiredFieldsNotice);
// Main content layout with two equal columns (50% each)
HorizontalLayout mainLayout = new HorizontalLayout();
mainLayout.setWidthFull();
mainLayout.setSpacing(true);
mainLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START);
// Left column (50%) - Pickup address section
VerticalLayout leftColumn = createPickupSection();
leftColumn.setWidth("50%");
// Right column (50%) - Delivery address section
VerticalLayout rightColumn = createDeliverySection();
rightColumn.setWidth("50%");
// Add copy button to the right column at the top
Button copyButton = new Button("Abholadresse kopieren");
copyButton.setIcon(new Icon(VaadinIcon.ARROW_RIGHT));
copyButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
copyButton.addClickListener(e -> copyPickupToDelivery());
copyButton.getStyle().set("margin-bottom", "var(--lumo-space-m)");
// Insert copy button at the beginning of right column
rightColumn.addComponentAsFirst(copyButton);
mainLayout.add(leftColumn, rightColumn);
add(mainLayout);
// Digital processing section
VerticalLayout digitalSection = new VerticalLayout();
digitalSection.setSpacing(false);
digitalSection.add(digitalProcessing, appUser);
add(digitalSection);
// Submit button
HorizontalLayout submitLayout = new HorizontalLayout();
submitLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
submitLayout.add(submitButton);
add(submitLayout);
}
private VerticalLayout createPickupSection() {
VerticalLayout section = new VerticalLayout();
section.setSpacing(true);
section.setPadding(false);
H3 title = new H3("Abholadresse");
title.getStyle().set("margin", "0");
title.getStyle().set("display", "flex");
title.getStyle().set("align-items", "center");
Icon helpIcon = new Icon(VaadinIcon.QUESTION_CIRCLE);
helpIcon.getStyle().set("margin-left", "8px");
title.add(helpIcon);
section.add(title);
section.add(pickupCompany);
section.add(pickupSalutation);
section.add(pickupFirstName);
section.add(pickupLastName);
section.add(pickupPhone);
HorizontalLayout streetLayout = new HorizontalLayout();
streetLayout.setWidthFull();
streetLayout.add(pickupStreet, pickupHouseNumber);
pickupStreet.setWidth("70%");
pickupHouseNumber.setWidth("30%");
section.add(streetLayout);
section.add(pickupAddressAddition);
HorizontalLayout zipCityLayout = new HorizontalLayout();
zipCityLayout.setWidthFull();
zipCityLayout.add(pickupZip, pickupCity);
pickupZip.setWidth("30%");
pickupCity.setWidth("70%");
section.add(zipCityLayout);
section.add(savePickupAddress);
return section;
}
private VerticalLayout createDeliverySection() {
VerticalLayout section = new VerticalLayout();
section.setSpacing(true);
section.setPadding(false);
H3 title = new H3("Lieferadresse");
title.getStyle().set("margin", "0");
section.add(title);
section.add(deliveryCompany);
section.add(deliverySalutation);
section.add(deliveryFirstName);
section.add(deliveryLastName);
section.add(deliveryPhone);
HorizontalLayout streetLayout = new HorizontalLayout();
streetLayout.setWidthFull();
streetLayout.add(deliveryStreet, deliveryHouseNumber);
deliveryStreet.setWidth("70%");
deliveryHouseNumber.setWidth("30%");
section.add(streetLayout);
section.add(deliveryAddressAddition);
HorizontalLayout zipCityLayout = new HorizontalLayout();
zipCityLayout.setWidthFull();
zipCityLayout.add(deliveryZip, deliveryCity);
deliveryZip.setWidth("30%");
deliveryCity.setWidth("70%");
section.add(zipCityLayout);
section.add(saveDeliveryAddress);
return section;
}
private void copyPickupToDelivery() {
deliveryCompany.setValue(pickupCompany.getValue());
deliverySalutation.setValue(pickupSalutation.getValue());
deliveryFirstName.setValue(pickupFirstName.getValue());
deliveryLastName.setValue(pickupLastName.getValue());
deliveryPhone.setValue(pickupPhone.getValue());
deliveryStreet.setValue(pickupStreet.getValue());
deliveryHouseNumber.setValue(pickupHouseNumber.getValue());
deliveryAddressAddition.setValue(pickupAddressAddition.getValue());
deliveryZip.setValue(pickupZip.getValue());
deliveryCity.setValue(pickupCity.getValue());
}
private void setupValidation() {
// Basic validation setup - detailed validation can be added later
}
private void submit() {
Job job = new Job();
// Set pickup address
job.setPickupCompany(pickupCompany.getValue());
job.setPickupSalutation(pickupSalutation.getValue());
job.setPickupFirstName(pickupFirstName.getValue());
job.setPickupLastName(pickupLastName.getValue());
job.setPickupPhone(pickupPhone.getValue());
job.setPickupStreet(pickupStreet.getValue());
job.setPickupHouseNumber(pickupHouseNumber.getValue());
job.setPickupAddressAddition(pickupAddressAddition.getValue());
job.setPickupZip(pickupZip.getValue());
job.setPickupCity(pickupCity.getValue());
job.setSavePickupAddress(savePickupAddress.getValue());
// Set delivery address
job.setDeliveryCompany(deliveryCompany.getValue());
job.setDeliverySalutation(deliverySalutation.getValue());
job.setDeliveryFirstName(deliveryFirstName.getValue());
job.setDeliveryLastName(deliveryLastName.getValue());
job.setDeliveryPhone(deliveryPhone.getValue());
job.setDeliveryStreet(deliveryStreet.getValue());
job.setDeliveryHouseNumber(deliveryHouseNumber.getValue());
job.setDeliveryAddressAddition(deliveryAddressAddition.getValue());
job.setDeliveryZip(deliveryZip.getValue());
job.setDeliveryCity(deliveryCity.getValue());
job.setSaveDeliveryAddress(saveDeliveryAddress.getValue());
// Set digital processing
job.setDigitalProcessing(digitalProcessing.getValue());
job.setAppUser(appUser.getValue());
job.setCustomerSelection(customerSelection.getValue());
addJobService.addJob(job);
System.out.println("Job created successfully");
}
}

View File

@@ -16,13 +16,16 @@ import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.Layout; import com.vaadin.flow.router.Layout;
import com.vaadin.flow.server.menu.MenuConfiguration; import com.vaadin.flow.server.menu.MenuConfiguration;
import com.vaadin.flow.server.menu.MenuEntry; import com.vaadin.flow.server.menu.MenuEntry;
import de.assecutor.votianlt.security.SecurityService;
import static com.vaadin.flow.theme.lumo.LumoUtility.*; import static com.vaadin.flow.theme.lumo.LumoUtility.*;
@Layout
public final class MainLayout extends AppLayout { public final class MainLayout extends AppLayout {
public MainLayout() { private final SecurityService securityService;
public MainLayout(SecurityService securityService) {
this.securityService = securityService;
setPrimarySection(Section.DRAWER); setPrimarySection(Section.DRAWER);
addToDrawer(createHeader(), new Scroller(createSideNav()), createUserMenu()); addToDrawer(createHeader(), new Scroller(createSideNav()), createUserMenu());
@@ -58,8 +61,9 @@ public final class MainLayout extends AppLayout {
} }
private Component createUserMenu() { private Component createUserMenu() {
// TODO Replace with real user information and actions String currentUser = securityService.getCurrentUsername();
var avatar = new Avatar("John Smith");
var avatar = new Avatar(currentUser);
avatar.addThemeVariants(AvatarVariant.LUMO_XSMALL); avatar.addThemeVariants(AvatarVariant.LUMO_XSMALL);
avatar.addClassNames(Margin.Right.SMALL); avatar.addClassNames(Margin.Right.SMALL);
avatar.setColorIndex(5); avatar.setColorIndex(5);
@@ -69,10 +73,10 @@ public final class MainLayout extends AppLayout {
userMenu.addClassNames(Margin.MEDIUM); userMenu.addClassNames(Margin.MEDIUM);
var userMenuItem = userMenu.addItem(avatar); var userMenuItem = userMenu.addItem(avatar);
userMenuItem.add("John Smith"); userMenuItem.add(currentUser);
userMenuItem.getSubMenu().addItem("View Profile"); userMenuItem.getSubMenu().addItem("Profil anzeigen");
userMenuItem.getSubMenu().addItem("Manage Settings"); userMenuItem.getSubMenu().addItem("Einstellungen");
userMenuItem.getSubMenu().addItem("Logout"); userMenuItem.getSubMenu().addItem("Abmelden", e -> securityService.logout());
return userMenu; return userMenu;
} }

View File

@@ -18,7 +18,7 @@ import java.time.Clock;
import static com.vaadin.flow.spring.data.VaadinSpringDataHelpers.toSpringPageRequest; import static com.vaadin.flow.spring.data.VaadinSpringDataHelpers.toSpringPageRequest;
@Route("customer") @Route(value = "customer", layout = de.assecutor.votianlt.pages.base.ui.view.MainLayout.class)
@PageTitle("Kunden") @PageTitle("Kunden")
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Kunden") @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Kunden")
public class CustomersView extends Main { public class CustomersView extends Main {

View File

@@ -12,5 +12,5 @@ public interface LoginRepository extends MongoRepository<User, String> {
// If you don't need a total row count, Slice is better than Page. // If you don't need a total row count, Slice is better than Page.
Slice<User> findAllBy(Pageable pageable); Slice<User> findAllBy(Pageable pageable);
Optional<User> findByEmailAndPassword(String email, String password); Optional<User> findByEmail(String email);
} }

View File

@@ -3,6 +3,7 @@ package de.assecutor.votianlt.pages.login.service;
import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.notification.Notification;
import de.assecutor.votianlt.model.User; import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.pages.login.domain.LoginRepository; import de.assecutor.votianlt.pages.login.domain.LoginRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -15,18 +16,25 @@ import java.util.Optional;
public class LoginService { public class LoginService {
private final LoginRepository loginRepository; private final LoginRepository loginRepository;
private final PasswordEncoder passwordEncoder;
LoginService(LoginRepository loginRepository, Clock clock) { LoginService(LoginRepository loginRepository, PasswordEncoder passwordEncoder, Clock clock) {
this.loginRepository = loginRepository; this.loginRepository = loginRepository;
this.passwordEncoder = passwordEncoder;
} }
public Optional<User> findUser(String mail, String password) { public Optional<User> findUser(String email, String password) {
var user = loginRepository.findByEmailAndPassword(mail, password); var userOpt = loginRepository.findByEmail(email);
if (userOpt.isPresent()) {
User user = userOpt.get();
// Prüfe das Passwort mit BCrypt
if (passwordEncoder.matches(password, user.getPassword())) {
return Optional.of(user);
}
}
if (user.isEmpty()) {
Notification.show("Login failed", 3000, Notification.Position.BOTTOM_END); Notification.show("Login failed", 3000, Notification.Position.BOTTOM_END);
} return Optional.empty();
return user;
} }
} }

View File

@@ -3,64 +3,58 @@ package de.assecutor.votianlt.pages.login.ui.view;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Main; import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.component.login.LoginForm;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility; import com.vaadin.flow.server.auth.AnonymousAllowed;
import de.assecutor.votianlt.pages.login.service.LoginService;
import de.assecutor.votianlt.pages.register.service.RegisterService;
import de.assecutor.votianlt.util.Util;
import java.io.Console;
import java.time.Clock;
@Route("login") @Route("login")
@PageTitle("Bei VotianLT anmelden") @PageTitle("Bei VotianLT anmelden")
//@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Bei VotianLT registrieren") @AnonymousAllowed
public class LoginView extends Main { public class LoginView extends VerticalLayout implements BeforeEnterObserver {
private final LoginService loginService;
TextField usernameField = new TextField("E-Mail-Adresse"); private final LoginForm loginForm = new LoginForm();
TextField password1Field = new TextField("Passwort");
Button submitButton = new Button("Registrieren");
public LoginView(LoginService loginService, Clock clock) {
this.loginService = loginService;
// Setze den Button als primär
submitButton = new Button("Anmelden", event -> login());
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
// Erstelle ein Div als Container (oder direkt ein Layout)
VerticalLayout formLayout = new VerticalLayout();
formLayout.add(usernameField, password1Field, submitButton);
// Zentriere die Inhalte vertikal und horizontal
formLayout.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
formLayout.setSpacing(true);
formLayout.setSizeUndefined(); // Inhalt eng setzen
public LoginView() {
addClassName("login-view");
setSizeFull(); setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL);
add(formLayout); setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
loginForm.setAction("login");
H1 title = new H1("VotianLT");
title.getStyle().set("color", "var(--lumo-primary-color)");
Button registerButton = new Button("Noch kein Konto? Registrieren",
e -> UI.getCurrent().navigate("register"));
registerButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
VerticalLayout loginLayout = new VerticalLayout();
loginLayout.setAlignItems(FlexComponent.Alignment.CENTER);
loginLayout.add(title, loginForm, registerButton);
loginLayout.setMaxWidth("400px");
loginLayout.setPadding(true);
add(loginLayout);
} }
private void login() { @Override
var user = loginService.findUser(usernameField.getValue(), password1Field.getValue()); public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
// Zeige Fehlermeldung bei fehlgeschlagener Anmeldung
if (user.isPresent()) { if (beforeEnterEvent.getLocation()
UI.getCurrent().navigate("customer"); .getQueryParameters()
.getParameters()
.containsKey("error")) {
loginForm.setError(true);
} }
} }
private void submit() {
Util.changeDrawerState(true);
}
} }

View File

@@ -1,12 +1,7 @@
package de.assecutor.votianlt.pages.register.domain; package de.assecutor.votianlt.pages.register.domain;
import de.assecutor.votianlt.model.User; import de.assecutor.votianlt.repository.UserRepository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface RegisterRepository extends MongoRepository<User, String> { public interface RegisterRepository extends UserRepository {
// Erbt alle Methoden von UserRepository
// If you don't need a total row count, Slice is better than Page.
Slice<User> findAllBy(Pageable pageable);
} }

View File

@@ -0,0 +1,59 @@
package de.assecutor.votianlt.pages.register.service;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
public UserService(PasswordEncoder passwordEncoder, UserRepository userRepository) {
this.passwordEncoder = passwordEncoder;
this.userRepository = userRepository;
}
public User findByEmail(String email) {
return userRepository.findByEmail(email).orElse(null);
}
public User save(User user) {
if (user.getPassword() != null && !user.getPassword().startsWith("$2a$")) {
// Passwort verschlüsseln, falls noch nicht verschlüsselt
user.setPassword(passwordEncoder.encode(user.getPassword()));
}
// Timestamps setzen
if (user.getCreatedAt() == null) {
user.setCreatedAt(LocalDateTime.now());
}
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
}
public boolean existsByEmail(String email) {
return userRepository.existsByEmail(email);
}
public User createUser(String email, String password, String firstName, String lastName) {
if (existsByEmail(email)) {
throw new RuntimeException("User with email " + email + " already exists");
}
User user = new User();
user.setEmail(email);
user.setPassword(password); // wird in save() verschlüsselt
user.setFirstname(firstName);
user.setName(lastName);
user.setIsActivated((byte) 1);
user.setIsEmailConfirmed((byte) 1);
return save(user);
}
}

View File

@@ -2,61 +2,162 @@ package de.assecutor.votianlt.pages.register.ui.view;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Main; import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility; import com.vaadin.flow.server.auth.AnonymousAllowed;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.pages.register.service.RegisterService; import de.assecutor.votianlt.pages.register.service.RegisterService;
import de.assecutor.votianlt.util.Util; import de.assecutor.votianlt.pages.register.service.UserService;
import java.time.Clock; import java.time.Clock;
@Route("register") @Route("register")
@PageTitle("Bei VotianLT anmelden") @PageTitle("Bei VotianLT registrieren")
//@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Bei VotianLT registrieren") @AnonymousAllowed
public class RegisterView extends Main { public class RegisterView extends VerticalLayout {
private final RegisterService registerService; private final RegisterService registerService;
private final UserService userService;
TextField mail = new TextField("E-Mail-Adresse"); private TextField emailField;
TextField password1Field = new TextField("Passwort"); private PasswordField passwordField;
TextField password2Field = new TextField("Passwort wiederholen"); private PasswordField confirmPasswordField;
Button submitButton = new Button("Registrieren"); private Button submitButton;
public RegisterView(RegisterService registerService, Clock clock) { public RegisterView(RegisterService registerService, UserService userService, Clock clock) {
this.registerService = registerService; this.registerService = registerService;
this.userService = userService;
// Setze den Button als primär // Layout-Konfiguration für vollständige Zentrierung
submitButton = new Button("Registrieren", event -> registerUser());
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
// Erstelle ein Div als Container (oder direkt ein Layout)
VerticalLayout formLayout = new VerticalLayout();
formLayout.add(mail, password1Field, password2Field, submitButton);
// Zentriere die Inhalte vertikal und horizontal
formLayout.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
formLayout.setSpacing(true);
formLayout.setSizeUndefined(); // Inhalt eng setzen
setSizeFull(); setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN, LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL); setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
setPadding(true);
setSpacing(false);
add(formLayout); // Hauptcontainer für das Formular
VerticalLayout formContainer = createFormContainer();
add(formContainer);
}
private VerticalLayout createFormContainer() {
VerticalLayout container = new VerticalLayout();
container.setWidth("400px");
container.setPadding(true);
container.setSpacing(true);
container.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
// Styling für den Container
container.getStyle().set("background-color", "var(--lumo-base-color)");
container.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
container.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
container.getStyle().set("box-shadow", "var(--lumo-box-shadow-s)");
// Titel
H1 title = new H1("Registrierung");
title.getStyle().set("text-align", "center");
title.getStyle().set("color", "var(--lumo-primary-color)");
title.getStyle().set("margin-top", "0");
H2 subtitle = new H2("Erstellen Sie Ihr VotianLT-Konto");
subtitle.getStyle().set("text-align", "center");
subtitle.getStyle().set("color", "var(--lumo-secondary-text-color)");
subtitle.getStyle().set("font-size", "var(--lumo-font-size-l)");
subtitle.getStyle().set("font-weight", "normal");
subtitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
// Formularfelder
emailField = new TextField("E-Mail-Adresse");
emailField.setWidthFull();
emailField.setRequired(true);
emailField.setPlaceholder("ihre.email@beispiel.de");
passwordField = new PasswordField("Passwort");
passwordField.setWidthFull();
passwordField.setRequired(true);
passwordField.setPlaceholder("Mindestens 6 Zeichen");
confirmPasswordField = new PasswordField("Passwort bestätigen");
confirmPasswordField.setWidthFull();
confirmPasswordField.setRequired(true);
confirmPasswordField.setPlaceholder("Passwort wiederholen");
// Submit Button
submitButton = new Button("Registrieren", event -> registerUser());
submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_LARGE);
submitButton.setWidthFull();
// Zurück-Link
Button backButton = new Button("Zurück zur Startseite", event ->
getUI().ifPresent(ui -> ui.navigate("")));
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
backButton.setWidthFull();
container.add(title, subtitle, emailField, passwordField, confirmPasswordField, submitButton, backButton);
return container;
} }
private void registerUser() { private void registerUser() {
var mail = this.mail.getValue(); var email = emailField.getValue().trim();
var password = password1Field.getValue(); var password = passwordField.getValue();
var confirmPassword = confirmPasswordField.getValue();
User user = new User(); // Validierung
user.setEmail(mail); if (email.isEmpty()) {
user.setPassword(password); Notification.show("Bitte geben Sie eine E-Mail-Adresse ein.", 3000, Notification.Position.MIDDLE);
emailField.focus();
return;
}
registerService.registerUser(user); if (!email.contains("@") || !email.contains(".")) {
Notification.show("Bitte geben Sie eine gültige E-Mail-Adresse ein.", 3000, Notification.Position.MIDDLE);
emailField.focus();
return;
}
if (password.isEmpty()) {
Notification.show("Bitte geben Sie ein Passwort ein.", 3000, Notification.Position.MIDDLE);
passwordField.focus();
return;
}
if (password.length() < 6) {
Notification.show("Das Passwort muss mindestens 6 Zeichen lang sein.", 3000, Notification.Position.MIDDLE);
passwordField.focus();
return;
}
if (!password.equals(confirmPassword)) {
Notification.show("Die Passwörter stimmen nicht überein.", 3000, Notification.Position.MIDDLE);
confirmPasswordField.focus();
return;
}
try {
// Benutzer erstellen
userService.createUser(email, password, "Benutzer", "Name");
// Erfolgsmeldung und Weiterleitung
Notification.show("Registrierung erfolgreich! Sie werden zur Anmeldung weitergeleitet.",
3000, Notification.Position.MIDDLE);
getUI().ifPresent(ui -> ui.navigate("login"));
} catch (RuntimeException e) {
if (e.getMessage().contains("already exists")) {
Notification.show("Ein Benutzer mit dieser E-Mail-Adresse existiert bereits.",
5000, Notification.Position.MIDDLE);
emailField.focus();
} else {
Notification.show("Registrierung fehlgeschlagen: " + e.getMessage(),
5000, Notification.Position.MIDDLE);
}
}
} }
} }

View File

@@ -1,40 +1,344 @@
package de.assecutor.votianlt.pages.start.ui.view; package de.assecutor.votianlt.pages.start.ui.view;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Main; import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.router.Menu; import com.vaadin.flow.component.html.*;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route; import com.vaadin.flow.router.Route;
import com.vaadin.flow.theme.lumo.LumoUtility; import com.vaadin.flow.router.RouterLayout;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import de.assecutor.votianlt.security.SecurityService;
@Route("") @Route("")
@PageTitle("Dummy") @PageTitle("VotianLT - Willkommen")
@Menu(order = 0, icon = "vaadin:clipboard-check", title = "DUMMY") @AnonymousAllowed
public class StartView extends Main { public class StartView extends VerticalLayout {
final Button registerBtn; private final SecurityService securityService;
final Button loginBtn;
public StartView() {
registerBtn = new Button("Registrieren", event -> register());
registerBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
add(registerBtn);
loginBtn = new Button("Anmelden", event -> login());
loginBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
add(loginBtn);
public StartView(SecurityService securityService) {
this.securityService = securityService;
setSizeFull(); setSizeFull();
addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN, setPadding(false);
LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL); setSpacing(false);
setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.STRETCH);
// Header mit Navigation
add(createHeader());
// Hero Section
add(createHeroSection());
// System Section
add(createSystemSection());
// App Section
add(createAppSection());
// Footer
add(createFooter());
}
private Component createHeader() {
HorizontalLayout header = new HorizontalLayout();
header.setWidthFull();
header.setPadding(true);
header.setSpacing(true);
header.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
header.getStyle().set("background-color", "var(--lumo-contrast-5pct)");
header.getStyle().set("border-bottom", "1px solid var(--lumo-contrast-10pct)");
// Logo
H1 logo = new H1("votian LT");
logo.getStyle().set("color", "var(--lumo-primary-color)");
logo.getStyle().set("margin", "0");
logo.getStyle().set("font-weight", "bold");
// Navigation - abhängig vom Anmeldestatus
Component navigation = securityService.isUserLoggedIn()
? createAuthenticatedNavigation()
: createAnonymousNavigation();
header.add(logo, navigation);
return header;
}
private Component createAnonymousNavigation() {
HorizontalLayout navButtons = new HorizontalLayout();
navButtons.setSpacing(true);
navButtons.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
Button loginBtn = new Button("Anmelden", event -> login());
loginBtn.addThemeVariants(ButtonVariant.LUMO_CONTRAST);
Button registerBtn = new Button("Registrieren", event -> register());
registerBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
navButtons.add(loginBtn, registerBtn);
return navButtons;
}
private Component createAuthenticatedNavigation() {
HorizontalLayout navLayout = new HorizontalLayout();
navLayout.setSpacing(true);
navLayout.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
// Auftragserstellung Button
Button createOrderBtn = new Button("Auftragserstellung", event ->
UI.getCurrent().navigate("add_job"));
createOrderBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
// Verwaltung ComboBox
ComboBox<String> managementCombo = new ComboBox<>();
managementCombo.setPlaceholder("Verwaltung");
managementCombo.setItems("Kunden", "Aufträge", "Firmen");
managementCombo.addValueChangeListener(event -> {
String value = event.getValue();
if (value != null) {
switch (value) {
case "Kunden":
UI.getCurrent().navigate("customer");
break;
case "Aufträge":
UI.getCurrent().navigate("orders");
break;
case "Firmen":
UI.getCurrent().navigate("add_company");
break;
}
managementCombo.clear(); // Reset selection
}
});
// Benutzer ComboBox
String currentUser = securityService.getCurrentUsername();
ComboBox<String> userCombo = new ComboBox<>();
userCombo.setPlaceholder(currentUser);
userCombo.setItems("Profil anzeigen", "Einstellungen", "Abmelden");
userCombo.addValueChangeListener(event -> {
String value = event.getValue();
if (value != null) {
switch (value) {
case "Profil anzeigen":
// TODO: Navigate to profile
break;
case "Einstellungen":
// TODO: Navigate to settings
break;
case "Abmelden":
securityService.logout();
break;
}
userCombo.clear(); // Reset selection
}
});
// Benachrichtigungs-Icon
Button notificationBtn = new Button();
notificationBtn.setIcon(VaadinIcon.BELL.create());
notificationBtn.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY);
notificationBtn.setTooltipText("Benachrichtigungen");
notificationBtn.addClickListener(event -> {
// TODO: Show notifications
com.vaadin.flow.component.notification.Notification.show("Keine neuen Benachrichtigungen", 3000,
com.vaadin.flow.component.notification.Notification.Position.TOP_END);
});
navLayout.add(createOrderBtn, managementCombo, userCombo, notificationBtn);
return navLayout;
}
private Component createHeroSection() {
VerticalLayout heroSection = new VerticalLayout();
heroSection.setWidthFull();
heroSection.setPadding(true);
heroSection.setSpacing(true);
heroSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
heroSection.getStyle().set("background", "linear-gradient(135deg, var(--lumo-primary-color-10pct), var(--lumo-primary-color-50pct))");
heroSection.getStyle().set("min-height", "400px");
heroSection.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
// Hero Image Placeholder
Icon heroIcon = VaadinIcon.TRUCK.create();
heroIcon.setSize("120px");
heroIcon.getStyle().set("color", "var(--lumo-primary-color)");
H1 heroTitle = new H1("VotianLT - Ihr digitaler Transportpartner");
heroTitle.getStyle().set("text-align", "center");
heroTitle.getStyle().set("color", "var(--lumo-primary-text-color)");
heroTitle.getStyle().set("margin-bottom", "var(--lumo-space-l)");
Paragraph heroDescription = new Paragraph(
"Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe - " +
"volldigital und aus einem Guss. Konzentrieren Sie sich auf Ihr Geschäft, " +
"wir kümmern uns um die Büroarbeit."
);
heroDescription.getStyle().set("text-align", "center");
heroDescription.getStyle().set("max-width", "600px");
heroDescription.getStyle().set("font-size", "var(--lumo-font-size-l)");
Button ctaButton = new Button("Jetzt kostenlos testen", event -> register());
ctaButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_LARGE);
heroSection.add(heroIcon, heroTitle, heroDescription, ctaButton);
return heroSection;
}
private Component createSystemSection() {
VerticalLayout systemSection = new VerticalLayout();
systemSection.setWidthFull();
systemSection.setPadding(true);
systemSection.setSpacing(true);
systemSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
systemSection.getStyle().set("background-color", "var(--lumo-base-color)");
// Section Header
H2 systemTitle = new H2("Das System");
systemTitle.getStyle().set("color", "var(--lumo-primary-color)");
systemTitle.getStyle().set("text-align", "center");
Paragraph systemIntro = new Paragraph(
"Für Solo-Selbstständige und Kleinunternehmer im Transportgewerbe ist von entscheidender Bedeutung, " +
"dass sie sich in erster Linie auf ihr eigentliches Geschäft konzentrieren können: Kunden gewinnen und Waren von A nach B liefern."
);
systemIntro.getStyle().set("text-align", "center");
systemIntro.getStyle().set("max-width", "800px");
systemIntro.getStyle().set("margin-bottom", "var(--lumo-space-xl)");
// Features Grid
HorizontalLayout featuresGrid = new HorizontalLayout();
featuresGrid.setWidthFull();
featuresGrid.setSpacing(true);
featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.START);
// Feature Cards
featuresGrid.add(
createFeatureCard(VaadinIcon.COG, "Einrichtungsassistent",
"Mithilfe des Einrichtungsassistenten haben Sie die Möglichkeit, Ihr Nutzerprofil zu vervollständigen."),
createFeatureCard(VaadinIcon.USERS, "Kunden- und Auftragsverwaltung",
"Mit der Kunden- und Auftragsverwaltung haben Sie alle Kontaktdaten und Auftragsdetails stets im Blick."),
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, "Auftragserstellung",
"Stellen Sie mit wenigen Mausklicks Aufträge ins System ein und legen Sie fest, welcher Mitarbeiter welchen Transportauftrag abarbeiten soll.")
);
systemSection.add(systemTitle, systemIntro, featuresGrid);
return systemSection;
}
private Component createFeatureCard(VaadinIcon iconType, String title, String description) {
VerticalLayout card = new VerticalLayout();
card.setSpacing(true);
card.setPadding(true);
card.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
card.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
card.getStyle().set("border-radius", "var(--lumo-border-radius-m)");
card.getStyle().set("background-color", "var(--lumo-base-color)");
card.getStyle().set("box-shadow", "var(--lumo-box-shadow-xs)");
card.setWidth("300px");
Icon icon = iconType.create();
icon.setSize("48px");
icon.getStyle().set("color", "var(--lumo-primary-color)");
H3 cardTitle = new H3(title);
cardTitle.getStyle().set("text-align", "center");
cardTitle.getStyle().set("margin", "var(--lumo-space-s) 0");
Paragraph cardDescription = new Paragraph(description);
cardDescription.getStyle().set("text-align", "center");
cardDescription.getStyle().set("font-size", "var(--lumo-font-size-s)");
card.add(icon, cardTitle, cardDescription);
return card;
}
private Component createAppSection() {
VerticalLayout appSection = new VerticalLayout();
appSection.setWidthFull();
appSection.setPadding(true);
appSection.setSpacing(true);
appSection.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
appSection.getStyle().set("background-color", "var(--lumo-contrast-5pct)");
H2 appTitle = new H2("Die App");
appTitle.getStyle().set("color", "var(--lumo-primary-color)");
appTitle.getStyle().set("text-align", "center");
Paragraph appDescription = new Paragraph(
"Jeder Auftrag kann optional über die votianLT-App abgearbeitet werden ganz ohne \"Zettelwirtschaft\". " +
"So gelangen alle relevanten Auftragsinformationen direkt auf das Smartphone des Fahrers."
);
appDescription.getStyle().set("text-align", "center");
appDescription.getStyle().set("max-width", "800px");
Icon appIcon = VaadinIcon.MOBILE.create();
appIcon.setSize("80px");
appIcon.getStyle().set("color", "var(--lumo-primary-color)");
appSection.add(appTitle, appDescription, appIcon);
return appSection;
}
private Component createFooter() {
VerticalLayout footer = new VerticalLayout();
footer.setWidthFull();
footer.setPadding(true);
footer.setSpacing(true);
footer.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
footer.getStyle().set("background-color", "var(--lumo-contrast-10pct)");
footer.getStyle().set("border-top", "1px solid var(--lumo-contrast-20pct)");
// Company Info
H3 companyTitle = new H3("Impressum");
companyTitle.getStyle().set("color", "var(--lumo-primary-color)");
companyTitle.getStyle().set("text-align", "center");
VerticalLayout companyInfo = new VerticalLayout();
companyInfo.setSpacing(false);
companyInfo.setPadding(false);
companyInfo.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
companyInfo.add(
new Paragraph("Assecutor Data Service GmbH"),
new Paragraph("Ottensener Str. 8, 22525 Hamburg"),
new Paragraph("Telefon: +49 40 18 123 771 0"),
new Paragraph("E-Mail: ahoi@assecutor.de")
);
// Call to Action
Paragraph ctaText = new Paragraph(
"Registrieren Sie sich noch heute und nutzen den kostenfreien Probemonat, " +
"um das System auf Herz und Nieren zu testen."
);
ctaText.getStyle().set("text-align", "center");
ctaText.getStyle().set("font-weight", "bold");
ctaText.getStyle().set("color", "var(--lumo-primary-color)");
ctaText.getStyle().set("margin-top", "var(--lumo-space-l)");
Paragraph slogan = new Paragraph("Betreiben Sie Ihr Geschäft smart … mit votianLT!");
slogan.getStyle().set("text-align", "center");
slogan.getStyle().set("font-style", "italic");
slogan.getStyle().set("color", "var(--lumo-primary-color)");
footer.add(companyTitle, companyInfo, ctaText, slogan);
return footer;
} }
private void register() { private void register() {
UI.getCurrent().navigate("register"); UI.getCurrent().navigate("register");
} }
private void login() { UI.getCurrent().navigate("login"); } private void login() {
UI.getCurrent().navigate("login");
}
} }

View File

@@ -0,0 +1,23 @@
package de.assecutor.votianlt.pages.test;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.assecutor.votianlt.pages.base.ui.view.MainLayout;
@Route(value = "test", layout = MainLayout.class)
@PageTitle("Test Seite")
public class TestView extends VerticalLayout {
public TestView() {
H1 title = new H1("Test Seite");
Paragraph description = new Paragraph("Diese Seite dient zum Testen der Navigation.");
add(title, description);
setPadding(true);
setSpacing(true);
}
}

View File

@@ -0,0 +1,17 @@
package de.assecutor.votianlt.repository;
import de.assecutor.votianlt.model.User;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends MongoRepository<User, String> {
Optional<User> findByEmail(String email);
boolean existsByEmail(String email);
void deleteByEmail(String email);
}

View File

@@ -0,0 +1,61 @@
package de.assecutor.votianlt.security;
import com.vaadin.flow.spring.security.VaadinWebSecurity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import de.assecutor.votianlt.pages.login.ui.view.LoginView;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends VaadinWebSecurity {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// Konfiguriere zusätzliche öffentliche Endpunkte vor der Basis-Konfiguration
http.authorizeHttpRequests(auth -> auth
// Öffentliche Endpunkte
.requestMatchers(
new AntPathRequestMatcher("/"),
new AntPathRequestMatcher("/register"),
new AntPathRequestMatcher("/login"),
new AntPathRequestMatcher("/images/**"),
new AntPathRequestMatcher("/icons/**"),
new AntPathRequestMatcher("/favicon.ico"),
new AntPathRequestMatcher("/robots.txt"),
new AntPathRequestMatcher("/manifest.webmanifest"),
new AntPathRequestMatcher("/sw.js"),
new AntPathRequestMatcher("/offline.html"),
new AntPathRequestMatcher("/frontend/**"),
new AntPathRequestMatcher("/webjars/**"),
new AntPathRequestMatcher("/h2-console/**"),
new AntPathRequestMatcher("/frontend-es5/**", "/frontend-es6/**")
).permitAll()
);
// Delegiere die Basis-Konfiguration an VaadinWebSecurity
// Dies fügt automatisch .anyRequest().authenticated() hinzu
super.configure(http);
// Setze die Login-View
setLoginView(http, LoginView.class);
// Logout-Konfiguration
http.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
);
}
}

View File

@@ -0,0 +1,42 @@
package de.assecutor.votianlt.security;
import com.vaadin.flow.spring.security.AuthenticationContext;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class SecurityService {
private final AuthenticationContext authenticationContext;
public SecurityService(AuthenticationContext authenticationContext) {
this.authenticationContext = authenticationContext;
}
public Optional<UserDetails> getAuthenticatedUser() {
return authenticationContext.getAuthenticatedUser(UserDetails.class);
}
public boolean isUserLoggedIn() {
return authenticationContext.isAuthenticated();
}
public String getCurrentUsername() {
return getAuthenticatedUser()
.map(UserDetails::getUsername)
.orElse("Anonymous");
}
public void logout() {
authenticationContext.logout();
}
public boolean hasRole(String role) {
return getAuthenticatedUser()
.map(user -> user.getAuthorities().stream()
.anyMatch(authority -> authority.getAuthority().equals("ROLE_" + role)))
.orElse(false);
}
}

View File

@@ -0,0 +1,44 @@
package de.assecutor.votianlt.security;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.pages.register.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Collections;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userService.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException("User not found with email: " + email);
}
return new org.springframework.security.core.userdetails.User(
user.getEmail(),
user.getPassword(),
user.getIsActivated() == 1, // enabled
true, // accountNonExpired
true, // credentialsNonExpired
true, // accountNonLocked
getAuthorities(user)
);
}
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
// Basis-Rolle für alle Benutzer
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
}
}

View File

@@ -12,4 +12,4 @@ vaadin.allowed-packages=com.vaadin,org.vaadin,de.assecutor.votianlt
spring.jpa.open-in-view=false spring.jpa.open-in-view=false
# MongoDB # MongoDB
spring.data.mongodb.uri=mongodb://192.168.180.28:27017/votianlt spring.data.mongodb.uri=mongodb://192.168.180.25:27017/votianlt