diff --git a/src/main/java/edu/group5/app/control/LoginController.java b/src/main/java/edu/group5/app/control/AuthController.java
similarity index 57%
rename from src/main/java/edu/group5/app/control/LoginController.java
rename to src/main/java/edu/group5/app/control/AuthController.java
index ae9d22e..249a436 100644
--- a/src/main/java/edu/group5/app/control/LoginController.java
+++ b/src/main/java/edu/group5/app/control/AuthController.java
@@ -13,17 +13,48 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-public class LoginController {
+/**
+ * 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 LoginController(AppState appState, NavigationController nav, UserService userService) {
+ public AuthController(AppState appState, NavigationController nav, UserService userService) {
this.appState = appState;
this.nav = nav;
this.userService = userService;
}
+ /**
+ * 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() ||
@@ -56,17 +87,30 @@ public void handleSignUp(SignUpPageView view, String firstName, String lastName,
if (success) {
User user = userService.getUserByEmail(email);
- appState.setCurrentUser(user);
- nav.showHomePage();
- } else {
- view.showError("Registration failed. Email may already be in use.");
- }
+ appState.setCurrentUser(user);
+ nav.showHomePage();
} else {
- view.showError("Registration failed. Must Accept Privacy Policy to create account.");
+ 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");
@@ -83,10 +127,17 @@ public void handleLogin(LoginPageView view, String email, char[] passwordChars)
}
}
+ /**
+ * Handles the logout of a {@link User}.
+ *
+ * Clears states in {@link AppState} and the application
+ * navigates to the login page.
+ */
public void handleLogout() {
appState.setCurrentUser(null);
appState.setCurrentOrganization(null);
appState.setCurrentDonationAmount(null);
+ 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 a579a9b..8a8ec79 100644
--- a/src/main/java/edu/group5/app/control/DonationController.java
+++ b/src/main/java/edu/group5/app/control/DonationController.java
@@ -15,6 +15,19 @@
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
+/**
+ * Controller responsible for donation-related operations.
+ *
+ *
+ * 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;
@@ -26,21 +39,45 @@ public DonationController(AppState appState, NavigationController nav, DonationS
this.service = service;
}
+ /**
+ * 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) {
return service.getDonationRepository().filterByUser(userId);
}
- public Set getUniqueOrgs() {
+ /**
+ * Returns a set of unique organization IDs that the current user
+ * has donated to.
+ *
+ * @return a set of organization IDs
+ */
+ public Set getUniqueOrganizationIDs() {
Map userDonations = getUserDonations(appState.getCurrentUser().getUserId());
- Set uniqueOrgs = new HashSet<>();
+ Set uniqueOrganizations = new HashSet<>();
for (Donation donation : userDonations.values()) {
- uniqueOrgs.add(donation.organizationId());
+ uniqueOrganizations.add(donation.organizationId());
}
- return uniqueOrgs;
+ 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();
diff --git a/src/main/java/edu/group5/app/control/NavigationController.java b/src/main/java/edu/group5/app/control/NavigationController.java
index 1b794d5..7bfbc8c 100644
--- a/src/main/java/edu/group5/app/control/NavigationController.java
+++ b/src/main/java/edu/group5/app/control/NavigationController.java
@@ -16,6 +16,9 @@
import edu.group5.app.view.userpage.UserPageView;
import javafx.scene.layout.BorderPane;
+/**
+ * Controller responsible for navigating between views within the root node.
+ */
public class NavigationController {
private final BorderPane root;
private final Header header;
@@ -23,7 +26,7 @@ public class NavigationController {
private final AppState appState;
- private final LoginController loginController;
+ private final AuthController authController;
private final DonationController donationController;
private final OrganizationController organizationController;
@@ -34,9 +37,9 @@ public NavigationController(BorderPane root, AppState appState, UserService user
this.appState = appState;
- this.loginController = new LoginController(appState, this, userService);
+ this.authController = new AuthController(appState, this, userService);
this.donationController = new DonationController(appState, this, donationService);
- this.organizationController = new OrganizationController(appState, this, organizationService);
+ this.organizationController = new OrganizationController(organizationService);
}
public void showHomePage() {
@@ -46,12 +49,12 @@ public void showHomePage() {
public void showLoginPage() {
root.setTop(loginHeader);
- root.setCenter(new LoginPageView(appState, this, loginController));
+ root.setCenter(new LoginPageView(appState, this, authController));
}
public void showSignUpPage() {
root.setTop(loginHeader);
- root.setCenter(new SignUpPageView(appState, this, loginController));
+ root.setCenter(new SignUpPageView(appState, this, authController));
}
public void showPaymentCompletePage() {
@@ -80,6 +83,6 @@ public void showAboutUsPage() {
public void showUserPage() {
root.setTop(header);
- root.setCenter(new UserPageView(appState, this, loginController, donationController, organizationController));
+ root.setCenter(new UserPageView(appState, 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
index 499b7c9..fb65127 100644
--- a/src/main/java/edu/group5/app/control/OrganizationController.java
+++ b/src/main/java/edu/group5/app/control/OrganizationController.java
@@ -1,28 +1,26 @@
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 java.util.Map;
import java.util.concurrent.CompletableFuture;
+/**
+ * Controller responsible for organization-related operations.
+ */
public class OrganizationController {
- private final AppState appState;
- private final NavigationController nav;
private final OrganizationService service;
- public OrganizationController(AppState appState, NavigationController nav, OrganizationService service) {
- this.appState = appState;
- this.nav = nav;
+ public OrganizationController(OrganizationService service) {
this.service = service;
}
- public Organization getOrgById(int orgId) {
+ public Organization getOrganizationById(int orgId) {
return service.findByOrgNumber(orgId);
}
- public Map getTrustedOrgs() {
+ public Map getTrustedOrganizations() {
return service.getTrustedOrganizations();
}
diff --git a/src/main/java/edu/group5/app/view/causespage/CausesPageView.java b/src/main/java/edu/group5/app/view/causespage/CausesPageView.java
index f6eb2b5..7db3ee9 100644
--- a/src/main/java/edu/group5/app/view/causespage/CausesPageView.java
+++ b/src/main/java/edu/group5/app/view/causespage/CausesPageView.java
@@ -65,7 +65,7 @@ private BorderPane createBody() {
vBox.setMaxWidth(Double.MAX_VALUE);
// Load organizations INSTANTLY from cache
- allOrganizations = orgController.getTrustedOrgs();
+ allOrganizations = orgController.getTrustedOrganizations();
vBox.getChildren().add(createOrganizationSection(null));
body.setContent(vBox);
@@ -126,6 +126,23 @@ private GridPane createOrganizationSection(String searchTerm) {
organizationGrid = grid;
}
+ if (allOrganizations == null) {
+ allOrganizations = orgController.getTrustedOrganizations();
+
+ //Show loading text while organizations and logos are fetched
+ grid.add(new javafx.scene.control.Label("Loading..."), 0, 0);
+
+ //Fetch trusted organizations with logos asynchronously (runs in background)
+ orgController.getOrganizationsWithLogosAsync()
+ .thenAccept(orgs -> {
+ this.allOrganizations = orgs;
+
+ // Update UI when data is ready
+ Platform.runLater(() -> updateOrganizationGrid(""));
+ });
+ return grid;
+ }
+
Map organizations = new HashMap<>();
if (searchTerm != null && !searchTerm.isEmpty()) {
// Filter organizations by search term
diff --git a/src/main/java/edu/group5/app/view/loginpage/LoginPageView.java b/src/main/java/edu/group5/app/view/loginpage/LoginPageView.java
index 3fb9bb7..11b3ba7 100644
--- a/src/main/java/edu/group5/app/view/loginpage/LoginPageView.java
+++ b/src/main/java/edu/group5/app/view/loginpage/LoginPageView.java
@@ -1,7 +1,7 @@
package edu.group5.app.view.loginpage;
import edu.group5.app.control.NavigationController;
-import edu.group5.app.control.LoginController;
+import edu.group5.app.control.AuthController;
import edu.group5.app.model.AppState;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@@ -23,16 +23,16 @@
public class LoginPageView extends BorderPane {
private final AppState appState;
private final NavigationController nav;
- private final LoginController loginController;
+ private final AuthController authController;
private TextField emailField;
private PasswordField passwordField;
private Label errorLabel;
- public LoginPageView(AppState appState, NavigationController nav, LoginController loginController) {
+ public LoginPageView(AppState appState, NavigationController nav, AuthController authController) {
this.appState = appState;
this.nav = nav;
- this.loginController = loginController;
+ this.authController = authController;
HBox content = new HBox();
content.setFillHeight(true);
@@ -105,7 +105,7 @@ private Button getLoginBtn() {
Button loginBtn = new Button("Log In");
loginBtn.setMaxWidth(300);
loginBtn.setId("login-btn");
- loginBtn.setOnMouseClicked(e -> loginController.handleLogin(
+ loginBtn.setOnMouseClicked(e -> authController.handleLogin(
this,
getEmail(),
getPassword()
diff --git a/src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java b/src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java
index 160f929..8638a1f 100644
--- a/src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java
+++ b/src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java
@@ -1,7 +1,7 @@
package edu.group5.app.view.loginpage;
import edu.group5.app.control.NavigationController;
-import edu.group5.app.control.LoginController;
+import edu.group5.app.control.AuthController;
import edu.group5.app.model.AppState;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@@ -24,7 +24,7 @@
public class SignUpPageView extends BorderPane {
private final AppState appState;
private final NavigationController nav;
- private final LoginController loginController;
+ private final AuthController authController;
private TextField nameField;
private TextField surnameField;
@@ -32,10 +32,10 @@ public class SignUpPageView extends BorderPane {
private PasswordField passwordField;
private Label errorLabel;
- public SignUpPageView(AppState appState, NavigationController nav, LoginController loginController) {
+ public SignUpPageView(AppState appState, NavigationController nav, AuthController authController) {
this.appState = appState;
this.nav = nav;
- this.loginController = loginController;
+ this.authController = authController;
HBox content = new HBox();
content.setFillHeight(true);
@@ -138,7 +138,7 @@ private Button getSignUpBtn() {
Button signUpBtn = new Button("Sign Up");
signUpBtn.setMaxWidth(300);
signUpBtn.setId("login-btn");
- signUpBtn.setOnMouseClicked(e -> loginController.handleSignUp(
+ signUpBtn.setOnMouseClicked(e -> authController.handleSignUp(
this,
getFirstName(),
getLastName(),
diff --git a/src/main/java/edu/group5/app/view/userpage/UserPageView.java b/src/main/java/edu/group5/app/view/userpage/UserPageView.java
index e3b7c8b..5e2370a 100644
--- a/src/main/java/edu/group5/app/view/userpage/UserPageView.java
+++ b/src/main/java/edu/group5/app/view/userpage/UserPageView.java
@@ -3,7 +3,7 @@
import edu.group5.app.control.DonationController;
import edu.group5.app.control.NavigationController;
import edu.group5.app.control.OrganizationController;
-import edu.group5.app.control.LoginController;
+import edu.group5.app.control.AuthController;
import edu.group5.app.model.AppState;
import edu.group5.app.model.donation.Donation;
import edu.group5.app.model.organization.Organization;
@@ -31,14 +31,14 @@
public class UserPageView extends BorderPane {
private final AppState appState;
private final NavigationController nav;
- private final LoginController loginController;
+ private final AuthController authController;
private final DonationController donationController;
private final OrganizationController organizationController;
- public UserPageView(AppState appState, NavigationController nav, LoginController loginController, DonationController donationController, OrganizationController organizationController) {
+ public UserPageView(AppState appState, NavigationController nav, AuthController authController, DonationController donationController, OrganizationController organizationController) {
this.appState = appState;
this.nav = nav;
- this.loginController = loginController;
+ this.authController = authController;
this.donationController = donationController;
this.organizationController = organizationController;
@@ -70,7 +70,7 @@ private HBox createProfileSection() {
Button logoutBtn = new Button("Logout");
logoutBtn.getStyleClass().add("logout-button");
- logoutBtn.setOnAction(e -> loginController.handleLogout());
+ logoutBtn.setOnAction(e -> authController.handleLogout());
VBox info = new VBox(10, name, email, location, logoutBtn);
info.setAlignment(Pos.CENTER_LEFT);
@@ -91,15 +91,15 @@ private VBox createCausesSection() {
causesFlow.setStyle("-fx-padding: 10;");
causesFlow.getStyleClass().add("section-box");
- Set uniqueOrgs = donationController.getUniqueOrgs();
+ Set uniqueOrganizations = donationController.getUniqueOrganizationIDs();
- if (uniqueOrgs.isEmpty()) {
+ if (uniqueOrganizations.isEmpty()) {
Label noCauses = new Label("No causes supported yet");
noCauses.setStyle("-fx-text-fill: #999;");
causesFlow.getChildren().add(noCauses);
} else {
- for (int orgId : uniqueOrgs) {
- Organization org = organizationController.getOrgById(orgId);
+ for (int orgId : uniqueOrganizations) {
+ Organization org = organizationController.getOrganizationById(orgId);
if (org != null) {
causesFlow.getChildren().add(createCauseChip(org));
}
@@ -128,40 +128,40 @@ private VBox createDonationsSection() {
scrollPane.setMaxWidth(650);
scrollPane.setPrefHeight(400);
scrollPane.setStyle("-fx-focus-color: transparent; -fx-faint-focus-color: transparent;");
-
+
VBox donationsBox = new VBox(12);
donationsBox.getStyleClass().add("donation-list");
donationsBox.setPadding(new Insets(10));
User currentUser = appState.getCurrentUser();
- Map userDonations =
+ Map userDonations =
donationController.getUserDonations(currentUser.getUserId());
// Filter donations based on search
searchField.textProperty().addListener((obs, oldVal, newVal) -> {
donationsBox.getChildren().clear();
-
+
if (userDonations.isEmpty()) {
Label noDonations = new Label("No donations yet");
noDonations.setStyle("-fx-text-fill: #999;");
donationsBox.getChildren().add(noDonations);
return;
}
-
+
String searchTerm = newVal.toLowerCase().trim();
boolean found = false;
-
+
for (Donation donation : userDonations.values()) {
- Organization org = organizationController.getOrgById(donation.organizationId());
+ Organization org = organizationController.getOrganizationById(donation.organizationId());
String orgName = (org != null) ? org.name() : "Unknown Organization";
-
+
// Filter by search term
if (searchTerm.isEmpty() || orgName.toLowerCase().contains(searchTerm)) {
donationsBox.getChildren().add(createDonationCard(donation));
found = true;
}
}
-
+
if (!found && !searchTerm.isEmpty()) {
Label noResults = new Label("No donations found for \"" + newVal + "\"");
noResults.setStyle("-fx-text-fill: #999;");
@@ -175,24 +175,18 @@ private VBox createDonationsSection() {
donationsBox.getChildren().add(noDonations);
} else {
for (Donation donation : userDonations.values()) {
+ Organization org = organizationController.getOrganizationById(donation.organizationId());
+ String orgName = (org != null) ? org.name() : "Unknown Organization";
donationsBox.getChildren().add(createDonationCard(donation));
-
}
}
+
scrollPane.setContent(donationsBox);
return new VBox(10, title, searchBox, scrollPane);
-
- }
-
-
- private Label createCauseChip(Organization org) {
- Label chip = new Label(org.name());
- chip.getStyleClass().add("cause-chip");
- return chip;
}
private BorderPane createDonationCard(Donation donation) {
- Organization org = organizationController.getOrgById(donation.organizationId());
+ Organization org = organizationController.getOrganizationById(donation.organizationId());
String orgName = (org != null) ? org.name() : "Unknown Organization";
// Use BorderPane to fix columns: LEFT | SPACE | RIGHT
@@ -208,17 +202,28 @@ private BorderPane createDonationCard(Donation donation) {
// RIGHT: Amount and date (stacked vertically)
VBox details = new VBox(4);
details.setAlignment(Pos.CENTER_RIGHT);
-
+
Label amountLabel = new Label(String.format("%.2f", donation.amount()) + " kr");
amountLabel.getStyleClass().add("donation-amount");
-
+
Label dateLabel = new Label(
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(donation.date()));
dateLabel.getStyleClass().add("donation-date");
-
+
details.getChildren().addAll(amountLabel, dateLabel);
card.setRight(details);
return card;
}
+
+ private FlowPane createCauseChip(Organization org) {
+ FlowPane chip = new FlowPane();
+ chip.getStyleClass().add("cause-chip");
+ chip.setPadding(new Insets(8, 12, 8, 12));
+
+ Label label = new Label(org.name());
+ chip.getChildren().add(label);
+
+ return chip;
+ }
}