+
+
+## Project descriptionπ»
+
+Help-Me-Help (HmH) is a Java desktop application, developed using Maven, designed to help users donate money to legitimate charitable organizations and emergency relief initiatives.
+The application fetches verified organization data from Innsamlingskontrollen (IK), a non-profit foundation that verifies fundraising activities.
+Users can create profiles, track their donation history, and browse organizations by status (approved/pending).
+The application prioritizes security, data persistence, and an intuitive user experience following Don Norman's interaction design principles.
+
+## Key Features
+
+- User authentication and profile management
+- Browse verified and pending organizations with descriptions and logos
+- Make donations to organizations
+- View donation history
+- Data persistence using H2 database
+- Web scraping of organization information (descriptions, logos) from external sources
+
+## Project structure π
+
+---
+The project follows a standard Maven layout and is organized into clearly separated packages according to responsibility-driven design (RDD).
+All source files are stored under the `src` directory.
+
+### Main Package Structure (`src/main`)
+
+
+
+### π¦ Package Responsibilities
+
+#### Models: Business logic and data entities
+
+- `Organization`: Represents a charity/relief organization with status, logo, description
+- `User` & `Customer`: User profiles with authentication
+- `Donation`: Records of user donations
+- Services and Repositories implement the business logic and data access layers
+
+#### Controller: Bridge between UI and business logic
+
+- `AuthController`: Handles user login/registration with password hashing (BCrypt)
+- `OrganizationController`: Manages organization data retrieval and caching
+- `DonationController`: Processes donations and updates user history
+- `NavigationController`: Coordinates page navigation
+
+#### View: JavaFX UI components
+
+- Login/signup pages with form validation
+- Organization browsing with filtering (approved/pending)
+- Donation flow with payment confirmation
+- User profile and donation history
+
+#### Utils: Helper functions
+
+- `ParameterValidator`: Validates null, empty, and positive values
+
+### JUnit Tests (`src/test`)
+
+The JUnit tests are stored under `src/test` and mirror the main package structure. These tests cover both positive and negative test of all classes (except `App.java` and UI classes) and their methods ensuring program reliability according to the specification given in the portofolie project descriptions
+
+
+### Maven Layout
+
+The project uses the standard Maven directory structure, which ensures:
+
+- clean separation of source and test files
+- compatibility with IDEs such as IntelliJ, VS Code, and Eclipse
+- maintainability and easy future extensions (e.g., persistence or additional views)
+
+## Link to repositoryπ
+
+
+
+## How to run the projectπ
+
+**Requirements:**
+
+- Java JDK 25
+- Maven
+- JavaFX SDK 25.0.1--
+
+**Run With Maven:** (Windows + Mac + Linux)
+
+1. **Download and Unzip Project:**
+ Download project zip from the repository.
+
+2. **Navigate to Project Folder:**
+ Navigate to project folder in the terminal.
+
+ ```bash
+ cd path/to/project/ (linux + mac)
+ cd path\to\project\ (windows)
+ ```
+
+3. **Run the Application:**
+ Start the program by running the main class:
+
+ ```bash
+ mvn javafx:run
+ ```
+
+**Run From JAR: (Windows)**
+
+1. Download Project JAR:
+ Go to repository and download the JAR from the jar release.
+
+2. Run the JAR in Terminal:
+
+ ```bash
+ java --module-path "path\to\javafx\sdk" --add-modules javafx.controls -jar path\to\jar.jar
+
+
+- Input: User interactions (login, organization selection, donation amount)
+- Output: JavaFX UI displaying organizations, user profiles, donation confirmations
+
+1. **Expected behavior:**
+
+
+The program allows the user to:
+
+- Login/signup with validation
+- Browse approved organizations from Innsamlingskontrollen API
+- View organization details (description, logo, status)
+- Complete donation workflow
+- View donation history in user profile
+- Graceful error handling with user-friendly messages
+
+---
+
+## How to run the tests π§ͺ
+
+This project uses JUnit 5 for unit testing.
+All test classes mirror the main package structure and are stored in `src/test`
+
+- ### Open the Project
+
+ Open your IDE and select **File > Open Folder**, navigating to the root folder of the project (containing `pom.xml`).
+
+- ### Run all tests
+
+ To execute the full test suite, run:
+
+ ```bash
+ mvn clean test
+
+This command:
+
+ 1. Cleans old build files
+ 2. Compiles the main source code
+ 3. Compiles the tests
+ 4. Runs all JUnit tests
+
+---
+
+### Viewing test results
+
+After the tests finish, Maven creates detailed reports here:
+`target/surefire-reports/`
+
+Each report includes:
+
+ 1. Test class summaries
+ 2. Stack traces for any failures
+ 3. Execution times
+ 4. Running tests in an IDE
+
+## Accessing the javadocs
+The javadoc can be generated and accessed using the following commands.
+
+1. Generate the Javadoc HTML
+ ```bash
+ mvn javadoc:javadoc
+ ```
+ ```
+ ```
+
+2. Open the HTML file
+ open ```target/reports/apidocs/index.html```
+
+## References π
+
+For more references and project details, kindly refer yourself to the project report and Wiki pages
+
+- **GitHub Wiki**: [GitHub Wiki](https://git.ntnu.no/Group-5/Help-Me-Help/wiki)
+
+- **Innsamlingskontrollen API**: [Innsamlingskontrollen](https://app.innsamlingskontrollen.no/api/public/v1/all)
+
+---
diff --git a/pom.xml b/pom.xml
index f9a8b88..5bbba22 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,7 +55,7 @@
org.springframeworkspring-core
- 6.1.10
+ 6.2.0org.slf4j
@@ -68,6 +68,11 @@
2.2.224runtime
+
+ org.jsoup
+ jsoup
+ 1.17.2
+
diff --git a/src/main/java/edu/group5/app/App.java b/src/main/java/edu/group5/app/App.java
index d657fb8..3aea332 100644
--- a/src/main/java/edu/group5/app/App.java
+++ b/src/main/java/edu/group5/app/App.java
@@ -1,19 +1,21 @@
package edu.group5.app;
-import edu.group5.app.control.MainController;
-import edu.group5.app.control.wrapper.DbWrapper;
-import edu.group5.app.control.wrapper.OrgApiWrapper;
-import edu.group5.app.model.donation.Donation;
+import edu.group5.app.control.NavigationController;
+import edu.group5.app.model.AppState;
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.User;
import edu.group5.app.model.user.UserRepository;
import edu.group5.app.model.user.UserService;
+import edu.group5.app.model.wrapper.DbWrapper;
+import edu.group5.app.model.wrapper.OrgApiWrapper;
+import edu.group5.app.view.aboutuspage.AboutUsView;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
+import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.util.List;
@@ -28,9 +30,13 @@ public class App extends Application {
DbWrapper dbWrapper;
UserRepository userRepository;
DonationRepository donationRepository;
+
+ BorderPane root;
+ AppState appState;
+ NavigationController nav;
+
private Logger logger;
- private MainController controller;
- private Scene scene;
+ static int MAX_RETRIES = 3;
@Override
public void init() {
@@ -40,8 +46,14 @@ public void init() {
this.dbWrapper = new DbWrapper(false);
OrgApiWrapper orgApiWrapper = new OrgApiWrapper("https://app.innsamlingskontrollen.no/api/public/v1/all");
- while (!dbWrapper.connect()) {
+ int retries = 0;
+ while (!dbWrapper.connect() && retries < MAX_RETRIES) {
this.logger.warning("Failed to connect to database");
+ retries++;
+ }
+ if (retries == MAX_RETRIES) {
+ this.logger.severe("Failed to connect to database after " + MAX_RETRIES + " attempts");
+ throw new RuntimeException("Failed to connect to database");
}
// Load data from database
@@ -59,28 +71,29 @@ 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);
-
- this.controller = new MainController(userService, donationService, organizationService);
-
- this.scene = controller.getMainView().getScene();
+ OrganizationService organizationService = new OrganizationService(organizationRepository, orgScraper);
+ this.root = new BorderPane();
+ this.appState = new AppState();
+ this.nav = new NavigationController(root, appState, userService, donationService, organizationService, this.getHostServices());
}
@Override
public void start(Stage stage) {
- this.controller.showLoginPage();
+ this.nav.showLoginPage();
+ Scene scene = new Scene(root, 1280, 720);
stage.getIcons().add(new Image(getClass().getResource("/header/images/hmh-logo.png").toExternalForm()));
stage.setTitle("Help-Me-Help");
- stage.setScene(this.scene);
+ stage.setScene(scene);
stage.show();
}
diff --git a/src/main/java/edu/group5/app/control/AuthController.java b/src/main/java/edu/group5/app/control/AuthController.java
new file mode 100644
index 0000000..d3d4ebe
--- /dev/null
+++ b/src/main/java/edu/group5/app/control/AuthController.java
@@ -0,0 +1,233 @@
+package edu.group5.app.control;
+
+import edu.group5.app.model.AppState;
+import edu.group5.app.model.user.User;
+import edu.group5.app.model.user.UserService;
+import edu.group5.app.utils.ParameterValidator;
+import edu.group5.app.view.loginpage.LoginPageView;
+import edu.group5.app.view.loginpage.SignUpPageView;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+/**
+ * Controller responsible for authentication-related operations.
+ *
+ *
Coordinates between {@link AppState}, {@link NavigationController}
+ * and {@link UserService} to:
+ *
+ *
Sign-up a new user
+ *
Login in a user
+ *
Logout a user
+ *
+ *
+ */
+public class AuthController {
+ private final AppState appState;
+ private final NavigationController nav;
+ private final UserService userService;
+
+ public AuthController(AppState appState, NavigationController nav, UserService userService) {
+ ParameterValidator.objectChecker(appState, "AppState");
+ ParameterValidator.objectChecker(nav, "NavigationController");
+ ParameterValidator.objectChecker(userService, "UserService");
+ this.appState = appState;
+ this.nav = nav;
+ this.userService = userService;
+ }
+
+
+ /**
+ * Sets the current logged-in user.
+ * @param user the user to set as current
+ */
+ public void setCurrentUser(User user) {
+ appState.setCurrentUser(user);
+ }
+
+ /**
+ * Gets the current logged-in user.
+ * @return the current user, or null if no user logged in
+ */
+ public User getCurrentUser() {
+ return appState.getCurrentUser();
+ }
+
+ /**
+ * Handles the registration of a {@link User}.
+ *
+ *
+ *
Validates the firstname, lastname, email and password fields
+ *
Encrypts the password
+ *
Invokes {@link UserService#registerUser(String, String, String, String, String)} to register the user
+ *
+ *
+ *
If the registration is successful, the user is stored in {@link AppState} and
+ * the application navigates to the home page. Otherwise, an error message
+ * is displayed in the provided view.
+ *
+ * @param view the view used to display feedback to the user
+ * @param firstName the user's first name
+ * @param lastName the user's last name
+ * @param email the user's email
+ * @param passwordChars the user's password
+ */
+ 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()
+ || passwordChars == null || passwordChars.length == 0) {
+ view.showError("All fields are required");
+ return;
+ }
+
+ // Checks if any input is too long.
+ if (firstName.length() > 32 || lastName.length() > 32
+ || email.length() > 32 || passwordChars.length > 72) {
+
+ HashMap> fields = new HashMap>();
+ List fields32 = new ArrayList();
+ List fields72 = new ArrayList();
+ fields.put("32", fields32);
+ fields.put("72", fields72);
+
+ if (firstName.length() > 32) {
+ fields32.add("First Name");
+ }
+ if (lastName.length() > 32) {
+ fields32.add("Last Name");
+ }
+ if (email.length() > 32) {
+ fields32.add("Email");
+ }
+ if (passwordChars.length > 72) {
+ fields72.add("Password");
+ }
+
+ int length32 = fields.get("32").size();
+ int length72 = fields.get("72").size();
+
+ String string32 = "";
+ if (length32 > 0) {
+ if (length32 > 1) {
+ for (int i = 0; i < length32; i++) {
+ if (i == length32 - 1) {
+ string32 += String.format("and %s", fields.get("32").get(i));
+ } else {
+ string32 += String.format("%s, ", fields.get("32").get(i));
+ }
+ }
+ string32 = string32 + " must have lengths of 32 characters.\n";
+ } else {
+ string32 = fields.get("32").getFirst() + " must have a length of 32 characters.\n";
+ }
+ }
+
+ String string72 = "";
+ if (length72 > 0) {
+ if (length72 > 1) {
+ for (int i = 0; i < length72; i++) {
+ if (i == length72 - 1) {
+ string72 += String.format("and %s", fields.get("72").get(i));
+ } else {
+ string72 += String.format("%s, ", fields.get("72").get(i));
+ }
+ }
+ string72 = string72 + " must have lengths of 72 characters.\n";
+ } else {
+ string72 = fields.get("72").getFirst()
+ + " must have a length of 72 characters.\n";
+ }
+ }
+
+ view.showError(string32 + string72 + "Try again.");
+ return;
+ }
+
+ // Privacy policy pop-up.
+ 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"
+ + "This information is only used to create your account,"
+ + "and no data will be sold to third parties.\n"
+ + "By creating an account,"
+ + "you accept the right of our app to store this information on your computer.");
+
+ BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+ // Clears password char array after creating a hash.
+ String hashedPassword = encoder.encode(new String(passwordChars));
+ for (int i = 0; i < passwordChars.length; i++) {
+ passwordChars[i] = '\u0000';
+ }
+
+ 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();
+ } else {
+ view.showError("Registration failed. Email may already be in use.");
+ }
+ }
+ }
+
+
+ /**
+ * Handles the login of a {@link User}.
+ *
+ *
+ *
Validates the email and password of the user
+ *
Invokes {@link UserService#login(String, char[])} to login in the user
+ *
+ *
+ * If the login is successful, the user is stored in {@link AppState} and the
+ * application navigates to the home page. Otherwise, an error message is
+ * displayed within the provided view.
+ *
+ * @param view the view used to display feedback to the user
+ * @param email the user's email
+ * @param passwordChars the user's password
+ */
+ public void handleLogin(LoginPageView view, String email, char[] passwordChars) {
+ if (email == null || email.trim().isEmpty() || passwordChars == null || passwordChars.length == 0) {
+ view.showError("Email and password are required");
+ return;
+ }
+
+ User user = userService.login(email, passwordChars);
+
+ if (user != null) {
+ appState.setCurrentUser(user);
+ nav.showHomePage();
+ } else {
+ view.showError("Invalid email or password");
+ }
+ }
+
+ /**
+ * Handles the logout of a {@link User}.
+ *
+ *
Clears states in {@link AppState} and the application
+ * navigates to the login page.
+ * Coordinates between {@link AppState}, {@link DonationService}
+ * and {@link NavigationController} to:
+ *
+ *
Retrieve donation data for the current user
+ *
Process new donations
+ *
Handle navigation after donation completion
+ *
+ *
+ */
+public class DonationController {
+ private final AppState appState;
+ private final NavigationController nav;
+ private final DonationService service;
+
+ public DonationController(AppState appState, NavigationController nav, DonationService service) {
+ ParameterValidator.objectChecker(appState, "AppState");
+ ParameterValidator.objectChecker(nav, "NavigationController");
+ ParameterValidator.objectChecker(service, "DonationService");
+ this.appState = appState;
+ this.nav = nav;
+ this.service = service;
+ }
+
+ /**
+ * Sets the current donation amount.
+ * @param amount the amount to donate
+ */
+ public void setDonationAmount(BigDecimal amount) {
+ appState.setCurrentDonationAmount(amount);
+ }
+
+ /**
+ * Gets the current donation amount.
+ * @return the donation amount
+ */
+ public BigDecimal getDonationAmount() {
+ return appState.getCurrentDonationAmount();
+ }
+
+ /**
+ * Sets the current payment method.
+ * @param paymentMethod the payment method
+ */
+ public void setPaymentMethod(String paymentMethod) {
+ appState.setCurrentPaymentMethod(paymentMethod);
+ }
+
+ /**
+ * Gets the current payment method.
+ * @return the payment method
+ */
+ public String getPaymentMethod() {
+ return appState.getCurrentPaymentMethod();
+ }
+
+ /**
+ * Retrieves all donations made by a specific user.
+ *
+ * @param userId the ID of the user
+ * @return a map of donations.
+ */
+ public Map getUserDonations(int userId) {
+ ParameterValidator.intChecker(userId, "User ID");
+ return service.getUserDonations(userId);
+ }
+
+ /**
+ * Returns a set of unique organization IDs that the current user
+ * has donated to.
+ *
+ * @return a set of organization IDs
+ */
+ public Set getUniqueOrganizationIDs() {
+ User currentUser = appState.getCurrentUser();
+ if (currentUser == null) {
+ throw new IllegalStateException("No user logged in");
+ }
+ Map userDonations = getUserDonations(currentUser.getUserId());
+
+ Set uniqueOrganizations = new HashSet<>();
+ for (Donation donation : userDonations.values()) {
+ uniqueOrganizations.add(donation.organizationId());
+ }
+
+ return uniqueOrganizations;
+ }
+
+ /**
+ * Processes a donation using data stored in {@link AppState}.
+ *
+ *
+ *
+ *
Validates the current user, organization, amount and payment method
+ *
Invokes {@link DonationService#donate(Customer, int, BigDecimal, String)} to create the donation
+ *
Clears temporary donation state
+ *
Navigates to the payment complete view
+ *
+ *
+ */
+ 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) {
+ showError("Error: No user logged in");
+ return;
+ }
+ if (!(currentUser instanceof Customer)) {
+ showError("Error: Only customers can donate");
+ return;
+ }
+ if (currentOrg == null) {
+ showError("Error: No organization selected");
+ return;
+ }
+ if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+ 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) {
+ System.out.println("Error: Invalid payment method");
+ return;
+ }
+
+ // Prevents donations that are too complex from being made
+ if (amount.stripTrailingZeros().precision() > 32 || amount.stripTrailingZeros().scale() > 16) {
+ this.showError("The number is too complex, please donate a smaller or less precise number");
+ return;
+ }
+
+ // Create donation via service
+ boolean success = service.donate(
+ customer,
+ currentOrg.orgNumber(),
+ amount,
+ paymentMethod
+ );
+
+ if (success) {
+ System.out.println("Donation created: "
+ + amount + " kr to " + currentOrg.name()
+ + ", with payment method: " + paymentMethod);
+ } else {
+ System.err.println("Failed to create donation");
+ }
+
+ // Clear donation session state
+ appState.setCurrentDonationAmount(null);
+ // Clear org
+ appState.setCurrentOrganization(null);
+ // Clear payment method
+ appState.setCurrentPaymentMethod(null);
+
+ // Navigate to payment complete
+ nav.showPaymentCompletePage();
+ }
+
+ private void showError(String message) {
+ ParameterValidator.stringChecker(message, "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/HeaderController.java b/src/main/java/edu/group5/app/control/HeaderController.java
deleted file mode 100644
index 0a1e424..0000000
--- a/src/main/java/edu/group5/app/control/HeaderController.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package edu.group5.app.control;
-
-public class HeaderController {
- private final MainController controller;
-
- public HeaderController(MainController controller) {
- this.controller = controller;
- }
-
- public void handleHomeBtn() {
- System.out.println("Home button pressed");
- controller.showHomePage();
- }
-
- public void handleCausesBtn() {
- System.out.println("Causes button pressed");
- controller.showBrowsePage();
- }
-
- public void handleAboutBtn() {
- System.out.println("About button pressed");
- }
-
- public void handleProfileBtn() {
- System.out.println("profileSection");
- controller.showUserPage();
- }
-}
diff --git a/src/main/java/edu/group5/app/control/HomePageController.java b/src/main/java/edu/group5/app/control/HomePageController.java
deleted file mode 100644
index 10d3fbf..0000000
--- a/src/main/java/edu/group5/app/control/HomePageController.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package edu.group5.app.control;
-
-public class HomePageController {
- private final MainController controller;
-
- public HomePageController(MainController controller) {
- this.controller = controller;
- }
-
- public void handleDonateToACauseBtn() {
- System.out.println("Donate to a cause button pressed");
- controller.showBrowsePage();
- }
-
- public void handleAboutUsBtn() {
- System.out.println("About us button pressed");
- controller.showAboutUsPage();
- }
-}
diff --git a/src/main/java/edu/group5/app/control/LoginPageController.java b/src/main/java/edu/group5/app/control/LoginPageController.java
deleted file mode 100644
index b28f191..0000000
--- a/src/main/java/edu/group5/app/control/LoginPageController.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package edu.group5.app.control;
-
-import edu.group5.app.model.user.User;
-import edu.group5.app.model.user.UserService;
-import edu.group5.app.view.loginpage.LoginPageView;
-
-public class LoginPageController {
- private final MainController controller;
- private final UserService userService;
- private LoginPageView view;
-
- public LoginPageController(MainController controller, UserService userService) {
- this.controller = controller;
- this.userService = userService;
- }
-
- public void setView(LoginPageView view) {
- this.view = view;
- }
-
- public void handleLoginBtn() {
- String email = view.getEmail();
- char[] passwordChars = view.getPassword();
-
- if (email == null || email.trim().isEmpty() || passwordChars == null || passwordChars.length == 0) {
- view.showError("Email and password are required");
- return;
- }
-
- User user = userService.login(email, passwordChars);
-
- if (user != null) {
- controller.setCurrentUser(user);
- controller.showHomePage();
- } else {
- view.showError("Invalid email or password");
- }
- }
-
- public void handleRegisterBtn() {
- System.out.println("Sign in button pressed");
- controller.showSignInPage();
- }
-}
diff --git a/src/main/java/edu/group5/app/control/MainController.java b/src/main/java/edu/group5/app/control/MainController.java
deleted file mode 100644
index 2b75487..0000000
--- a/src/main/java/edu/group5/app/control/MainController.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package edu.group5.app.control;
-
-import edu.group5.app.control.donationpage.DonationPageController;
-import edu.group5.app.model.donation.DonationService;
-import edu.group5.app.model.organization.Organization;
-import edu.group5.app.model.organization.OrganizationService;
-import edu.group5.app.model.user.User;
-import edu.group5.app.model.user.UserService;
-import edu.group5.app.view.MainView;
-import edu.group5.app.view.donationpage.DonationPageView;
-
-import java.math.BigDecimal;
-
-public class MainController {
- private final MainView view;
- private final HeaderController headerController;
- private final HomePageController homePageController;
- private final BrowsePageController browsePageController;
- private final BrowseCardController browseCardController;
- private final OrganizationPageController organizationPageController;
- private final DonationPageController donationPageController;
- private final UserService userService;
- private final DonationService donationService;
- private final OrganizationService organizationService;
- private User currentUser;
- private Organization currentOrganization;
- private BigDecimal currentDonationAmount;
-
- public MainController(UserService userService, DonationService donationService,
- OrganizationService organizationService) {
- this.userService = userService;
- this.donationService = donationService;
- this.organizationService = organizationService;
-
- this.view = new MainView(this, userService);
- this.headerController = new HeaderController(this);
- this.homePageController = new HomePageController(this);
- this.browsePageController = new BrowsePageController(this);
- this.browseCardController = new BrowseCardController(this);
- this.organizationPageController = new OrganizationPageController(this);
- this.donationPageController = new DonationPageController(this);
- }
-
- public UserService getUserService() {
- return userService;
- }
-
- public DonationService getDonationService() {
- return donationService;
- }
-
- public OrganizationService getOrganizationService() {
- return organizationService;
- }
-
- public void setCurrentUser(User user) {
- this.currentUser = user;
- }
-
- public User getCurrentUser() {
- return this.currentUser;
- }
-
- public void setCurrentOrganization(Organization organization) {
- this.currentOrganization = organization;
- }
-
- public Organization getCurrentOrganization() {
- return this.currentOrganization;
- }
-
- public void setCurrentDonationAmount(BigDecimal amount) {
- this.currentDonationAmount = amount;
- }
-
- public BigDecimal getCurrentDonationAmount() {
- return this.currentDonationAmount;
- }
-
- public void logout() {
- currentUser = null;
- currentOrganization = null;
- currentDonationAmount = null;
- showLoginPage();
- }
-
- public MainView getMainView() {
- return view;
- }
-
- public void showHomePage() {
- view.showHomePage(homePageController, headerController);
- }
-
- public void showLoginPage() {
- view.showLoginPage();
- }
- public void showSignInPage() {
- view.showSignInPage();
- }
- public void showPaymentCompletePage() {
- view.showPaymentCompletePage();
- }
- public void showBrowsePage() {
- view.showBrowsePage(browsePageController, browseCardController, headerController);
- }
-
- public void showOrganizationPage() {
- view.showOrganizationPage(organizationPageController, headerController);
- }
-
- public void showDonationPage() {
- view.showDonationPage();
- }
-
- public void showAboutUsPage() {}
-
- public void showUserPage() {
- view.showUserPage();
- }
-}
diff --git a/src/main/java/edu/group5/app/control/NavigationController.java b/src/main/java/edu/group5/app/control/NavigationController.java
new file mode 100644
index 0000000..8bbbbb3
--- /dev/null
+++ b/src/main/java/edu/group5/app/control/NavigationController.java
@@ -0,0 +1,150 @@
+package edu.group5.app.control;
+
+import edu.group5.app.model.AppState;
+import edu.group5.app.model.donation.DonationService;
+import edu.group5.app.model.organization.OrganizationService;
+import edu.group5.app.model.user.UserService;
+import edu.group5.app.utils.ParameterValidator;
+import edu.group5.app.view.Header;
+import edu.group5.app.view.aboutuspage.AboutUsView;
+import edu.group5.app.view.causespage.CausesPageView;
+import edu.group5.app.view.donationpage.DonationPageView;
+import edu.group5.app.view.donationpage.PaymentCompletePageView;
+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.SignUpPageView;
+import edu.group5.app.view.organizationpage.OrganizationPageView;
+import edu.group5.app.view.userpage.UserPageView;
+import javafx.application.HostServices;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.VBox;
+
+/**
+ * Controller responsible for handling navigation between different pages of the application.
+ * Coordinates between {@link AppState}, {@link AuthController}, {@link DonationController},
+ * {@link OrganizationController} and the various views to manage page transitions and state updates.
+ *
+ * Provides methods to navigate to the home page, login page, sign-up page, causes page,
+ * organization page, donation page, payment complete page and user profile page. Each method updates the
+ * top header and center content of the main application layout accordingly.
+ *
+ */
+public class NavigationController {
+ private final BorderPane root;
+ private final Header header;
+ private final LoginHeader loginHeader;
+
+ private final AppState appState;
+ private final HostServices hostServices;
+
+ private final AuthController authController;
+ private final DonationController donationController;
+ private final OrganizationController organizationController;
+
+ public NavigationController(BorderPane root, AppState appState, UserService userService,
+ DonationService donationService, OrganizationService organizationService, HostServices hostServices) {
+ ParameterValidator.objectChecker(root, "Root BorderPane");
+ ParameterValidator.objectChecker(appState, "AppState");
+ ParameterValidator.objectChecker(userService, "UserService");
+ ParameterValidator.objectChecker(donationService, "DonationService");
+ ParameterValidator.objectChecker(organizationService, "OrganizationService");
+ ParameterValidator.objectChecker(hostServices, "HostServices");
+
+ this.root = root;
+ this.header = new Header(this);
+ this.loginHeader = new LoginHeader();
+ this.appState = appState;
+ this.hostServices = hostServices;
+
+ this.authController = new AuthController(appState, this, userService);
+ this.donationController = new DonationController(appState, this, donationService);
+ this.organizationController = new OrganizationController(appState, organizationService);
+ }
+
+ /**
+ * Navigates to the home page by setting the top header and center content of the main layout.
+ * The home page serves as the landing page of the application, providing an overview and access to various features.
+ */
+ public void showHomePage() {
+ root.setTop(header);
+ root.setCenter(new HomePageView(this));
+ }
+
+ /**
+ * Navigates to the login page by setting the top header and center content of the main layout.
+ * The login page allows existing users to enter their credentials and access their account.
+ */
+ public void showLoginPage() {
+ root.setTop(loginHeader);
+ root.setCenter(new LoginPageView(this, authController));
+ }
+
+ /**
+ * Navigates to the sign-up page by setting the top header and center content of the main layout.
+ * The sign-up page allows new users to create an account by providing their details.
+ */
+ public void showSignUpPage() {
+ root.setTop(loginHeader);
+ root.setCenter(new SignUpPageView(this, authController));
+ }
+
+ /**
+ * Navigates to the payment complete page by setting the top header and center content of the main layout.
+ * The payment complete page confirms the successful completion of a donation transaction.
+ */
+ public void showPaymentCompletePage() {
+ root.setTop(header);
+ root.setCenter(new PaymentCompletePageView(this));
+ }
+
+ /**
+ * Navigates to the causes page by setting the top header and center content of the main layout.
+ * The causes page allows users to browse and search for organizations they may want to donate to.
+ */
+ public void showCausesPage() {
+ root.setTop(header);
+ root.setCenter(new CausesPageView(this, organizationController));
+ }
+
+ /**
+ * Navigates to the organization page by setting the top header and center content of the main layout.
+ * The organization page provides detailed information about a specific organization, including its mission,
+ * impact, and donation options, allowing users to learn more before making a donation.
+ */
+ public void showOrganizationPage() {
+ root.setTop(header);
+ root.setCenter(new OrganizationPageView(this, organizationController, donationController));
+ }
+
+ /**
+ * Navigates to the donation page by setting the top header and center content of the main layout.
+ * The donation page allows users to make new donations to selected organizations.
+ */
+ public void showDonationPage() {
+ root.setTop(header);
+ root.setCenter(new DonationPageView(this, donationController));
+ }
+
+ /**
+ * Displays an "About Us" dialog with information about the application and its developers.
+ * The dialog includes a description of the app's mission and a hyperlink to the project's GitHub repository
+ */
+ public void showAboutUsPage() {
+ new AboutUsView(hostServices).displayAboutUs();
+ }
+
+ /**
+ * Navigates to the user profile page by setting the top header and center content of the main layout.
+ * The user profile page allows users to view and manage their account information,
+ * donation history, and other personalized features.
+ */
+ public void showUserPage() {
+ root.setTop(header);
+ root.setCenter(new UserPageView(this, authController, donationController, organizationController));
+ }
+}
diff --git a/src/main/java/edu/group5/app/control/OrganizationController.java b/src/main/java/edu/group5/app/control/OrganizationController.java
new file mode 100644
index 0000000..49201aa
--- /dev/null
+++ b/src/main/java/edu/group5/app/control/OrganizationController.java
@@ -0,0 +1,62 @@
+package edu.group5.app.control;
+
+import edu.group5.app.model.AppState;
+import edu.group5.app.model.organization.Organization;
+import edu.group5.app.model.organization.OrganizationService;
+import edu.group5.app.utils.ParameterValidator;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Controller responsible for organization-related operations.
+ *
+ *
+ * Coordinates with {@link OrganizationService} to:
+ *
+ *
Retrieve organization information by ID
+ *
Retrieve all trusted organizations
+ *
+ *
+ */
+public class OrganizationController {
+ private final AppState appState;
+ private final OrganizationService service;
+
+ public OrganizationController(AppState appState, OrganizationService service) {
+ ParameterValidator.objectChecker(appState, "AppState");
+ ParameterValidator.objectChecker(service, "OrganizationService");
+ this.appState = appState;
+ this.service = service;
+ }
+
+
+ /**
+ * Sets the current selected organization.
+ * @param org the organization to set as current
+ */
+ public void setCurrentOrganization(Organization org) {
+ appState.setCurrentOrganization(org);
+ }
+
+ /**
+ * Gets the current selected organization.
+ * @return the current organization, or null if none selected
+ */
+ public Organization getCurrentOrganization() {
+ return appState.getCurrentOrganization();
+ }
+
+ public Organization getOrganizationById(int orgId) {
+ ParameterValidator.intChecker(orgId, "Organization ID");
+ return service.findByOrgNumber(orgId);
+ }
+
+ public Map getTrustedOrganizations() {
+ return service.getTrustedOrganizations();
+ }
+
+ public CompletableFuture