diff --git a/src/main/java/edu/group5/app/App.java b/src/main/java/edu/group5/app/App.java index 6704bd2..d657fb8 100644 --- a/src/main/java/edu/group5/app/App.java +++ b/src/main/java/edu/group5/app/App.java @@ -1,25 +1,100 @@ 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.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.User; +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; + +import java.util.logging.Logger; + /** - * 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 { + DbWrapper dbWrapper; + UserRepository userRepository; + DonationRepository donationRepository; + private Logger logger; + private MainController controller; + private Scene scene; + @Override - public void start(Stage stage) { - MainController controller = new MainController(); + public void init() { + this.logger = Logger.getLogger(App.class.getName()); + this.logger.info("Application starting"); + + this.dbWrapper = new DbWrapper(false); + OrgApiWrapper orgApiWrapper = new OrgApiWrapper("https://app.innsamlingskontrollen.no/api/public/v1/all"); + + while (!dbWrapper.connect()) { + this.logger.warning("Failed to connect to database"); + } + + // 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 + this.userRepository = new UserRepository(userData); + this.donationRepository = new DonationRepository(donationData); + OrganizationRepository organizationRepository = new OrganizationRepository(organizationData); + + // Create services (backend wiring) + UserService userService = new UserService(this.userRepository); + DonationService donationService = new DonationService(this.donationRepository, organizationRepository); + OrganizationService organizationService = new OrganizationService(organizationRepository); - Scene scene = controller.getMainView().getScene(); - controller.showLoginPage(); + this.controller = new MainController(userService, donationService, organizationService); + + this.scene = controller.getMainView().getScene(); + } + + @Override + public void start(Stage stage) { + this.controller.showLoginPage(); stage.getIcons().add(new Image(getClass().getResource("/header/images/hmh-logo.png").toExternalForm())); stage.setTitle("Help-Me-Help"); - stage.setScene(scene); + stage.setScene(this.scene); stage.show(); } + + @Override + public void stop() throws Exception { + super.stop(); + this.logger.info("Application stopping"); + this.dbWrapper.connect(); + this.dbWrapper.exportUsers(this.userRepository.export()); + this.dbWrapper.exportDonations(this.donationRepository.export()); + this.dbWrapper.disconnect(); + } + + 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..7e3adc0 100644 --- a/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java +++ b/src/main/java/edu/group5/app/control/wrapper/DbWrapper.java @@ -95,10 +95,13 @@ public List importUsers() { public int exportUsers(List data) { this.importUsers(); - + if (data == null) { throw new IllegalArgumentException("data can't be null"); } + if (data.isEmpty()) { + return 0; + } if (data.get(0).length != 6) { throw new IllegalArgumentException("data's arrays must have a length of 6"); } @@ -141,7 +144,7 @@ public int exportUsers(List data) { return rowsAffected; } - private List importDonations() { + public List fetchAllDonations() { return this.importDonations(0, true); } @@ -182,12 +185,15 @@ 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"); } + if (data.isEmpty()) { + return 0; + } if (data.get(0).length != 6) { throw new IllegalArgumentException("data's arrays must have a length of 6"); } diff --git a/src/main/java/edu/group5/app/model/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java index f3363b7..3cd1fa9 100644 --- a/src/main/java/edu/group5/app/model/DBRepository.java +++ b/src/main/java/edu/group5/app/model/DBRepository.java @@ -1,18 +1,21 @@ package edu.group5.app.model; + import java.util.HashMap; import java.util.Map; import java.util.List; + /** * Abstract base class for repositories that store their data * in a database-related structure. * *

- * Extends {@link Repository} and specifies that the content - * is stored as a {@link Map}. + * Extends {@link Repository} and specifies that the content + * is stored as a {@link Map}. *

*/ public abstract class DBRepository extends Repository { protected final Map contentLock; + /** * Constructs a DBRepository with the given content. * @@ -23,19 +26,18 @@ protected DBRepository(Map content) { this.contentLock = new HashMap<>(); } - protected void updateContentLock() { - synchronized (contentLock) { - contentLock.clear(); - contentLock.putAll(this.content); - } - } + protected abstract void updateContentLock(); public abstract boolean addContent(V value); /** - * Exports the repository content as a list of Object arrays, where each array represents a row of data. - * This method is intended for converting the repository content into a format suitable for database storage or export. - * @return a List of Object arrays representing the repository content for database export + * Exports the repository content as a list of Object arrays, where each array + * represents a row of data. + * This method is intended for converting the repository content into a format + * suitable for database storage or export. + * + * @return a List of Object arrays representing the repository content for + * database export */ public abstract List export(); } diff --git a/src/main/java/edu/group5/app/model/donation/DonationRepository.java b/src/main/java/edu/group5/app/model/donation/DonationRepository.java index 6181460..a55ea74 100644 --- a/src/main/java/edu/group5/app/model/donation/DonationRepository.java +++ b/src/main/java/edu/group5/app/model/donation/DonationRepository.java @@ -15,7 +15,7 @@ * Repository class for Donation. * *

