From ae2743d8e8cc6052081a5251d4c1886bb9fccd11 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Tue, 12 Aug 2025 12:25:10 +0200 Subject: [PATCH] Erweiterungen --- pom.xml | 28 +- .../votianlt/config/DataInitializer.java | 88 +++++ .../de/assecutor/votianlt/model/Company.java | 14 +- .../de/assecutor/votianlt/model/Customer.java | 25 +- .../java/de/assecutor/votianlt/model/Job.java | 42 +++ .../de/assecutor/votianlt/model/User.java | 61 +-- .../add_company/ui/view/AddCompanyView.java | 6 +- .../add_customer/ui/view/AddCustomerView.java | 6 +- .../pages/add_job/service/AddJobService.java | 13 + .../pages/add_job/ui/view/AddJobView.java | 353 ++++++++++++++++++ .../pages/base/ui/view/MainLayout.java | 20 +- .../customers/ui/view/CustomersView.java | 2 +- .../pages/login/domain/LoginRepository.java | 2 +- .../pages/login/service/LoginService.java | 20 +- .../pages/login/ui/view/LoginView.java | 82 ++-- .../register/domain/RegisterRepository.java | 11 +- .../pages/register/service/UserService.java | 59 +++ .../pages/register/ui/view/RegisterView.java | 167 +++++++-- .../pages/start/ui/view/StartView.java | 344 ++++++++++++++++- .../votianlt/pages/test/TestView.java | 23 ++ .../votianlt/repository/UserRepository.java | 17 + .../votianlt/security/SecurityConfig.java | 61 +++ .../votianlt/security/SecurityService.java | 42 +++ .../security/UserDetailsServiceImpl.java | 44 +++ src/main/resources/application.properties | 2 +- 25 files changed, 1363 insertions(+), 169 deletions(-) create mode 100644 src/main/java/de/assecutor/votianlt/config/DataInitializer.java create mode 100644 src/main/java/de/assecutor/votianlt/model/Job.java create mode 100644 src/main/java/de/assecutor/votianlt/pages/add_job/service/AddJobService.java create mode 100644 src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java create mode 100644 src/main/java/de/assecutor/votianlt/pages/register/service/UserService.java create mode 100644 src/main/java/de/assecutor/votianlt/pages/test/TestView.java create mode 100644 src/main/java/de/assecutor/votianlt/repository/UserRepository.java create mode 100644 src/main/java/de/assecutor/votianlt/security/SecurityConfig.java create mode 100644 src/main/java/de/assecutor/votianlt/security/SecurityService.java create mode 100644 src/main/java/de/assecutor/votianlt/security/UserDetailsServiceImpl.java diff --git a/pom.xml b/pom.xml index e1c3f1f..d43a0b5 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,9 @@ 21 + 21 + 21 + 21 24.7.0 1.3.0 @@ -67,10 +70,16 @@ spring-boot-starter-data-mongodb + + + org.springframework.boot + spring-boot-starter-security + + org.projectlombok lombok - 1.18.30 + 1.18.38 provided @@ -82,6 +91,23 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + 21 + + + org.projectlombok + lombok + 1.18.38 + + + + com.diffplug.spotless spotless-maven-plugin diff --git a/src/main/java/de/assecutor/votianlt/config/DataInitializer.java b/src/main/java/de/assecutor/votianlt/config/DataInitializer.java new file mode 100644 index 0000000..2d1c076 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/config/DataInitializer.java @@ -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."); + } +} diff --git a/src/main/java/de/assecutor/votianlt/model/Company.java b/src/main/java/de/assecutor/votianlt/model/Company.java index 62f9b7b..e05e762 100644 --- a/src/main/java/de/assecutor/votianlt/model/Company.java +++ b/src/main/java/de/assecutor/votianlt/model/Company.java @@ -6,12 +6,12 @@ import org.bson.types.ObjectId; @Data public class Company { - public ObjectId id; + private ObjectId id; - public String name; - public String street; - public String houseNumber; - public String addressAddition; - public String zip; - public String city; + private String name; + private String street; + private String houseNumber; + private String addressAddition; + private String zip; + private String city; } \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/model/Customer.java b/src/main/java/de/assecutor/votianlt/model/Customer.java index c54fa29..ef34f2f 100644 --- a/src/main/java/de/assecutor/votianlt/model/Customer.java +++ b/src/main/java/de/assecutor/votianlt/model/Customer.java @@ -7,17 +7,16 @@ import org.bson.types.ObjectId; public class Customer { private ObjectId id; - - public String title; - public String companyName; - public String firstname; - public String lastName; - public String telephone; - public String fax; - public String mail; - public String street; - public String houseNumber; - public String addressAddition; - public String zip; - public String city; + private String title; + private String companyName; + private String firstname; + private String lastName; + private String telephone; + private String fax; + private String mail; + private String street; + private String houseNumber; + private String addressAddition; + private String zip; + private String city; } \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/model/Job.java b/src/main/java/de/assecutor/votianlt/model/Job.java new file mode 100644 index 0000000..a371aff --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/model/Job.java @@ -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; +} diff --git a/src/main/java/de/assecutor/votianlt/model/User.java b/src/main/java/de/assecutor/votianlt/model/User.java index 9c5c0c6..c95405d 100644 --- a/src/main/java/de/assecutor/votianlt/model/User.java +++ b/src/main/java/de/assecutor/votianlt/model/User.java @@ -1,28 +1,45 @@ package de.assecutor.votianlt.model; 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 -public class User -{ - public int usrId; - public int hqId; - public short type; - public String title; - public String name; - public String firstname; - public java.sql.Date birthdate; - public String email; - public String invitationEmail; - public String phone; - public String phone2; - public String fax; - public String password; - public byte isActivated; - public String activationCode; - public byte isEmailConfirmed; - public byte isPasswordLost; - public String passwordCode; - public int passwordTimestamp; - public long activationDate; +@Document(collection = "users") +public class User { + + @Id + private String id; + + private int usrId; + private int hqId; + private short type; + private String title; + private String name; + private String firstname; + private LocalDate birthdate; + + @Indexed(unique = true) + private String email; + + private String invitationEmail; + private String phone; + private String phone2; + private String fax; + 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 roles; } \ No newline at end of file diff --git a/src/main/java/de/assecutor/votianlt/pages/add_company/ui/view/AddCompanyView.java b/src/main/java/de/assecutor/votianlt/pages/add_company/ui/view/AddCompanyView.java index 4355b87..24b5ac1 100644 --- a/src/main/java/de/assecutor/votianlt/pages/add_company/ui/view/AddCompanyView.java +++ b/src/main/java/de/assecutor/votianlt/pages/add_company/ui/view/AddCompanyView.java @@ -16,11 +16,13 @@ import de.assecutor.votianlt.model.Company; import de.assecutor.votianlt.pages.add_company.service.AddCompanyService; import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; +import jakarta.annotation.security.RolesAllowed; 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") @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neue Firma anlegen") +@RolesAllowed("USER") public class AddCompanyView extends Main { private final AddCompanyService addCompanyService; @@ -82,7 +84,7 @@ public class AddCompanyView extends Main { private void submit() { Company company = new Company(); - company.name = companyName.getValue(); + company.setName(companyName.getValue()); try { binder.writeBean(company); diff --git a/src/main/java/de/assecutor/votianlt/pages/add_customer/ui/view/AddCustomerView.java b/src/main/java/de/assecutor/votianlt/pages/add_customer/ui/view/AddCustomerView.java index 04cb50a..88dd259 100644 --- a/src/main/java/de/assecutor/votianlt/pages/add_customer/ui/view/AddCustomerView.java +++ b/src/main/java/de/assecutor/votianlt/pages/add_customer/ui/view/AddCustomerView.java @@ -16,11 +16,13 @@ import de.assecutor.votianlt.model.Customer; import de.assecutor.votianlt.pages.add_customer.service.AddCustomerService; import de.assecutor.votianlt.pages.base.ui.component.ViewToolbar; +import jakarta.annotation.security.RolesAllowed; 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") @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Neuen Kunden anlegen") +@RolesAllowed("USER") public class AddCustomerView extends Main { private final AddCustomerService addCustomerService; @@ -82,7 +84,7 @@ public class AddCustomerView extends Main { private void submit() { Customer customer = new Customer(); - customer.companyName = companyName.getValue(); + customer.setCompanyName(companyName.getValue()); try { binder.writeBean(customer); diff --git a/src/main/java/de/assecutor/votianlt/pages/add_job/service/AddJobService.java b/src/main/java/de/assecutor/votianlt/pages/add_job/service/AddJobService.java new file mode 100644 index 0000000..f99bba4 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/pages/add_job/service/AddJobService.java @@ -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()); + } +} diff --git a/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java b/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java new file mode 100644 index 0000000..1d93983 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/pages/add_job/ui/view/AddJobView.java @@ -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 customerSelection; + private Button preloadAddressButton; + + // Required fields notice + private Span requiredFieldsNotice; + + // Pickup address fields + private TextField pickupCompany; + private ComboBox 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 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 appUser; + + // Submit button + private Button submitButton; + + private final Binder 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"); + } +} diff --git a/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java b/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java index 01331af..23f0ff7 100644 --- a/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java +++ b/src/main/java/de/assecutor/votianlt/pages/base/ui/view/MainLayout.java @@ -16,13 +16,16 @@ import com.vaadin.flow.component.sidenav.SideNavItem; import com.vaadin.flow.router.Layout; import com.vaadin.flow.server.menu.MenuConfiguration; import com.vaadin.flow.server.menu.MenuEntry; +import de.assecutor.votianlt.security.SecurityService; import static com.vaadin.flow.theme.lumo.LumoUtility.*; -@Layout public final class MainLayout extends AppLayout { - public MainLayout() { + private final SecurityService securityService; + + public MainLayout(SecurityService securityService) { + this.securityService = securityService; setPrimarySection(Section.DRAWER); addToDrawer(createHeader(), new Scroller(createSideNav()), createUserMenu()); @@ -58,8 +61,9 @@ public final class MainLayout extends AppLayout { } private Component createUserMenu() { - // TODO Replace with real user information and actions - var avatar = new Avatar("John Smith"); + String currentUser = securityService.getCurrentUsername(); + + var avatar = new Avatar(currentUser); avatar.addThemeVariants(AvatarVariant.LUMO_XSMALL); avatar.addClassNames(Margin.Right.SMALL); avatar.setColorIndex(5); @@ -69,10 +73,10 @@ public final class MainLayout extends AppLayout { userMenu.addClassNames(Margin.MEDIUM); var userMenuItem = userMenu.addItem(avatar); - userMenuItem.add("John Smith"); - userMenuItem.getSubMenu().addItem("View Profile"); - userMenuItem.getSubMenu().addItem("Manage Settings"); - userMenuItem.getSubMenu().addItem("Logout"); + userMenuItem.add(currentUser); + userMenuItem.getSubMenu().addItem("Profil anzeigen"); + userMenuItem.getSubMenu().addItem("Einstellungen"); + userMenuItem.getSubMenu().addItem("Abmelden", e -> securityService.logout()); return userMenu; } diff --git a/src/main/java/de/assecutor/votianlt/pages/customers/ui/view/CustomersView.java b/src/main/java/de/assecutor/votianlt/pages/customers/ui/view/CustomersView.java index cf72eb1..95cbb7a 100644 --- a/src/main/java/de/assecutor/votianlt/pages/customers/ui/view/CustomersView.java +++ b/src/main/java/de/assecutor/votianlt/pages/customers/ui/view/CustomersView.java @@ -18,7 +18,7 @@ import java.time.Clock; 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") @Menu(order = 0, icon = "vaadin:clipboard-check", title = "Kunden") public class CustomersView extends Main { diff --git a/src/main/java/de/assecutor/votianlt/pages/login/domain/LoginRepository.java b/src/main/java/de/assecutor/votianlt/pages/login/domain/LoginRepository.java index c66c79c..b302182 100644 --- a/src/main/java/de/assecutor/votianlt/pages/login/domain/LoginRepository.java +++ b/src/main/java/de/assecutor/votianlt/pages/login/domain/LoginRepository.java @@ -12,5 +12,5 @@ public interface LoginRepository extends MongoRepository { // If you don't need a total row count, Slice is better than Page. Slice findAllBy(Pageable pageable); - Optional findByEmailAndPassword(String email, String password); + Optional findByEmail(String email); } diff --git a/src/main/java/de/assecutor/votianlt/pages/login/service/LoginService.java b/src/main/java/de/assecutor/votianlt/pages/login/service/LoginService.java index 1a7168a..0f24e36 100644 --- a/src/main/java/de/assecutor/votianlt/pages/login/service/LoginService.java +++ b/src/main/java/de/assecutor/votianlt/pages/login/service/LoginService.java @@ -3,6 +3,7 @@ package de.assecutor.votianlt.pages.login.service; import com.vaadin.flow.component.notification.Notification; import de.assecutor.votianlt.model.User; import de.assecutor.votianlt.pages.login.domain.LoginRepository; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -15,18 +16,25 @@ import java.util.Optional; public class LoginService { private final LoginRepository loginRepository; + private final PasswordEncoder passwordEncoder; - LoginService(LoginRepository loginRepository, Clock clock) { + LoginService(LoginRepository loginRepository, PasswordEncoder passwordEncoder, Clock clock) { this.loginRepository = loginRepository; + this.passwordEncoder = passwordEncoder; } - public Optional findUser(String mail, String password) { - var user = loginRepository.findByEmailAndPassword(mail, password); + public Optional findUser(String email, String password) { + var userOpt = loginRepository.findByEmail(email); - if (user.isEmpty()) { - Notification.show("Login failed", 3000, Notification.Position.BOTTOM_END); + if (userOpt.isPresent()) { + User user = userOpt.get(); + // Prüfe das Passwort mit BCrypt + if (passwordEncoder.matches(password, user.getPassword())) { + return Optional.of(user); + } } - return user; + Notification.show("Login failed", 3000, Notification.Position.BOTTOM_END); + return Optional.empty(); } } diff --git a/src/main/java/de/assecutor/votianlt/pages/login/ui/view/LoginView.java b/src/main/java/de/assecutor/votianlt/pages/login/ui/view/LoginView.java index 327021b..aaa7100 100644 --- a/src/main/java/de/assecutor/votianlt/pages/login/ui/view/LoginView.java +++ b/src/main/java/de/assecutor/votianlt/pages/login/ui/view/LoginView.java @@ -3,64 +3,58 @@ package de.assecutor.votianlt.pages.login.ui.view; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; 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.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.login.LoginForm; import com.vaadin.flow.component.orderedlayout.FlexComponent; 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.Route; -import com.vaadin.flow.theme.lumo.LumoUtility; -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; +import com.vaadin.flow.server.auth.AnonymousAllowed; @Route("login") @PageTitle("Bei VotianLT anmelden") -//@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Bei VotianLT registrieren") -public class LoginView extends Main { - private final LoginService loginService; +@AnonymousAllowed +public class LoginView extends VerticalLayout implements BeforeEnterObserver { - TextField usernameField = new TextField("E-Mail-Adresse"); - 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 + private final LoginForm loginForm = new LoginForm(); + public LoginView() { + addClassName("login-view"); 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() { - var user = loginService.findUser(usernameField.getValue(), password1Field.getValue()); - - if (user.isPresent()) { - UI.getCurrent().navigate("customer"); + @Override + public void beforeEnter(BeforeEnterEvent beforeEnterEvent) { + // Zeige Fehlermeldung bei fehlgeschlagener Anmeldung + if (beforeEnterEvent.getLocation() + .getQueryParameters() + .getParameters() + .containsKey("error")) { + loginForm.setError(true); } } - - private void submit() { - Util.changeDrawerState(true); - } } diff --git a/src/main/java/de/assecutor/votianlt/pages/register/domain/RegisterRepository.java b/src/main/java/de/assecutor/votianlt/pages/register/domain/RegisterRepository.java index ffb80a8..fb5a98c 100644 --- a/src/main/java/de/assecutor/votianlt/pages/register/domain/RegisterRepository.java +++ b/src/main/java/de/assecutor/votianlt/pages/register/domain/RegisterRepository.java @@ -1,12 +1,7 @@ package de.assecutor.votianlt.pages.register.domain; -import de.assecutor.votianlt.model.User; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.mongodb.repository.MongoRepository; +import de.assecutor.votianlt.repository.UserRepository; -public interface RegisterRepository extends MongoRepository { - - // If you don't need a total row count, Slice is better than Page. - Slice findAllBy(Pageable pageable); +public interface RegisterRepository extends UserRepository { + // Erbt alle Methoden von UserRepository } diff --git a/src/main/java/de/assecutor/votianlt/pages/register/service/UserService.java b/src/main/java/de/assecutor/votianlt/pages/register/service/UserService.java new file mode 100644 index 0000000..d62b53f --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/pages/register/service/UserService.java @@ -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); + } +} diff --git a/src/main/java/de/assecutor/votianlt/pages/register/ui/view/RegisterView.java b/src/main/java/de/assecutor/votianlt/pages/register/ui/view/RegisterView.java index 2c778ff..086cd66 100644 --- a/src/main/java/de/assecutor/votianlt/pages/register/ui/view/RegisterView.java +++ b/src/main/java/de/assecutor/votianlt/pages/register/ui/view/RegisterView.java @@ -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.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.VerticalLayout; +import com.vaadin.flow.component.textfield.PasswordField; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; -import com.vaadin.flow.theme.lumo.LumoUtility; -import de.assecutor.votianlt.model.User; +import com.vaadin.flow.server.auth.AnonymousAllowed; 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; @Route("register") -@PageTitle("Bei VotianLT anmelden") -//@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Bei VotianLT registrieren") -public class RegisterView extends Main { +@PageTitle("Bei VotianLT registrieren") +@AnonymousAllowed +public class RegisterView extends VerticalLayout { private final RegisterService registerService; + private final UserService userService; - TextField mail = new TextField("E-Mail-Adresse"); - TextField password1Field = new TextField("Passwort"); - TextField password2Field = new TextField("Passwort wiederholen"); - Button submitButton = new Button("Registrieren"); + private TextField emailField; + private PasswordField passwordField; + private PasswordField confirmPasswordField; + private Button submitButton; - public RegisterView(RegisterService registerService, Clock clock) { + public RegisterView(RegisterService registerService, UserService userService, Clock clock) { this.registerService = registerService; + this.userService = userService; - // Setze den Button als primär - 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 - + // Layout-Konfiguration für vollständige Zentrierung 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() { - var mail = this.mail.getValue(); - var password = password1Field.getValue(); + var email = emailField.getValue().trim(); + var password = passwordField.getValue(); + var confirmPassword = confirmPasswordField.getValue(); - User user = new User(); - user.setEmail(mail); - user.setPassword(password); + // Validierung + if (email.isEmpty()) { + 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); + } + } } } diff --git a/src/main/java/de/assecutor/votianlt/pages/start/ui/view/StartView.java b/src/main/java/de/assecutor/votianlt/pages/start/ui/view/StartView.java index 5128679..c43d7b2 100644 --- a/src/main/java/de/assecutor/votianlt/pages/start/ui/view/StartView.java +++ b/src/main/java/de/assecutor/votianlt/pages/start/ui/view/StartView.java @@ -1,40 +1,344 @@ 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.button.Button; import com.vaadin.flow.component.button.ButtonVariant; -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.router.Menu; +import com.vaadin.flow.component.combobox.ComboBox; +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.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("") -@PageTitle("Dummy") -@Menu(order = 0, icon = "vaadin:clipboard-check", title = "DUMMY") -public class StartView extends Main { +@PageTitle("VotianLT - Willkommen") +@AnonymousAllowed +public class StartView extends VerticalLayout { - final Button registerBtn; - 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); + private final SecurityService securityService; + public StartView(SecurityService securityService) { + this.securityService = securityService; setSizeFull(); - addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN, - LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL); + setPadding(false); + 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 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 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() { UI.getCurrent().navigate("register"); } - private void login() { UI.getCurrent().navigate("login"); } + private void login() { + UI.getCurrent().navigate("login"); + } } diff --git a/src/main/java/de/assecutor/votianlt/pages/test/TestView.java b/src/main/java/de/assecutor/votianlt/pages/test/TestView.java new file mode 100644 index 0000000..ad95123 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/pages/test/TestView.java @@ -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); + } +} diff --git a/src/main/java/de/assecutor/votianlt/repository/UserRepository.java b/src/main/java/de/assecutor/votianlt/repository/UserRepository.java new file mode 100644 index 0000000..bb21ab0 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/repository/UserRepository.java @@ -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 { + + Optional findByEmail(String email); + + boolean existsByEmail(String email); + + void deleteByEmail(String email); +} diff --git a/src/main/java/de/assecutor/votianlt/security/SecurityConfig.java b/src/main/java/de/assecutor/votianlt/security/SecurityConfig.java new file mode 100644 index 0000000..3e9e8b9 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/security/SecurityConfig.java @@ -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") + ); + } +} diff --git a/src/main/java/de/assecutor/votianlt/security/SecurityService.java b/src/main/java/de/assecutor/votianlt/security/SecurityService.java new file mode 100644 index 0000000..017f5e1 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/security/SecurityService.java @@ -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 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); + } +} diff --git a/src/main/java/de/assecutor/votianlt/security/UserDetailsServiceImpl.java b/src/main/java/de/assecutor/votianlt/security/UserDetailsServiceImpl.java new file mode 100644 index 0000000..c519d73 --- /dev/null +++ b/src/main/java/de/assecutor/votianlt/security/UserDetailsServiceImpl.java @@ -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 getAuthorities(User user) { + // Basis-Rolle für alle Benutzer + return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f06ab0a..ec44326 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,4 +12,4 @@ vaadin.allowed-packages=com.vaadin,org.vaadin,de.assecutor.votianlt spring.jpa.open-in-view=false # MongoDB -spring.data.mongodb.uri=mongodb://192.168.180.28:27017/votianlt \ No newline at end of file +spring.data.mongodb.uri=mongodb://192.168.180.25:27017/votianlt \ No newline at end of file