diff --git a/pom.xml b/pom.xml
index 75290fc..5bbba22 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,7 +55,7 @@
org.springframework
spring-core
- 6.1.10
+ 6.2.0
org.slf4j
diff --git a/src/main/java/edu/group5/app/App.java b/src/main/java/edu/group5/app/App.java
index be397d8..b9d67da 100644
--- a/src/main/java/edu/group5/app/App.java
+++ b/src/main/java/edu/group5/app/App.java
@@ -5,6 +5,7 @@
import edu.group5.app.model.donation.DonationRepository;
import edu.group5.app.model.donation.DonationService;
import edu.group5.app.model.organization.OrganizationRepository;
+import edu.group5.app.model.organization.OrganizationScraper;
import edu.group5.app.model.organization.OrganizationService;
import edu.group5.app.model.user.UserRepository;
import edu.group5.app.model.user.UserService;
@@ -62,16 +63,16 @@ public void init() {
System.err.println("Failed to load organization data: " + e.getMessage());
}
- // Create repositories with fetched data
+ // Create scraper and repositories with fetched data
+ OrganizationScraper orgScraper = new OrganizationScraper();
this.userRepository = new UserRepository(userData);
this.donationRepository = new DonationRepository(donationData);
- OrganizationRepository organizationRepository = new OrganizationRepository(organizationData);
+ OrganizationRepository organizationRepository = new OrganizationRepository(organizationData, orgScraper);
// Create services (backend wiring)
UserService userService = new UserService(this.userRepository);
DonationService donationService = new DonationService(this.donationRepository, organizationRepository);
- OrganizationService organizationService = new OrganizationService(organizationRepository);
-
+ OrganizationService organizationService = new OrganizationService(organizationRepository, orgScraper);
this.root = new BorderPane();
this.appState = new AppState();
this.nav = new NavigationController(root, appState, userService, donationService, organizationService);
diff --git a/src/main/java/edu/group5/app/control/AuthController.java b/src/main/java/edu/group5/app/control/AuthController.java
index 7e22cba..249a436 100644
--- a/src/main/java/edu/group5/app/control/AuthController.java
+++ b/src/main/java/edu/group5/app/control/AuthController.java
@@ -4,7 +4,13 @@
import edu.group5.app.model.user.User;
import edu.group5.app.model.user.UserService;
import edu.group5.app.view.loginpage.LoginPageView;
-import edu.group5.app.view.loginpage.SignInPageView;
+import edu.group5.app.view.loginpage.SignUpPageView;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+
+import java.util.Arrays;
+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
@@ -49,7 +55,7 @@ public AuthController(AppState appState, NavigationController nav, UserService u
* @param email the user's email
* @param passwordChars the user's password
*/
- public void handleSignUp(SignInPageView view, String firstName, String lastName, String email, char[] passwordChars) {
+ public void handleSignUp(SignUpPageView view, String firstName, String lastName, String email, char[] passwordChars) {
if (firstName == null || firstName.trim().isEmpty() ||
lastName == null || lastName.trim().isEmpty() ||
email == null || email.trim().isEmpty() ||
@@ -58,15 +64,28 @@ public void handleSignUp(SignInPageView view, String firstName, String lastName,
return;
}
- String password = new String(passwordChars);
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
- String hashedPassword = encoder.encode(password);
- boolean success = userService.registerUser(
- "Customer", firstName, lastName, email, hashedPassword);
+ // Clears password char array after creating a hash.
+ String hashedPassword = encoder.encode(new String(passwordChars));
+ for (int i = 0; i < passwordChars.length; i++) {
+ passwordChars[0] = '0';
+ }
+
+ Alert privacyPolicy = new Alert(Alert.AlertType.CONFIRMATION);
+ privacyPolicy.setTitle("Accept Privacy Policy");
+ privacyPolicy.setHeaderText("Accept Privacy Policy");
+ privacyPolicy.setContentText(
+ "Your user information like:\n" +
+ "Name and email—as well as donations tied to your account—will be saved locally on your machine.\n" +
+ "By creating an account, you accept the right of our app to store this information.");
- if (success) {
- User user = userService.getUserByEmail(email);
+ if (privacyPolicy.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) {
+ boolean success = userService.registerUser(
+ "Customer", firstName, lastName, email, hashedPassword);
+
+ if (success) {
+ User user = userService.getUserByEmail(email);
appState.setCurrentUser(user);
nav.showHomePage();
@@ -74,6 +93,7 @@ public void handleSignUp(SignInPageView view, String firstName, String lastName,
view.showError("Registration failed. Email may already be in use.");
}
}
+}
/**
* Handles the login of a {@link User}.
@@ -120,4 +140,4 @@ public void handleLogout() {
appState.setCurrentPaymentMethod(null);
nav.showLoginPage();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/edu/group5/app/control/DonationController.java b/src/main/java/edu/group5/app/control/DonationController.java
index e6fc80f..8a8ec79 100644
--- a/src/main/java/edu/group5/app/control/DonationController.java
+++ b/src/main/java/edu/group5/app/control/DonationController.java
@@ -12,6 +12,9 @@
import java.util.Map;
import java.util.Set;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+
/**
* Controller responsible for donation-related operations.
*
@@ -75,27 +78,58 @@ public Set getUniqueOrganizationIDs() {
*
*
*/
- public void handleDonate() {
- // Get session data from MainController
+ public void requestDonationConfirmation() {
+ // Get session data
User currentUser = appState.getCurrentUser();
Organization currentOrg = appState.getCurrentOrganization();
BigDecimal amount = appState.getCurrentDonationAmount();
String paymentMethod = appState.getCurrentPaymentMethod();
+ // Validate before showing dialog
if (currentUser == null) {
- System.err.println("Error: No user logged in");
+ showError("Error: No user logged in");
return;
}
- if (!(currentUser instanceof Customer customer)) {
- System.err.println("Error: Only customers can donate");
+ if (!(currentUser instanceof Customer)) {
+ showError("Error: Only customers can donate");
return;
}
if (currentOrg == null) {
- System.err.println("Error: No organization selected");
+ showError("Error: No organization selected");
return;
}
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
- System.err.println("Error: Invalid donation amount");
+ showError("Please select a donation amount first");
+ return;
+ }
+
+ // Show confirmation dialog
+ Alert confirmDialog = new Alert(Alert.AlertType.CONFIRMATION);
+ confirmDialog.setTitle("Confirm Donation");
+ confirmDialog.setHeaderText("Confirm Your Donation");
+ confirmDialog.setContentText(
+ "Organization: " + currentOrg.name() + "\n" +
+ "Amount: " + amount + " kr\n" +
+ "Payment Method: " + paymentMethod + "\n\n" +
+ "Are you sure you want to proceed?"
+ );
+
+ // If user clicks OK, process donation
+ if (confirmDialog.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) {
+ handleDonate();
+ }
+ // If Cancel, dialog just closes and nothing happens
+ }
+
+ private void handleDonate() {
+ // This now only handles the actual donation processing
+ User currentUser = appState.getCurrentUser();
+ Organization currentOrg = appState.getCurrentOrganization();
+ BigDecimal amount = appState.getCurrentDonationAmount();
+ String paymentMethod = appState.getCurrentPaymentMethod();
+
+ if (!(currentUser instanceof Customer customer)) {
+ System.err.println("Error: Only customers can donate");
return;
}
if (paymentMethod == null) {
@@ -123,4 +157,12 @@ public void handleDonate() {
// Navigate to payment complete
nav.showPaymentCompletePage();
}
+
+ private void showError(String message) {
+ Alert errorAlert = new Alert(Alert.AlertType.WARNING);
+ errorAlert.setTitle("Donation Error");
+ errorAlert.setHeaderText("Cannot Process Donation");
+ errorAlert.setContentText(message);
+ errorAlert.showAndWait();
+ }
}
diff --git a/src/main/java/edu/group5/app/control/NavigationController.java b/src/main/java/edu/group5/app/control/NavigationController.java
index 2d1e6ab..7bfbc8c 100644
--- a/src/main/java/edu/group5/app/control/NavigationController.java
+++ b/src/main/java/edu/group5/app/control/NavigationController.java
@@ -11,7 +11,7 @@
import edu.group5.app.view.homepage.HomePageView;
import edu.group5.app.view.loginpage.LoginHeader;
import edu.group5.app.view.loginpage.LoginPageView;
-import edu.group5.app.view.loginpage.SignInPageView;
+import edu.group5.app.view.loginpage.SignUpPageView;
import edu.group5.app.view.organizationpage.OrganizationPageView;
import edu.group5.app.view.userpage.UserPageView;
import javafx.scene.layout.BorderPane;
@@ -52,9 +52,9 @@ public void showLoginPage() {
root.setCenter(new LoginPageView(appState, this, authController));
}
- public void showSignInPage() {
+ public void showSignUpPage() {
root.setTop(loginHeader);
- root.setCenter(new SignInPageView(appState, this, authController));
+ root.setCenter(new SignUpPageView(appState, this, authController));
}
public void showPaymentCompletePage() {
diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
index cc0a6b1..61155b7 100644
--- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
+++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
@@ -11,25 +11,33 @@
* Repository class for managing Organization entities. It provides methods to retrieve trusted organizations,
* find organizations by their organization number or name, and initializes the repository with input data.
* The repository uses a HashMap to store Organization objects for efficient retrieval based on their organization number.
- * Handles the business logic associated with organizations
+ * Delegates web scraping to OrganizationScraper for separation of concerns.
*/
public class OrganizationRepository extends Repository {
private final HashMap grandMap;
+ private final OrganizationScraper scraper;
/**
- * Initializes the repository with the given input data, c
- * onverting it into Organization objects and storing them in a map for efficient retrieval.
- * The input is expected to be an array of objects, where each object contains
+ * Initializes the repository with the given input data and scraper.
+ * Converts input into Organization objects and stores them in a map for efficient retrieval.
+ * The input is expected to be an array of objects, where each object contains
* the necessary information to create an Organization.
+ *
* @param input the input data used to populate the repository, must not be null
- * @throws IllegalArgumentException if the input is null
+ * @param scraper the OrganizationScraper to use for fetching web data, must not be null
+ * @throws IllegalArgumentException if input or scraper is null
*/
- public OrganizationRepository(Object[] input) {
- super(new HashMap<>());
- grandMap = new HashMap<>();
+ public OrganizationRepository(Object[] input, OrganizationScraper scraper) {
+ super(new HashMap<>());
+ this.grandMap = new HashMap<>();
if (input == null) {
throw new IllegalArgumentException("The input cannot be null");
}
+ if (scraper == null) {
+ throw new IllegalArgumentException("The scraper cannot be null");
+ }
+ this.scraper = scraper;
+
ObjectMapper mapper = new ObjectMapper();
for (Object obj : input) {
@@ -42,7 +50,8 @@ public OrganizationRepository(Object[] input) {
boolean trusted = "approved".equalsIgnoreCase((String) contentMap.get("status"));
String websiteURL = (String) contentMap.get("url");
boolean isPreApproved = Boolean.TRUE.equals(contentMap.get("is_pre_approved"));
- String description = "Information about " + name;
+ String description = scraper.fetchDescription(websiteURL);
+ description = description != null ? description : "Information about " + name;
Organization org = new Organization(orgNumber, name, trusted, websiteURL, isPreApproved, description, null);
grandMap.put(org.orgNumber(), org);
diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationScraper.java b/src/main/java/edu/group5/app/model/organization/OrganizationScraper.java
new file mode 100644
index 0000000..6a8c230
--- /dev/null
+++ b/src/main/java/edu/group5/app/model/organization/OrganizationScraper.java
@@ -0,0 +1,115 @@
+package edu.group5.app.model.organization;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.TextNode;
+import org.jsoup.select.Elements;
+
+import java.util.stream.Collectors;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Handles web scraping of organization information from Innsamlingskontrollen.
+ * Responsible for fetching logos and descriptions from organization pages.
+ * All results are cached to avoid redundant network requests.
+ */
+public class OrganizationScraper {
+ private final Map logoCache = new HashMap<>();
+ private final Map descriptionCache = new HashMap<>();
+
+ /**
+ * Fetches the description for the given URL by scraping all text content
+ * inside {@code