- * Extends {@link DBRepository} and manages Donation entities. + * Extends {@link DBRepository} and manages Donation entities. *

*/ public class DonationRepository extends DBRepository { @@ -23,10 +23,12 @@ public class DonationRepository extends DBRepository { /** * Constructs DonationRepository from a list of Object[] rows from the database. - * @param rows List of Object[] representing donations from the DB. - * Each row must have 6 elements: - * [donationId, userId, organizationId, amount, date, paymentMethod] - * @throws IllegalArgumentException if the input list is null or any row is invalid + * + * @param rows List of Object[] representing donations from the DB. + * Each row must have 6 elements: + * [donationId, userId, organizationId, amount, date, paymentMethod] + * @throws IllegalArgumentException if the input list is null or any row is + * invalid */ public DonationRepository(List rows) { super(new HashMap<>()); @@ -35,12 +37,12 @@ public DonationRepository(List rows) { } this.content = new HashMap<>(); for (Object[] row : rows) { - if (row == null || row.length != 6 ) { + if (row == null || row.length != 6) { throw new IllegalArgumentException("Each row must contain exactly 6 elements"); } int donationId = (int) row[0]; int customerId = (int) row[1]; - int organizationId = (int) row[2]; + int organizationId = (int) row[2]; BigDecimal amount = (BigDecimal) row[3]; Timestamp date = (Timestamp) row[4]; String paymentMethod = (String) row[5]; @@ -48,23 +50,39 @@ public DonationRepository(List rows) { Donation donation = new Donation(donationId, customerId, organizationId, amount, date, paymentMethod); this.content.put(donationId, donation); } - super.updateContentLock(); + this.updateContentLock(); + } + + @Override + protected void updateContentLock() { + synchronized (contentLock) { + this.contentLock.clear(); + this.contentLock.putAll(this.content); + } } @Override public List export() { - return content.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(entry -> { Donation donation = entry.getValue(); - return new Object[] { - donation.donationId(), donation.userId(), - donation.organizationId(), donation.amount(), - donation.date(), donation.paymentMethod()};}) - .toList(); + Map output = new HashMap<>(this.content); + for (int i : super.contentLock.keySet()) { + output.remove(i); + } + this.updateContentLock(); + return output.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> { + Donation donation = entry.getValue(); + return new Object[] { + donation.donationId(), donation.userId(), + donation.organizationId(), donation.amount(), + donation.date(), donation.paymentMethod() }; + }) + .toList(); } /** * Retrieves a donation by its ID. + * * @param donationId the ID of the donation to retrieve * @return the Donation object with the specified ID, or null if not found * @throws IllegalArgumentException if the donationId is not positive @@ -77,10 +95,12 @@ public Donation getDonationById(int donationId) { } /** - * Generates the next donation ID based on the current maximum ID in the repository. + * Generates the next donation ID based on the current maximum ID in the + * repository. + * * @return the next donation ID to be used for a new donation */ - public int getNextDonationId() { + public int getNextDonationId() { return content.keySet().stream().max(Integer::compareTo).orElse(0) + 1; } /* TODO change this when data database is introduced */ @@ -91,22 +111,22 @@ public Map getAllDonations() { /** * Adds a new donation to the repository *

- * The donation is stored using its {@code donationId} as the key. - * If a donation with the same ID already exists, the donation - * will not be added. + * The donation is stored using its {@code donationId} as the key. + * If a donation with the same ID already exists, the donation + * will not be added. *

* * @param donation the donation to add * @return {@code true} if the donation was successfully added, and - * {@code false} if a donation with the same ID already exists + * {@code false} if a donation with the same ID already exists */ @Override public boolean addContent(Donation donation) { if (donation == null) { throw new IllegalArgumentException("Donation cannot be null"); } - if (content.containsKey(donation.donationId())){ - return false; + if (content.containsKey(donation.donationId())) { + return false; } this.content.put(donation.donationId(), donation); return true; @@ -116,12 +136,12 @@ public boolean addContent(Donation donation) { * Returns all donations sorted by date (ascending). * *

- * The returned map preserves the sorted order. + * The returned map preserves the sorted order. *

* * @return a new {@link HashMap} containing the donations sorted by date */ - public HashMap sortByDate(){ + public HashMap sortByDate() { return content.entrySet().stream() .sorted(Map.Entry.comparingByValue( Comparator.comparing(Donation::date))) @@ -136,7 +156,7 @@ public HashMap sortByDate(){ * Returns all donations sorted by amount (ascending). * *

- * The returned map preserves the sorted order from lowest to highest amount. + * The returned map preserves the sorted order from lowest to highest amount. *

* * @return a new {@link HashMap} containing the donations sorted by amount. @@ -149,12 +169,12 @@ public HashMap sortByAmount() { Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, - LinkedHashMap::new - )); + LinkedHashMap::new)); } /** * Returns all donations associated with a specific organization. + * * @param orgNumber the organization ID to filter by * @return a map containing all donations that belong to the given organization * @throws IllegalArgumentException if the orgNumber is not positive @@ -165,17 +185,17 @@ public HashMap filterByOrganization(int orgNumber) { } return content.entrySet() .stream() - .filter(entry -> entry.getValue().organizationId() == orgNumber) + .filter(entry -> entry.getValue().organizationId() == orgNumber) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, - LinkedHashMap::new - )); + LinkedHashMap::new)); } /** * Returns all donations made by a specific user. + * * @param userId the user ID to filter by * @return a map containing all donations that belong to the given user * @throws IllegalArgumentException if the userId is not positive @@ -190,7 +210,6 @@ public HashMap filterByUser(int userId) { Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, - LinkedHashMap::new - )); + LinkedHashMap::new)); } } 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/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java index 692126f..193f433 100644 --- a/src/main/java/edu/group5/app/model/user/UserRepository.java +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -6,11 +6,13 @@ import edu.group5.app.model.DBRepository; -public class UserRepository extends DBRepository{ +public class UserRepository extends DBRepository { public final static String ROLE_CUSTOMER = "Customer"; + /** * Constructs UserRepository using Hashmap, * and extends the content from DBRepository. + * * @param content the underlying map used to store users, * where the key represents the user ID */ @@ -20,7 +22,7 @@ public UserRepository(List rows) { throw new IllegalArgumentException("The list of rows cannot be null"); } for (Object[] row : rows) { - if (row == null || row.length != 6 ) { + if (row == null || row.length != 6) { throw new IllegalArgumentException("Each row must contain exactly 6 elements"); } int userId = (int) row[0]; @@ -38,28 +40,46 @@ public UserRepository(List rows) { } this.content.put(userId, user); } - super.updateContentLock(); + this.updateContentLock(); } + @Override + protected void updateContentLock() { + synchronized (contentLock) { + this.contentLock.clear(); + this.contentLock.putAll(this.content); + } + } @Override public List export() { - return content.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(entry -> { User user = entry.getValue(); - return new Object[]{user.getUserId(), user.getRole(), - user.getFirstName(), user.getLastName(), - user.getEmail(), user.getPasswordHash()};}) + Map output = new HashMap<>(this.content); + for (int i : contentLock.keySet()) { + output.remove(i); + } + this.updateContentLock(); + return output.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> { + User user = entry.getValue(); + return new Object[] { user.getUserId(), user.getRole(), + user.getFirstName(), user.getLastName(), + user.getEmail(), user.getPasswordHash() }; + }) .toList(); + } public HashMap getUsers() { return new HashMap<>(content); } + /** * Retrieves a user by their unique identifier. + * * @param userId the unique identifier of the user to retrieve - * @return the user with the specified ID, or {@code null} if no such user exists + * @return the user with the specified ID, or {@code null} if no such user + * exists * @throws IllegalArgumentException if the userId is not positive */ public User getUserById(int userId) { @@ -72,12 +92,13 @@ public User getUserById(int userId) { /** * Generates the next user ID based on repository size. * Uses size+1 and then moves forward if that ID is already taken. + * * @return the next available user ID * @throws IllegalStateException if no available user ID can be found */ - public int getNextUserId() { + public int getNextUserId() { if (content.isEmpty()) { - return 1; + return 1; } int maxKey = content.keySet().stream().max(Integer::compareTo).orElseThrow( () -> new IllegalStateException("No keys found")); @@ -88,21 +109,21 @@ public int getNextUserId() { /** * Adds a new user to the repository *

- * The user is stored using its {@code userId} as the key. - * If a user with the same ID already exists, the user - * will not be added. + * The user is stored using its {@code userId} as the key. + * If a user with the same ID already exists, the user + * will not be added. *

* * @param user the user to add * @return {@code true} if the user was successfully added, and - * {@code false} if a user with the same ID already exists + * {@code false} if a user with the same ID already exists */ @Override public boolean addContent(User user) { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } - if (content.containsKey(user.getUserId())){ + if (content.containsKey(user.getUserId())) { return false; } this.content.put(user.getUserId(), user); @@ -111,8 +132,10 @@ public boolean addContent(User user) { /** * Finds a user by their email address. + * * @param email the email address of the user to find - * @return the user with the specified email address, or {@code null} if no such user exists + * @return the user with the specified email address, or {@code null} if no such + * user exists */ public User findUserByEmail(String email) { if (email == null || email.trim().isEmpty()) { @@ -122,5 +145,5 @@ public User findUserByEmail(String email) { .filter(user -> user.getEmail().equals(email)) .findFirst() .orElse(null); - } + } } 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..9ab73ee 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() + " kr" + " • " + 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/control/wrapper/DbWrapperDonationsTest.java b/src/test/java/edu/group5/app/control/wrapper/DbWrapperDonationsTest.java index f31d57c..8c401c4 100644 --- a/src/test/java/edu/group5/app/control/wrapper/DbWrapperDonationsTest.java +++ b/src/test/java/edu/group5/app/control/wrapper/DbWrapperDonationsTest.java @@ -196,4 +196,12 @@ public void addingDonationListWithNullInRowThrowsExpectedException() { assertTrue(this.db.disconnect()); assertEquals("One or more rows in data contains null values", exception.getMessage()); } + + @Test + public void dataIsEmptyAfterExportingAndImportingEmptyList() { + assertTrue(this.db.importDonations((int) this.users.get(0)[0]).size() == 0); + assertEquals(0, this.db.exportDonations(new ArrayList())); + assertTrue(this.db.importDonations((int) this.users.get(0)[0]).size() == 0); + assertTrue(this.db.disconnect()); + } } diff --git a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java index bdf0110..ce81b2a 100644 --- a/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/donation/DonationRepositoryTest.java @@ -263,4 +263,13 @@ void filterByUserIdThrowsIfNegative() { () -> repo.filterByUser(0)); assertEquals("User ID must be positive", ex.getMessage()); } + + @Test + void exportExportsOnlyNewInformation() { + repo.addContent(donation1); + repo.addContent(donation2); + assertEquals(2, repo.export().size()); + repo.addContent(donation3); + assertEquals(1, repo.export().size()); + } } \ 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/UserRepositoryTest.java b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java index 0700828..a4afbc8 100644 --- a/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/user/UserRepositoryTest.java @@ -12,6 +12,7 @@ public class UserRepositoryTest { private UserRepository repo; private List rows; +private User additionalUser; @BeforeEach void setUp() { @@ -19,6 +20,7 @@ void setUp() { rows.add(new Object[]{1, "Customer", "John", "Cena", "john@example.com", "hashedpass"}); rows.add(new Object[]{2, "Customer", "Jane", "Doe", "jane@example.com", "hashedpass"}); repo = new UserRepository(rows); + this.additionalUser = new Customer(3, "John", "Doe", "john@example.com", "hashedpass"); } @Test @@ -145,4 +147,11 @@ void exportContainsAllUsers() { assertEquals(2, exported.get(1)[0]); assertEquals("Customer", exported.get(0)[1]); } + + @Test + void exportExportsOnlyNewInformation() { + assertEquals(0, repo.export().size()); + repo.addContent(this.additionalUser); + assertEquals(1, repo.export().size()); + } } \ No newline at end of file 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() {