diff --git a/src/main/java/edu/group5/app/App.java b/src/main/java/edu/group5/app/App.java index 6704bd2..782922c 100644 --- a/src/main/java/edu/group5/app/App.java +++ b/src/main/java/edu/group5/app/App.java @@ -1,18 +1,62 @@ 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.DonationRepository; +import edu.group5.app.model.donation.DonationService; +import edu.group5.app.model.organization.OrganizationRepository; +import edu.group5.app.model.organization.OrganizationService; +import edu.group5.app.model.user.UserRepository; +import edu.group5.app.model.user.UserService; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; +import java.util.List; + /** - * Hello world! + * Main entry point for the Help-Me-Help charity donation application. + * Handles database connection, data loading, and application setup. */ public class App extends Application { @Override public void start(Stage stage) { - MainController controller = new MainController(); + DbWrapper dbWrapper = new DbWrapper(true); + OrgApiWrapper orgApiWrapper = new OrgApiWrapper("https://app.innsamlingskontrollen.no/api/public/v1/all"); + + if (!dbWrapper.connect()) { + System.err.println("Failed to connect to database"); + return; + } + + // Load data from database + List userData = dbWrapper.importUsers(); + List donationData = dbWrapper.fetchAllDonations(); + dbWrapper.disconnect(); + + // Load organizations from API + Object[] organizationData = new Object[0]; + try { + if (orgApiWrapper.importData()) { + organizationData = orgApiWrapper.getData(); + } + } catch (InterruptedException e) { + System.err.println("Failed to load organization data: " + e.getMessage()); + } + + // Create repositories with fetched data + UserRepository userRepository = new UserRepository(userData); + DonationRepository donationRepository = new DonationRepository(donationData); + OrganizationRepository organizationRepository = new OrganizationRepository(organizationData); + + // Create services (backend wiring) + UserService userService = new UserService(userRepository); + DonationService donationService = new DonationService(donationRepository, organizationRepository); + OrganizationService organizationService = new OrganizationService(organizationRepository); + + MainController controller = new MainController(userService, donationService, organizationService); Scene scene = controller.getMainView().getScene(); controller.showLoginPage(); @@ -22,4 +66,8 @@ public void start(Stage stage) { stage.setScene(scene); stage.show(); } + + public static void main(String[] args) { + launch(args); + } } diff --git a/src/main/java/edu/group5/app/control/BrowseCardController.java b/src/main/java/edu/group5/app/control/BrowseCardController.java index 297ba9f..9f86271 100644 --- a/src/main/java/edu/group5/app/control/BrowseCardController.java +++ b/src/main/java/edu/group5/app/control/BrowseCardController.java @@ -1,5 +1,7 @@ package edu.group5.app.control; +import edu.group5.app.model.organization.Organization; + public class BrowseCardController { private final MainController controller; @@ -7,8 +9,8 @@ public BrowseCardController(MainController mainController) { this.controller = mainController; } - public void handleCardClick() { - System.out.println("Browse Card Clicked"); + public void handleCardClick(Organization organization) { + controller.setCurrentOrganization(organization); controller.showOrganizationPage(); } } diff --git a/src/main/java/edu/group5/app/control/LoginPageController.java b/src/main/java/edu/group5/app/control/LoginPageController.java index a06d135..b28f191 100644 --- a/src/main/java/edu/group5/app/control/LoginPageController.java +++ b/src/main/java/edu/group5/app/control/LoginPageController.java @@ -1,11 +1,42 @@ 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) { + 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 index 8106975..2b75487 100644 --- a/src/main/java/edu/group5/app/control/MainController.java +++ b/src/main/java/edu/group5/app/control/MainController.java @@ -1,10 +1,16 @@ 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; @@ -13,10 +19,20 @@ public class MainController { 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; - public MainController() { - this.view = new MainView(this); + this.view = new MainView(this, userService); this.headerController = new HeaderController(this); this.homePageController = new HomePageController(this); this.browsePageController = new BrowsePageController(this); @@ -25,6 +41,18 @@ public MainController() { 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; } @@ -33,8 +61,26 @@ 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(); } diff --git a/src/main/java/edu/group5/app/control/SignInPageController.java b/src/main/java/edu/group5/app/control/SignInPageController.java index a51e4d5..2d8d874 100644 --- a/src/main/java/edu/group5/app/control/SignInPageController.java +++ b/src/main/java/edu/group5/app/control/SignInPageController.java @@ -1,15 +1,56 @@ package edu.group5.app.control; +import edu.group5.app.model.user.UserService; +import edu.group5.app.view.loginpage.SignInPageView; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import edu.group5.app.model.user.User; public class SignInPageController { private final MainController controller; + private final UserService userService; + private SignInPageView view; - public SignInPageController(MainController controller) { + public SignInPageController(MainController controller, UserService userService) { this.controller = controller; + this.userService = userService; + } + + public void setView(SignInPageView view) { + this.view = view; } + public void handleSignInBtn() { - System.out.println("Sign in button pressed"); - controller.showHomePage(); + String firstName = view.getFirstName(); + String lastName = view.getLastName(); + String email = view.getEmail(); + char[] passwordChars = view.getPassword(); + + 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; + } + + String password = new String(passwordChars); + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + String hashedPassword = encoder.encode(password); + + boolean success = userService.registerUser( + "Customer", firstName, lastName, email, hashedPassword); + + if (success) { + User user = userService.getUserByEmail(email); + + controller.setCurrentUser(user); + controller.showHomePage(); + } else { + view.showError("Registration failed. Email may already be in use."); + } } + public void handleLoginBtn() { System.out.println("Back to login button pressed"); controller.showLoginPage(); diff --git a/src/main/java/edu/group5/app/control/donationpage/DonationPageController.java b/src/main/java/edu/group5/app/control/donationpage/DonationPageController.java index 248c4a1..5fed3e2 100644 --- a/src/main/java/edu/group5/app/control/donationpage/DonationPageController.java +++ b/src/main/java/edu/group5/app/control/donationpage/DonationPageController.java @@ -1,6 +1,11 @@ package edu.group5.app.control.donationpage; import edu.group5.app.control.MainController; +import edu.group5.app.model.organization.Organization; +import edu.group5.app.model.user.Customer; +import edu.group5.app.model.user.User; + +import java.math.BigDecimal; public class DonationPageController { private final MainController controller; @@ -9,7 +14,46 @@ public DonationPageController(MainController controller) { this.controller = controller; } public void handleDonationBtn() { - System.out.println("Donating"); + // Get session data from MainController + User currentUser = controller.getCurrentUser(); + Organization currentOrg = controller.getCurrentOrganization(); + BigDecimal amount = controller.getCurrentDonationAmount(); + + if (currentUser == null) { + System.err.println("Error: No user logged in"); + return; + } + if (!(currentUser instanceof Customer customer)) { + System.err.println("Error: Only customers can donate"); + return; + } + if (currentOrg == null) { + System.err.println("Error: No organization selected"); + return; + } + if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { + System.err.println("Error: Invalid donation amount"); + return; + } + + // Create donation via service + boolean success = controller.getDonationService().donate( + customer, + currentOrg.orgNumber(), + amount, + "Online" + ); + + if (success) { + System.out.println("Donation created: " + amount + " kr to " + currentOrg.name()); + } else { + System.err.println("Failed to create donation"); + } + + // Clear donation session state + controller.setCurrentDonationAmount(null); + + // Navigate to payment complete controller.showPaymentCompletePage(); } diff --git a/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java b/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java index 65196dc..babb929 100644 --- a/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java +++ b/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java @@ -141,7 +141,7 @@ public int exportUsers(List data) { return rowsAffected; } - private List importDonations() { + public List fetchAllDonations() { return this.importDonations(0, true); } @@ -182,8 +182,8 @@ private List importDonations(int user_id, boolean all) { return this.donations; } - public int exportDonations(List data) { - this.importDonations(); + public int exportDonations(List data) { + this.fetchAllDonations(); if (data == null) { throw new IllegalArgumentException("data can't be null"); diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationService.java b/src/main/java/edu/group5/app/model/organization/OrganizationService.java new file mode 100644 index 0000000..c5979f5 --- /dev/null +++ b/src/main/java/edu/group5/app/model/organization/OrganizationService.java @@ -0,0 +1,58 @@ +package edu.group5.app.model.organization; + +import java.util.Map; + +/** + * Service class for managing organization-related operations. + * It interacts with the OrganizationRepository to retrieve organization information + * and contains business logic associated with organization management. + */ +public class OrganizationService { + private OrganizationRepository organizationRepository; + + /** + * Constructs an OrganizationService with the given OrganizationRepository. + * @param organizationRepository the OrganizationRepository to use for managing organization data; must not be null + * @throws IllegalArgumentException if organizationRepository is null + */ + public OrganizationService(OrganizationRepository organizationRepository) { + if (organizationRepository == null) { + throw new IllegalArgumentException("OrganizationRepository cannot be null"); + } + this.organizationRepository = organizationRepository; + } + + /** + * Getter for the OrganizationRepository used by this service. + * @return the OrganizationRepository instance used by this service + */ + public OrganizationRepository getOrganizationRepository() { + return this.organizationRepository; + } + + /** + * Retrieves all trusted organizations. + * @return a map of trusted organizations by organization number + */ + public Map getTrustedOrganizations() { + return organizationRepository.getTrustedOrganizations(); + } + + /** + * Finds an organization by its organization number. + * @param orgNumber the organization number to find + * @return the Organization if found, null otherwise + */ + public Organization findByOrgNumber(int orgNumber) { + return organizationRepository.findByOrgNumber(orgNumber); + } + + /** + * Finds an organization by its name. + * @param name the name of the organization + * @return the Organization if found, null otherwise + */ + public Organization findByOrgName(String name) { + return organizationRepository.findByOrgName(name); + } +} diff --git a/src/main/java/edu/group5/app/model/user/UserService.java b/src/main/java/edu/group5/app/model/user/UserService.java index 44e092b..628c785 100644 --- a/src/main/java/edu/group5/app/model/user/UserService.java +++ b/src/main/java/edu/group5/app/model/user/UserService.java @@ -21,7 +21,7 @@ public UserService(UserRepository userRepository) { } /** - * Getter for the UserRepository used by this service. + * Getter for the UserRepository used by this service. * This method allows access to the user repository for managing user data and performing operations such as registration and login. * @return the UserRepository instance used by this service */ @@ -65,7 +65,7 @@ public boolean registerUser(String role, String firstName, String lastName, * Authenticates a user based on the provided email and password. * @param email the email address of the user attempting to log in; must not be null or empty * @param password the plaintext password of the user attempting to log in; must not be null or empty - * @return the authenticated User object if the login is successful + * @return the authenticated User object if the login is successful * (i.e., the user exists and the password is correct), null otherwise * @throws IllegalArgumentException if email is null or empty, or if password is null or empty */ @@ -79,4 +79,16 @@ public User login(String email, char[] password) { } return null; } + + /** + * Retrieves a user by email address. + * @param email the email address of the user to find; must not be null or empty + * @return the User object if found, null otherwise + */ + public User getUserByEmail(String email) { + if (email == null || email.trim().isEmpty()) { + return null; + } + return this.userRepository.findUserByEmail(email); + } } diff --git a/src/main/java/edu/group5/app/view/MainView.java b/src/main/java/edu/group5/app/view/MainView.java index 27eeac5..e35935f 100644 --- a/src/main/java/edu/group5/app/view/MainView.java +++ b/src/main/java/edu/group5/app/view/MainView.java @@ -5,7 +5,8 @@ import edu.group5.app.control.*; import edu.group5.app.control.donationpage.DonationPageController; import edu.group5.app.control.donationpage.PaymentCompleteController; -import edu.group5.app.model.user.Customer; +import edu.group5.app.model.user.User; +import edu.group5.app.model.user.UserService; import edu.group5.app.view.donationpage.DonationPageView; import edu.group5.app.view.donationpage.PaymentCompletePageView; import edu.group5.app.view.homepage.HomePageView; @@ -17,6 +18,7 @@ import javafx.scene.layout.BorderPane; public class MainView { + private final MainController mainController; private final HeaderController headerController; private final HomePageController homePageController; private final LoginPageController loginPageController; @@ -26,11 +28,12 @@ public class MainView { private final Scene scene; private final BorderPane root; - public MainView(MainController mainController) { + public MainView(MainController mainController, UserService userService) { + this.mainController = mainController; this.headerController = new HeaderController(mainController); this.homePageController = new HomePageController(mainController); - this.loginPageController = new LoginPageController(mainController); - this.signInPageController = new SignInPageController(mainController); + this.loginPageController = new LoginPageController(mainController, userService); + this.signInPageController = new SignInPageController(mainController, userService); this.donationPageController = new DonationPageController(mainController); this.paymentCompleteController = new PaymentCompleteController(mainController); this.root = new BorderPane(); @@ -55,18 +58,18 @@ public void showLoginPage() { } public void showBrowsePage(BrowsePageController browsePageController, BrowseCardController browseCardController, HeaderController headerController) { - root.setCenter(new BrowsePageView(getScene(), browsePageController, browseCardController, headerController)); + root.setCenter(new BrowsePageView(getScene(), browsePageController, browseCardController, headerController, mainController)); } public void showOrganizationPage(OrganizationPageController organizationController, HeaderController headerController) { - root.setCenter(new OrganizationPageView(organizationController, headerController)); + root.setCenter(new OrganizationPageView(organizationController, headerController, mainController)); } public void showSignInPage() { root.setCenter(new SignInPageView(signInPageController)); } public void showDonationPage() { - root.setCenter(new DonationPageView(donationPageController, headerController)); + root.setCenter(new DonationPageView(donationPageController, headerController, mainController)); } public void showPaymentCompletePage() { root.setCenter(new PaymentCompletePageView(paymentCompleteController)); @@ -75,13 +78,9 @@ public void showPaymentCompletePage() { public void showAboutUsPage() {} public void showUserPage() { - Customer testCustomer = new Customer( - 1, - "Jinwoo", - "Son", - "aurafarmer@gmail.com", - "hashedpassword" - ); - root.setCenter(new UserPageView(headerController, testCustomer)); + User currentUser = mainController.getCurrentUser(); + if (currentUser != null) { + root.setCenter(new UserPageView(headerController, mainController)); + } } } diff --git a/src/main/java/edu/group5/app/view/browsepage/BrowseCard.java b/src/main/java/edu/group5/app/view/browsepage/BrowseCard.java index 02cc094..ad4be8a 100644 --- a/src/main/java/edu/group5/app/view/browsepage/BrowseCard.java +++ b/src/main/java/edu/group5/app/view/browsepage/BrowseCard.java @@ -1,6 +1,7 @@ package edu.group5.app.view.browsepage; import edu.group5.app.control.BrowseCardController; +import edu.group5.app.model.organization.Organization; import javafx.geometry.Pos; import javafx.scene.image.Image; import javafx.scene.image.ImageView; @@ -10,23 +11,25 @@ public class BrowseCard extends VBox { private final BrowseCardController controller; + private final Organization organization; - public BrowseCard(BrowseCardController browseCardController, String img, String name) { + public BrowseCard(BrowseCardController browseCardController, Organization org, String img) { this.controller = browseCardController; + this.organization = org; setId("mainContainer"); getStylesheets().add(getClass().getResource("/browsepage/browse_org.css").toExternalForm()); getChildren().addAll( imageContainer(img), - orgName(name), + orgName(org.name()), checkMarkContainer() ); setOnMouseClicked(e -> { - controller.handleCardClick(); + controller.handleCardClick(organization); }); - setSpacing(20); + setSpacing(10); setFillWidth(true); setAlignment(Pos.CENTER); } @@ -34,8 +37,8 @@ public BrowseCard(BrowseCardController browseCardController, String img, String private StackPane imageContainer(String img) { StackPane imageContainer = new StackPane(); imageContainer.setId("imageContainer"); - imageContainer.setPrefHeight(120); - imageContainer.setPrefWidth(120); + imageContainer.setPrefHeight(80); + imageContainer.setPrefWidth(80); imageContainer.setMaxWidth(Double.MAX_VALUE); ImageView logo = new ImageView( @@ -45,7 +48,7 @@ private StackPane imageContainer(String img) { logo.setId("logo"); logo.setSmooth(true); logo.setPreserveRatio(true); - logo.setFitHeight(150); + logo.setFitHeight(80); imageContainer.getChildren().add(logo); return imageContainer; @@ -54,6 +57,7 @@ private StackPane imageContainer(String img) { private Text orgName(String text) { Text orgName = new Text(text); orgName.setId("orgName"); + orgName.setWrappingWidth(150); return orgName; } diff --git a/src/main/java/edu/group5/app/view/browsepage/BrowsePageView.java b/src/main/java/edu/group5/app/view/browsepage/BrowsePageView.java index 4cb54b2..a2d92c8 100644 --- a/src/main/java/edu/group5/app/view/browsepage/BrowsePageView.java +++ b/src/main/java/edu/group5/app/view/browsepage/BrowsePageView.java @@ -3,6 +3,8 @@ import edu.group5.app.control.BrowseCardController; import edu.group5.app.control.BrowsePageController; import edu.group5.app.control.HeaderController; +import edu.group5.app.control.MainController; +import edu.group5.app.model.organization.Organization; import edu.group5.app.view.Header; import javafx.geometry.Pos; import javafx.scene.Scene; @@ -10,15 +12,22 @@ import javafx.scene.control.TextField; import javafx.scene.layout.*; +import java.util.Map; +import java.util.stream.Collectors; + public class BrowsePageView extends BorderPane { private final Scene scene; private final BrowsePageController controller; private final BrowseCardController orgController; + private final MainController mainController; + private GridPane organizationGrid; + private Map allOrganizations; - public BrowsePageView(Scene mainScene, BrowsePageController browsePageController, BrowseCardController browseCardController, HeaderController headerController) { + public BrowsePageView(Scene mainScene, BrowsePageController browsePageController, BrowseCardController browseCardController, HeaderController headerController, MainController mainController) { this.scene = mainScene; this.controller = browsePageController; this.orgController = browseCardController; + this.mainController = mainController; getStylesheets().add(getClass().getResource("/browsepage/browsepage.css").toExternalForm()); Header headerView = new Header(headerController); setTop(headerView); @@ -29,11 +38,16 @@ private ScrollPane createBody() { ScrollPane body = new ScrollPane(); body.setId("body"); body.setFitToWidth(true); + body.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + body.setStyle("-fx-focus-color: transparent; -fx-faint-focus-color: transparent;"); + VBox vBox = new VBox(); + vBox.setStyle("-fx-padding: 10;"); vBox.setSpacing(10); + vBox.setMaxWidth(Double.MAX_VALUE); vBox.getChildren().addAll( createSearchSection(), - createOrganizationSection() + createOrganizationSection(null) ); body.setContent(vBox); return body; @@ -42,25 +56,39 @@ private ScrollPane createBody() { private HBox createSearchSection() { HBox searchSection = new HBox(); TextField searchField = new TextField(); - searchField.setPromptText("Search.."); + searchField.setPromptText("Search organizations..."); + + // Add listener for search text changes + searchField.textProperty().addListener((obs, oldVal, newVal) -> { + updateOrganizationGrid(newVal.trim()); + }); + searchSection.getChildren().add(searchField); return searchSection; } - private GridPane createOrganizationSection() { + private GridPane createOrganizationSection(String searchTerm) { GridPane grid = new GridPane(); grid.setId("card-grid"); - grid.setHgap(10); - grid.setVgap(10); - grid.setMaxWidth(Double.MAX_VALUE - 50); + grid.setHgap(20); + grid.setVgap(20); + grid.setStyle("-fx-padding: 0;"); + grid.setMaxWidth(Double.MAX_VALUE); + + if (allOrganizations == null) { + allOrganizations = mainController.getOrganizationService().getTrustedOrganizations(); + } + + // Filter organizations by search term + Map organizations = filterOrganizations(searchTerm); int column = 0; int row = 0; - for (int i = 0; i < 16; i++) { - BrowseCard card = new BrowseCard(orgController, "/browsepage/images/children_of_shambala.png", "Shambala Foundation"); - GridPane.setFillWidth(card, true); - grid.setAlignment(Pos.CENTER); + for (Organization org : organizations.values()) { + String defaultImg = "/browsepage/images/children_of_shambala.png"; + BrowseCard card = new BrowseCard(orgController, org, defaultImg); + grid.add(card, column, row); column++; @@ -76,6 +104,42 @@ private GridPane createOrganizationSection() { col.setPercentWidth(25); grid.getColumnConstraints().add(col); } + + // Store reference for later updates + if (organizationGrid == null) { + organizationGrid = grid; + } + return grid; } + + private Map filterOrganizations(String searchTerm) { + // If no search term, return all organizations + if (searchTerm == null || searchTerm.isEmpty()) { + return allOrganizations; + } + + String lowerSearchTerm = searchTerm.toLowerCase(); + return allOrganizations.values().stream() + .filter(org -> org.name().toLowerCase().contains(lowerSearchTerm)) + .collect(Collectors.toMap( + Organization::orgNumber, + org -> org + )); + } + + private void updateOrganizationGrid(String searchTerm) { + if (organizationGrid == null) { + return; + } + + organizationGrid.getChildren().clear(); + organizationGrid.getColumnConstraints().clear(); + + // Rebuild grid with filtered organizations + GridPane updated = createOrganizationSection(searchTerm); + + organizationGrid.getChildren().addAll(updated.getChildren()); + organizationGrid.getColumnConstraints().addAll(updated.getColumnConstraints()); + } } diff --git a/src/main/java/edu/group5/app/view/donationpage/DonationPageView.java b/src/main/java/edu/group5/app/view/donationpage/DonationPageView.java index 5cf7171..6c5e5e6 100644 --- a/src/main/java/edu/group5/app/view/donationpage/DonationPageView.java +++ b/src/main/java/edu/group5/app/view/donationpage/DonationPageView.java @@ -1,6 +1,7 @@ package edu.group5.app.view.donationpage; import edu.group5.app.control.HeaderController; +import edu.group5.app.control.MainController; import edu.group5.app.control.donationpage.DonationPageController; import edu.group5.app.view.Header; import javafx.geometry.Insets; @@ -13,12 +14,23 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; +import javafx.scene.Node; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class DonationPageView extends BorderPane { private final DonationPageController controller; + private final MainController mainController; + private final List allDonationElements = new ArrayList<>(); + private final Map elementAmounts = new HashMap<>(); - public DonationPageView(DonationPageController donationPageController, HeaderController headerController) { + public DonationPageView(DonationPageController donationPageController, HeaderController headerController, MainController mainController) { this.controller = donationPageController; + this.mainController = mainController; getStylesheets().add(getClass().getResource("/donationpage/donation.css").toExternalForm()); Header headerView = new Header(headerController); @@ -61,13 +73,14 @@ public Button createDonationButton(String title, String amount) { button.setTextAlignment(TextAlignment.CENTER); button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); button.getStyleClass().add("donation-button"); + + BigDecimal parsedAmount = parseAmount(amount); + elementAmounts.put(button, parsedAmount); + button.setOnAction(e -> { - if (button.getStyleClass().contains("donation-button-selected")) { - button.getStyleClass().remove("donation-button-selected"); - } else { - button.getStyleClass().add("donation-button-selected"); - } + selectDonationElement(button); }); + allDonationElements.add(button); return button; } private VBox createCustomButton() { @@ -89,6 +102,19 @@ private VBox createCustomButton() { box.setAlignment(Pos.CENTER); box.getStyleClass().add("donation-button"); + + box.setOnMouseClicked(e -> { + try { + BigDecimal amount = new BigDecimal(amountField.getText().trim()); + elementAmounts.put(box, amount); + selectDonationElement(box); + } catch (NumberFormatException exception) { + System.err.println("Invalid custom donation amount: " + amountField.getText()); + } + + }); + + allDonationElements.add(box); return box; } private HBox createDonateSection() { @@ -100,7 +126,35 @@ private HBox createDonateSection() { section.setAlignment(Pos.CENTER); section.setPadding(new Insets(20, 0, 30, 0)); return section; + } + private void selectDonationElement(Node element) { + // Remove selected class from all elements + for (Node node : allDonationElements) { + node.getStyleClass().remove("donation-button-selected"); + } + + element.getStyleClass().add("donation-button-selected"); + + // Extract and store the amount + extractAndStoreAmount(element); + } + + private void extractAndStoreAmount(Node element) { + BigDecimal amount = elementAmounts.get(element); + if (amount != null) { + mainController.setCurrentDonationAmount(amount); + } else { + System.err.println("Error: No amount found for selected element"); + } + } + + private BigDecimal parseAmount(String amountStr) { + try { + return new BigDecimal(amountStr.replace("kr", "").trim()); + } catch (NumberFormatException e) { + return BigDecimal.ZERO; + } } -} +} \ No newline at end of file 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 d555301..96d83d7 100644 --- a/src/main/java/edu/group5/app/view/loginpage/LoginPageView.java +++ b/src/main/java/edu/group5/app/view/loginpage/LoginPageView.java @@ -15,9 +15,13 @@ public class LoginPageView extends BorderPane { private final LoginPageController controller; + private TextField emailField; + private PasswordField passwordField; + private Label errorLabel; public LoginPageView(LoginPageController loginPageController) { this.controller = loginPageController; + this.controller.setView(this); LoginHeader loginHeaderView = new LoginHeader(); setTop(loginHeaderView); @@ -33,6 +37,20 @@ public LoginPageView(LoginPageController loginPageController) { setCenter(content); } + + public String getEmail() { + return emailField.getText(); + } + + public char[] getPassword() { + return passwordField.getText().toCharArray(); + } + + public void showError(String message) { + errorLabel.setText(message); + errorLabel.setStyle("-fx-text-fill: red;"); + } + private VBox getOuterSection() { VBox outerSection = new VBox(12); outerSection.setAlignment(Pos.CENTER); @@ -44,13 +62,20 @@ private VBox getLoginBox() { VBox loginSection = new VBox(12); loginSection.setAlignment(Pos.CENTER); loginSection.setId("login-box"); - loginSection.getChildren().addAll(getEmailBox(), getPasswordBox(), getLoginBtn()); + loginSection.getChildren().addAll(getErrorLabel(), getEmailBox(), getPasswordBox(), getLoginBtn()); return loginSection; } + + private Label getErrorLabel() { + errorLabel = new Label(); + errorLabel.setPrefHeight(20); + return errorLabel; + } + private VBox getEmailBox() { VBox emailBox = new VBox(); emailBox.setMaxWidth(300); - TextField emailField = new TextField(); + emailField = new TextField(); emailField.setPromptText("aurafarmer@gmail.com"); emailField.setMaxWidth(300); emailBox.getChildren().addAll(new Label("Email"), emailField); @@ -59,7 +84,7 @@ private VBox getEmailBox() { private VBox getPasswordBox() { VBox passwordBox = new VBox(); passwordBox.setMaxWidth(300); - PasswordField passwordField = new PasswordField(); + passwordField = new PasswordField(); passwordField.setMaxWidth(300); passwordBox.getChildren().addAll(new Label("Password"), passwordField); return passwordBox; @@ -68,6 +93,7 @@ private Button getLoginBtn() { Button loginBtn = new Button("Log In"); loginBtn.setMaxWidth(300); loginBtn.setId("login-btn"); + loginBtn.setOnMouseClicked(e -> controller.handleLoginBtn()); return loginBtn; } public Button getRegisterBtn() { diff --git a/src/main/java/edu/group5/app/view/loginpage/SignInPageView.java b/src/main/java/edu/group5/app/view/loginpage/SignInPageView.java index 1c5ddae..6ee0e9b 100644 --- a/src/main/java/edu/group5/app/view/loginpage/SignInPageView.java +++ b/src/main/java/edu/group5/app/view/loginpage/SignInPageView.java @@ -14,9 +14,15 @@ public class SignInPageView extends BorderPane { private final SignInPageController controller; + private TextField nameField; + private TextField surnameField; + private TextField emailField; + private PasswordField passwordField; + private Label errorLabel; public SignInPageView(SignInPageController signInPageController) { this.controller = signInPageController; + this.controller.setView(this); setTop(new LoginHeader()); HBox content = new HBox(); @@ -32,6 +38,29 @@ public SignInPageView(SignInPageController signInPageController) { setCenter(content); } + + + public String getFirstName() { + return nameField.getText(); + } + + public String getLastName() { + return surnameField.getText(); + } + + public String getEmail() { + return emailField.getText(); + } + + public char[] getPassword() { + return passwordField.getText().toCharArray(); + } + + public void showError(String message) { + errorLabel.setText(message); + errorLabel.setStyle("-fx-text-fill: red;"); + } + private VBox getOuterSection() { VBox outerSection = new VBox(12); outerSection.setAlignment(Pos.CENTER); @@ -43,21 +72,28 @@ private VBox getSignInBox() { VBox signInSection = new VBox(12); signInSection.setAlignment(Pos.CENTER); signInSection.setId("login-box"); - signInSection.getChildren().addAll(getNameRow(), getEmailBox(), getPasswordBox(), getSignInBtn()); + signInSection.getChildren().addAll(getErrorLabel(), getNameRow(), getEmailBox(), getPasswordBox(), getSignInBtn()); return signInSection; } + + private Label getErrorLabel() { + errorLabel = new Label(); + errorLabel.setPrefHeight(20); + return errorLabel; + } + private HBox getNameRow() { HBox nameRow = new HBox(12); nameRow.setMaxWidth(300); VBox nameBox = new VBox(); - TextField nameField = new TextField(); + nameField = new TextField(); nameField.setPromptText("Jinwoo"); HBox.setHgrow(nameBox, Priority.ALWAYS); nameBox.getChildren().addAll(new Label("First name"), nameField); VBox surnameBox = new VBox(); - TextField surnameField = new TextField(); + surnameField = new TextField(); surnameField.setPromptText("Son"); HBox.setHgrow(surnameBox, Priority.ALWAYS); surnameBox.getChildren().addAll(new Label("Last Name"), surnameField); @@ -69,7 +105,7 @@ private HBox getNameRow() { private VBox getEmailBox() { VBox emailBox = new VBox(); emailBox.setMaxWidth(300); - TextField emailField = new TextField(); + emailField = new TextField(); emailField.setPromptText("aurafarmer@gmail.com"); emailField.setMaxWidth(300); emailBox.getChildren().addAll(new Label("Email"), emailField); @@ -78,7 +114,7 @@ private VBox getEmailBox() { private VBox getPasswordBox() { VBox passwordBox = new VBox(); passwordBox.setMaxWidth(300); - PasswordField passwordField = new PasswordField(); + passwordField = new PasswordField(); passwordField.setMaxWidth(300); passwordBox.getChildren().addAll(new Label("Password"), passwordField); return passwordBox; diff --git a/src/main/java/edu/group5/app/view/organizationpage/OrganizationPageView.java b/src/main/java/edu/group5/app/view/organizationpage/OrganizationPageView.java index 4155ccd..4ec7c91 100644 --- a/src/main/java/edu/group5/app/view/organizationpage/OrganizationPageView.java +++ b/src/main/java/edu/group5/app/view/organizationpage/OrganizationPageView.java @@ -1,7 +1,9 @@ package edu.group5.app.view.organizationpage; import edu.group5.app.control.HeaderController; +import edu.group5.app.control.MainController; import edu.group5.app.control.OrganizationPageController; +import edu.group5.app.model.organization.Organization; import edu.group5.app.view.Header; import javafx.geometry.Pos; import javafx.scene.control.Button; @@ -17,9 +19,11 @@ public class OrganizationPageView extends BorderPane { private final OrganizationPageController controller; + private final MainController mainController; - public OrganizationPageView(OrganizationPageController controller, HeaderController headerController) { + public OrganizationPageView(OrganizationPageController controller, HeaderController headerController, MainController mainController) { this.controller = controller; + this.mainController = mainController; getStylesheets().add(getClass().getResource("/organizationpage/organizationpage.css").toExternalForm()); Header headerView = new Header(headerController); setTop(headerView); @@ -58,8 +62,11 @@ private StackPane createImageContainer() { imageContainer.setPrefWidth(120); imageContainer.setMaxWidth(Double.MAX_VALUE); + Organization org = mainController.getCurrentOrganization(); + String imagePath = org != null ? "/browsepage/images/children_of_shambala.png" : "/browsepage/images/children_of_shambala.png"; + ImageView logo = new ImageView( - new Image(getClass().getResource("/browsepage/images/children_of_shambala.png").toExternalForm()) + new Image(getClass().getResource(imagePath).toExternalForm()) ); logo.setId("logo"); @@ -71,15 +78,17 @@ private StackPane createImageContainer() { } private VBox createOrgInfoSection() { + Organization org = mainController.getCurrentOrganization(); + VBox orgInfoSection = new VBox(); orgInfoSection.setSpacing(50); VBox orgNameAndDescription = new VBox(); - Label orgName = new Label("Shambala Foundation"); + Label orgName = new Label(org != null ? org.name() : "Unknown Organization"); orgName.setId("orgName"); - Text description = new Text("Descriptive text"); + Text description = new Text(org != null ? org.description() : "No description available"); description.setId("description"); orgNameAndDescription.getChildren().addAll(orgName, description); 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 547b812..62711aa 100644 --- a/src/main/java/edu/group5/app/view/userpage/UserPageView.java +++ b/src/main/java/edu/group5/app/view/userpage/UserPageView.java @@ -1,25 +1,35 @@ package edu.group5.app.view.userpage; import edu.group5.app.control.HeaderController; -import edu.group5.app.model.user.Customer; +import edu.group5.app.control.MainController; +import edu.group5.app.model.donation.Donation; +import edu.group5.app.model.organization.Organization; +import edu.group5.app.model.user.User; import edu.group5.app.view.Header; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.text.Text; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + public class UserPageView extends BorderPane { - private final Customer customer; + private final User currentUser; + private final MainController mainController; + + public UserPageView(HeaderController headerController, MainController mainController) { + this.mainController = mainController; + this.currentUser = mainController.getCurrentUser(); - public UserPageView(HeaderController headerController, Customer customer) { - this.customer = customer; getStylesheets().add(getClass().getResource("/userpage/userpage.css").toExternalForm()); Header headerView = new Header(headerController); @@ -30,6 +40,7 @@ public UserPageView(HeaderController headerController, Customer customer) { content.getChildren().addAll(createProfileSection(), createCausesSection(), createDonationsSection()); setCenter(content); } + private HBox createProfileSection() { ImageView avatar = new ImageView(new Image(getClass().getResourceAsStream("/userpage/account_circle.png"))); avatar.setFitWidth(150); @@ -37,39 +48,89 @@ private HBox createProfileSection() { avatar.setPreserveRatio(true); avatar.setId("avatar"); - Text name = new Text(customer.getFirstName() + " " + customer.getLastName()); + Text name = new Text(currentUser.getFirstName() + " " + currentUser.getLastName()); name.setId("profile-name"); - Label email = new Label(customer.getEmail()); + Label email = new Label(currentUser.getEmail()); email.getStyleClass().add("profile-info"); Label location = new Label("Trondheim, Norway"); location.getStyleClass().add("profile-info"); - VBox info = new VBox(10, name, email, location); + Button logoutBtn = new Button("Logout"); + logoutBtn.getStyleClass().add("logout-button"); + logoutBtn.setOnAction(e -> mainController.logout()); + + VBox info = new VBox(10, name, email, location, logoutBtn); info.setAlignment(Pos.CENTER_LEFT); HBox profile = new HBox(40, avatar, info); profile.setAlignment(Pos.CENTER_LEFT); return profile; } + private VBox createCausesSection() { Text title = new Text("YOUR SUPPORTED CAUSES"); title.getStyleClass().add("section-title"); - Pane causesPlaceholder = new Pane(); - causesPlaceholder.getStyleClass().add("section-box"); - - return new VBox(10, title, causesPlaceholder); + VBox causesBox = new VBox(10); + causesBox.getStyleClass().add("section-box"); + causesBox.setPadding(new Insets(10)); + + HashMap userDonations = mainController.getDonationService() + .getDonationRepository().filterByUser(currentUser.getUserId()); + + Set uniqueOrgs = new HashSet<>(); + for (Donation donation : userDonations.values()) { + uniqueOrgs.add(donation.organizationId()); + } + + if (uniqueOrgs.isEmpty()) { + Label noCauses = new Label("No causes supported yet"); + noCauses.setStyle("-fx-text-fill: #999;"); + causesBox.getChildren().add(noCauses); + } else { + for (int orgId : uniqueOrgs) { + Organization org = mainController.getOrganizationService().findByOrgNumber(orgId); + if (org != null) { + Label causeLabel = new Label("• " + org.name()); + causesBox.getChildren().add(causeLabel); + } + } + } + + return new VBox(10, title, causesBox); } + private VBox createDonationsSection() { Text title = new Text("PREVIOUS DONATIONS"); title.getStyleClass().add("section-title"); - Pane donationsPlaceholder = new Pane(); - donationsPlaceholder.getStyleClass().add("section-box"); - - return new VBox(10, title, donationsPlaceholder); + VBox donationsBox = new VBox(10); + donationsBox.getStyleClass().add("section-box"); + donationsBox.setPadding(new Insets(10)); + + HashMap userDonations = mainController.getDonationService() + .getDonationRepository().filterByUser(currentUser.getUserId()); + + if (userDonations.isEmpty()) { + Label noDonations = new Label("No donations yet"); + noDonations.setStyle("-fx-text-fill: #999;"); + donationsBox.getChildren().add(noDonations); + } else { + for (Donation donation : userDonations.values()) { + Organization org = mainController.getOrganizationService() + .findByOrgNumber(donation.organizationId()); + String orgName = (org != null) ? org.name() : "Unknown Organization"; + + Label donationLabel = new Label( + orgName + " • " + donation.amount() + " • " + donation.date() + ); + donationsBox.getChildren().add(donationLabel); + } + } + + return new VBox(10, title, donationsBox); } } diff --git a/src/main/resources/browsepage/browse_org.css b/src/main/resources/browsepage/browse_org.css index 96ce14e..9a50c71 100644 --- a/src/main/resources/browsepage/browse_org.css +++ b/src/main/resources/browsepage/browse_org.css @@ -3,7 +3,6 @@ -fx-border-width: 1px; -fx-border-radius: 1em; -fx-padding: 5px; - -fx-pref-width: 10px; -fx-background-color: white; -fx-background-radius: 1em; } @@ -20,6 +19,7 @@ #orgName { -fx-font-size: x-large; -fx-font-weight: bold; + -fx-text-alignment: center; } #checkMarkContainer {} \ No newline at end of file diff --git a/src/main/resources/userpage/userpage.css b/src/main/resources/userpage/userpage.css index 6253079..8401a77 100644 --- a/src/main/resources/userpage/userpage.css +++ b/src/main/resources/userpage/userpage.css @@ -16,4 +16,16 @@ -fx-pref-height: 120px; -fx-pref-width: 700px; -fx-background-radius: 6; +} +.logout-button { + -fx-background-color: #e03030; + -fx-text-fill: white; + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-padding: 8 20; + -fx-background-radius: 4; + -fx-cursor: hand; +} +.logout-button:hover { + -fx-background-color: #c02020; } \ No newline at end of file diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationServiceTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationServiceTest.java new file mode 100644 index 0000000..e34aba7 --- /dev/null +++ b/src/test/java/edu/group5/app/model/organization/OrganizationServiceTest.java @@ -0,0 +1,70 @@ +package edu.group5.app.model.organization; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +public class OrganizationServiceTest { + private OrganizationRepository repo; + private OrganizationService service; + private Object[] content; + + @BeforeEach + public void setUp() { + Map orgMap = new HashMap<>(); + orgMap.put("org_number", "1"); + orgMap.put("name", "Misjonsalliansen"); + orgMap.put("status", "approved"); + orgMap.put("url", "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/"); + orgMap.put("is_pre_approved", false); + + content = new Object[]{orgMap}; + repo = new OrganizationRepository(content); + service = new OrganizationService(repo); + } + + @Test + void constructor_throwsIfNull() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> new OrganizationService(null)); + assertEquals("OrganizationRepository cannot be null", ex.getMessage()); + } + + @Test + void testGetOrganizationRepository() { + assertEquals(repo, service.getOrganizationRepository()); + } + + @Test + void testGetTrustedOrganizations() { + Map trustedOrgs = service.getTrustedOrganizations(); + assertNotNull(trustedOrgs); + assertTrue(trustedOrgs.containsKey(1)); + Organization org = trustedOrgs.get(1); + assertEquals(1, org.orgNumber()); + assertEquals("Misjonsalliansen", org.name()); + assertTrue(org.trusted()); + assertEquals("https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", org.websiteUrl()); + assertFalse(org.isPreApproved()); + } + + @Test + void testFindByOrgNumber() { + Organization org = service.findByOrgNumber(1); + assertNotNull(org); + assertEquals(1, org.orgNumber()); + assertEquals("Misjonsalliansen", org.name()); + } + + + @Test + void testFindByOrgName() { + Organization org = service.findByOrgName("Misjonsalliansen"); + assertNotNull(org); + assertEquals(1, org.orgNumber()); + assertEquals("Misjonsalliansen", org.name()); + } +} diff --git a/src/test/java/edu/group5/app/model/user/UserServiceTest.java b/src/test/java/edu/group5/app/model/user/UserServiceTest.java index c6713d7..8f56957 100644 --- a/src/test/java/edu/group5/app/model/user/UserServiceTest.java +++ b/src/test/java/edu/group5/app/model/user/UserServiceTest.java @@ -17,7 +17,7 @@ public class UserServiceTest { private UserRepository repo; - private UserService service; + private UserService service; @BeforeEach void setUp() {